diff --git a/Makefile b/Makefile index e93ea5f..236dbb8 100644 --- a/Makefile +++ b/Makefile @@ -42,3 +42,6 @@ local-debug: local-down: bash deploy/local/scripts/down-local.sh + +validate-prophet-mesh-scope-mirror: + node scripts/validate-prophet-mesh-scope-mirror.mjs diff --git a/contracts/prophet-mesh/prophet-mesh-memory-scope.v0.1.json b/contracts/prophet-mesh/prophet-mesh-memory-scope.v0.1.json new file mode 100644 index 0000000..4db6d63 --- /dev/null +++ b/contracts/prophet-mesh/prophet-mesh-memory-scope.v0.1.json @@ -0,0 +1,65 @@ +{ + "schemaVersion": "memory-mesh.cross-repo-scope-mirror.v0.1", + "kind": "CrossRepoScopeMirror", + "contractVersion": "v0.1", + "mirrorId": "memory-mesh:prophet-mesh-memory-scope:v0.1", + "createdAt": "2026-06-10T00:00:00Z", + "sourceAuthority": { + "repo": "SocioProphet/prophet-mesh", + "path": "specs/runtime-release-bundle.yaml", + "contractVersion": "v0.1", + "claim": "memory_scope field in execution_trace must be explicit and non-empty" + }, + "mirrorAuthority": { + "repo": "SocioProphet/memory-mesh", + "path": "contracts/prophet-mesh/prophet-mesh-memory-scope.v0.1.json", + "attestation": "memory-mesh independently mirrors the memory-scope policy boundary established by prophet-mesh" + }, + "scopePolicy": { + "requiredField": "execution_trace.memory_scope", + "allowedScopePatterns": [ + "relationship_context:approved", + "noetica-session-local", + "noetica-session-local:*", + "unscoped" + ], + "prohibitedValues": [null, ""], + "enforcement": "reject_if_absent_or_empty", + "effectBoundary": { + "workspaceWriteEnabled": false, + "externalSendEnabled": false, + "effectEnabled": false + } + }, + "privatePreviewInvariants": [ + "execution_trace_must_include_memory_scope", + "memory_scope_must_be_explicit", + "memory_scope_must_not_be_empty_string", + "memory_scope_must_match_allowed_pattern_or_be_scoped_local" + ], + "nonProductionBoundary": { + "claim": "This mirror contract covers dry-run and receipt-only modes only. Live execution scope policy requires a separate contract version.", + "coveredModes": ["dry_run_receipt_preview", "receipt_only"], + "excludedModes": ["live_execution"] + }, + "crossRepoTraceability": { + "prophetMeshAdapterRef": { + "repo": "SocioProphet/agentplane", + "path": "contracts/prophet-mesh/prophet-mesh-agentplane-adapter.v0.1.json", + "contractVersion": "v0.1", + "mergeCommit": "faa767f42028ad0f2475c993700cdbef8490a38e", + "contentSha256": "38a3edb62813521a62f257f3f952271255d25dc3a05a14d6b96f04a6ff9b4268", + "mode": "dry_run_receipt_preview" + }, + "runtimeReleaseBundleContract": { + "repo": "SocioProphet/prophet-mesh", + "path": "specs/runtime-release-bundle.yaml", + "invariant": "execution_trace_must_include_memory_scope" + } + }, + "promotionNotes": [ + "Memory-scope authority was self-attested in prophet-mesh only prior to this mirror.", + "This contract closes the symmetry gap: registry mirrors choir authority, model-router mirrors routing policy, agentplane projects execution receipts, memory-mesh now mirrors memory-scope policy.", + "No live execution scope is claimed. Non-production boundary applies." + ] +} diff --git a/scripts/validate-prophet-mesh-scope-mirror.mjs b/scripts/validate-prophet-mesh-scope-mirror.mjs new file mode 100644 index 0000000..815a819 --- /dev/null +++ b/scripts/validate-prophet-mesh-scope-mirror.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +/** + * Validate the prophet-mesh memory-scope mirror contract. + * Lane G — confirms the mirror is structurally sound and cross-repo refs are present. + */ +import { readFile } from 'fs/promises' +import { fileURLToPath } from 'url' + +const CONTRACT_PATH = 'contracts/prophet-mesh/prophet-mesh-memory-scope.v0.1.json' +const REQUIRED_FIELDS = [ + 'schemaVersion', 'kind', 'contractVersion', 'mirrorId', + 'sourceAuthority', 'mirrorAuthority', 'scopePolicy', + 'privatePreviewInvariants', 'nonProductionBoundary', 'crossRepoTraceability' +] +const REQUIRED_INVARIANTS = [ + 'execution_trace_must_include_memory_scope', + 'memory_scope_must_be_explicit', + 'memory_scope_must_not_be_empty_string' +] +const REQUIRED_MODES = ['dry_run_receipt_preview', 'receipt_only'] + +function assert(condition, message) { + if (!condition) throw new Error(message) +} + +async function main() { + const raw = await readFile(CONTRACT_PATH, 'utf8') + const contract = JSON.parse(raw) + + for (const field of REQUIRED_FIELDS) { + assert(field in contract, `missing required field: ${field}`) + } + + assert(contract.kind === 'CrossRepoScopeMirror', 'kind must be CrossRepoScopeMirror') + assert(contract.contractVersion === 'v0.1', 'contractVersion must be v0.1') + + const policy = contract.scopePolicy + assert(policy.requiredField === 'execution_trace.memory_scope', 'scopePolicy.requiredField must be execution_trace.memory_scope') + assert(policy.enforcement === 'reject_if_absent_or_empty', 'scopePolicy.enforcement must be reject_if_absent_or_empty') + assert(policy.effectBoundary.effectEnabled === false, 'effectBoundary.effectEnabled must be false') + assert(policy.effectBoundary.workspaceWriteEnabled === false, 'effectBoundary.workspaceWriteEnabled must be false') + + for (const invariant of REQUIRED_INVARIANTS) { + assert( + contract.privatePreviewInvariants.includes(invariant), + `missing required invariant: ${invariant}` + ) + } + + const boundary = contract.nonProductionBoundary + for (const mode of REQUIRED_MODES) { + assert(boundary.coveredModes.includes(mode), `nonProductionBoundary.coveredModes must include ${mode}`) + } + assert(!boundary.coveredModes.includes('live_execution'), 'live_execution must not be in coveredModes') + + const trace = contract.crossRepoTraceability + assert(typeof trace.prophetMeshAdapterRef === 'object', 'crossRepoTraceability.prophetMeshAdapterRef required') + const ref = trace.prophetMeshAdapterRef + assert(ref.repo === 'SocioProphet/agentplane', 'adapter ref repo must be SocioProphet/agentplane') + assert(typeof ref.contentSha256 === 'string' && ref.contentSha256.length === 64, 'contentSha256 must be 64-char hex') + assert(ref.mode === 'dry_run_receipt_preview', 'adapter ref mode must be dry_run_receipt_preview') + + console.log(JSON.stringify({ + ok: true, + contractPath: CONTRACT_PATH, + mirrorId: contract.mirrorId, + scopePolicy: contract.scopePolicy.enforcement, + invariantsVerified: REQUIRED_INVARIANTS.length, + adapterRefSha256: ref.contentSha256.slice(0, 16) + '...', + nonProductionBoundary: boundary.coveredModes + }, null, 2)) +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)) + process.exit(1) +}) + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + // already runs via top-level await equivalent above +}