Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/scenario-learning-binding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: scenario-learning-binding

on:
pull_request:
paths:
- "schemas/scenario-learning-proposal-binding.schema.json"
- "examples/scenario-learning/scenario-learning-proposal-binding.example.json"
- "scripts/validate_scenario_learning_proposal_binding.py"
- ".github/workflows/scenario-learning-binding.yml"
- "README.md"
- "Makefile"
push:
branches:
- main
paths:
- "schemas/scenario-learning-proposal-binding.schema.json"
- "examples/scenario-learning/scenario-learning-proposal-binding.example.json"
- "scripts/validate_scenario_learning_proposal_binding.py"
- ".github/workflows/scenario-learning-binding.yml"
- "README.md"
- "Makefile"

jobs:
validate-scenario-learning-binding:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install validation dependency
run: python -m pip install jsonschema
- name: Validate ScenarioLearningProposalBinding example
run: make validate-scenario-learning-binding

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +25 to +34
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PYTHON ?= python

.PHONY: validate-upstreams validate-python validate-deploy-assets validate-agent-learning-proposal validate-governed-learning-lifecycle validate-workspace-recall-promotion validate-channel-provenance-write-gate validate-wallguard-memory-compartment-gate validate local-preflight local-up local-smoke local-debug local-down
.PHONY: validate-upstreams validate-python validate-deploy-assets validate-agent-learning-proposal validate-scenario-learning-binding validate-governed-learning-lifecycle validate-workspace-recall-promotion validate-channel-provenance-write-gate validate-wallguard-memory-compartment-gate validate local-preflight local-up local-smoke local-debug local-down

validate-upstreams:
$(PYTHON) scripts/validate_upstreams.py third_party/upstreams.lock.yaml
Expand All @@ -17,6 +17,9 @@ validate-agent-learning-proposal:
$(PYTHON) scripts/validate_agent_learning_proposal.py
$(PYTHON) scripts/validate_agent_learning_proposal_generator.py

validate-scenario-learning-binding:
$(PYTHON) scripts/validate_scenario_learning_proposal_binding.py

validate-governed-learning-lifecycle:
$(PYTHON) scripts/validate_governed_learning_lifecycle.py

Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ The workflow `.github/workflows/agent-learning-proposal.yml` runs this validatio

The example enforces review-only proposal mode, pending human review, no raw sensitive payload storage, evidence references, policy decision references, repo-local operating-contract destinations, and disabled durable writeback.

## Scenario learning proposal bindings

Memory Mesh now carries a narrow binding contract for durable learning candidates that originate from governed SCOPE-D adversarial scenarios.

This binding does not write memory. It links a SCOPE-D scenario, runtime decision receipts, evidence references, policy decision references, and memory effect metadata to a review-only learning proposal. The purpose is to preserve scenario-derived lessons without allowing scenario output, ATT&CK coverage, dashboard exports, or model summaries to become durable memory by accident.

The contract, example, validator, and workflow live at:

- `schemas/scenario-learning-proposal-binding.schema.json`
- `examples/scenario-learning/scenario-learning-proposal-binding.example.json`
- `scripts/validate_scenario_learning_proposal_binding.py`
- `.github/workflows/scenario-learning-binding.yml`

Validate locally:

```bash
python -m pip install jsonschema
make validate-scenario-learning-binding
```

The example enforces review-only mode, pending human review, no raw sensitive payload storage, scenario/evidence/policy references, required runtime decision receipt references, pending review memory effects, and disabled durable writeback.

## Channel provenance memory write gates

Memory Mesh now carries a channel-provenance write-gate contract for durable memory candidates that originate from channel-conditioned percepts.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"apiVersion": "memory.mesh.scenario-learning-binding/v1",
"kind": "ScenarioLearningProposalBinding",
"bindingId": "urn:srcos:memory-binding:scenario-learning:workspace-transduction-001",
"createdAt": "2026-05-29T13:30:00Z",
"sourceScenario": {
"scenarioRef": "adversarial-scenario:workspace-transduction-001",
"scenarioClass": "workspace_transduction",
"sourceRepo": "SocioProphet/SCOPE-D",
"sourcePrRef": "SocioProphet/SCOPE-D#60",
"runtimeDecisionReceiptRefs": ["wargames-runtime-receipt:allow-validate-001"],
"claimPromotionState": "triaged",
"redactionState": "redacted"
},
"agentLearningProposalRef": "urn:srcos:memory-proposal:scenario-workspace-transduction-001",
"memoryEffect": {
"effectType": "learning_candidate",
"targetRef": "memory-target:scope-d-wargames-lessons",
"proposalRequired": true,
"reviewState": "pending_review"
},
"reviewOnlyGate": {
"proposalMode": "review_only",
"reviewRequired": true,
"reviewStatus": "pending",
"abstentionBoundary": "Do not write durable memory from SCOPE-D scenario output until the linked AgentLearningProposal is reviewed and explicitly approved by the later governance flow."
},
"evidenceRefs": [
"evidence:scout-header-summary-001",
"negative-evidence:missing-validator-record-001",
"proof:scout:profile-001"
],
"policyDecisionRefs": [
"policy:wargames-runtime-dry-run-v0.1",
"engagement-authorization:e6-tabletop-001",
"control:scenario-boundary-review"
],
"redaction": {
"rawSensitivePayloadStored": false,
"redactionSummary": "Only scenario, evidence, proof, policy, and receipt references are stored. No raw transcript, credentials, payload bodies, or private telemetry are stored."
},
"writeback": {
"enabled": false,
"performed": false,
"writebackRef": null
}
}
85 changes: 85 additions & 0 deletions schemas/scenario-learning-proposal-binding.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.socioprophet.org/memory-mesh/scenario-learning-proposal-binding.schema.json",
"title": "Memory Mesh Scenario Learning Proposal Binding",
"type": "object",
"required": [
"apiVersion",
"kind",
"bindingId",
"createdAt",
"sourceScenario",
"agentLearningProposalRef",
"memoryEffect",
"reviewOnlyGate",
"evidenceRefs",
"policyDecisionRefs",
"redaction",
"writeback"
],
"properties": {
"apiVersion": { "const": "memory.mesh.scenario-learning-binding/v1" },
"kind": { "const": "ScenarioLearningProposalBinding" },
"bindingId": { "type": "string", "pattern": "^urn:srcos:memory-binding:scenario-learning:[a-z0-9][a-z0-9._:-]*$" },
"createdAt": { "type": "string", "format": "date-time" },
"sourceScenario": {
"type": "object",
"required": ["scenarioRef", "scenarioClass", "sourceRepo", "sourcePrRef", "runtimeDecisionReceiptRefs", "claimPromotionState", "redactionState"],
"properties": {
"scenarioRef": { "type": "string", "pattern": "^adversarial-scenario:[a-z0-9][a-z0-9._:-]*$" },
"scenarioClass": { "type": "string", "minLength": 1 },
"sourceRepo": { "type": "string", "minLength": 1 },
"sourcePrRef": { "type": ["string", "null"] },
"runtimeDecisionReceiptRefs": { "type": "array", "minItems": 1, "items": { "type": "string", "pattern": "^wargames-runtime-receipt:[a-z0-9][a-z0-9._:-]*$" } },
"claimPromotionState": { "enum": ["raw", "hypothesis", "triaged", "blocked"] },
"redactionState": { "enum": ["redacted", "synthetic", "withheld"] }
},
"additionalProperties": false
},
"agentLearningProposalRef": { "type": "string", "minLength": 1 },
"memoryEffect": {
"type": "object",
"required": ["effectType", "targetRef", "proposalRequired", "reviewState"],
"properties": {
"effectType": { "enum": ["learning_candidate", "false_continuity", "stale_policy_resurrection", "entity_merge_error", "identity_split", "malicious_summary", "privilege_hidden_in_context", "poisoned_embedding", "sandbox_to_canon_leak", "invalid_claim_promotion"] },
"targetRef": { "type": "string", "minLength": 1 },
"proposalRequired": { "const": true },
"reviewState": { "enum": ["pending_review", "blocked"] }
},
"additionalProperties": false
},
"reviewOnlyGate": {
"type": "object",
"required": ["proposalMode", "reviewRequired", "reviewStatus", "abstentionBoundary"],
"properties": {
"proposalMode": { "const": "review_only" },
"reviewRequired": { "const": true },
"reviewStatus": { "const": "pending" },
"abstentionBoundary": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
},
"evidenceRefs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
"policyDecisionRefs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
"redaction": {
"type": "object",
"required": ["rawSensitivePayloadStored", "redactionSummary"],
"properties": {
"rawSensitivePayloadStored": { "const": false },
"redactionSummary": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
},
"writeback": {
"type": "object",
"required": ["enabled", "performed", "writebackRef"],
"properties": {
"enabled": { "const": false },
"performed": { "const": false },
"writebackRef": { "type": "null" }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
104 changes: 104 additions & 0 deletions scripts/validate_scenario_learning_proposal_binding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Validate ScenarioLearningProposalBinding examples."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

from jsonschema import Draft202012Validator

ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "schemas" / "scenario-learning-proposal-binding.schema.json"
DEFAULT_EXAMPLE = ROOT / "examples" / "scenario-learning" / "scenario-learning-proposal-binding.example.json"


def load_json(path: Path):
with path.open("r", encoding="utf-8") as handle:
return json.load(handle)


def validate_binding(example: dict, *, source_label: str) -> int:
gate = example["reviewOnlyGate"]
if gate["proposalMode"] != "review_only":
print(f"{source_label}: Scenario learning binding must remain review_only.")
return 1

if gate["reviewRequired"] is not True or gate["reviewStatus"] != "pending":
print(f"{source_label}: Scenario learning binding must remain pending human review.")
return 1

writeback = example["writeback"]
if writeback["enabled"] is not False or writeback["performed"] is not False:
print(f"{source_label}: Scenario learning binding must not enable or perform durable writeback.")
return 1

if writeback["writebackRef"] is not None:
print(f"{source_label}: Scenario learning binding must not carry a writebackRef before approval.")
return 1

if example["redaction"]["rawSensitivePayloadStored"] is not False:
print(f"{source_label}: Scenario learning binding must not store raw sensitive payloads.")
return 1

scenario = example["sourceScenario"]
if scenario["claimPromotionState"] not in {"raw", "hypothesis", "triaged", "blocked"}:
print(f"{source_label}: Scenario claimPromotionState must not be promoted beyond triaged/blocked.")
return 1

if not scenario["runtimeDecisionReceiptRefs"]:
print(f"{source_label}: Scenario learning binding requires runtime decision receipt references.")
return 1

memory_effect = example["memoryEffect"]
if memory_effect["proposalRequired"] is not True:
print(f"{source_label}: Scenario memory effect must require proposal routing.")
return 1

if memory_effect["reviewState"] not in {"pending_review", "blocked"}:
print(f"{source_label}: Scenario memory effect must remain pending_review or blocked.")
return 1

if not example.get("evidenceRefs"):
print(f"{source_label}: Scenario learning binding requires evidenceRefs.")
return 1

if not example.get("policyDecisionRefs"):
print(f"{source_label}: Scenario learning binding requires policyDecisionRefs.")
return 1

return 0


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Validate ScenarioLearningProposalBinding artifact.")
parser.add_argument("--binding", default=str(DEFAULT_EXAMPLE), help="Scenario learning binding JSON file to validate")
return parser


def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
binding_path = Path(args.binding).resolve()
schema = load_json(SCHEMA)
example = load_json(binding_path)
Draft202012Validator.check_schema(schema)
validator = Draft202012Validator(schema)
errors = sorted(validator.iter_errors(example), key=lambda error: list(error.path))
if errors:
print("Scenario learning proposal binding failed validation:")
for error in errors:
location = ".".join(str(part) for part in error.path) or "<root>"
print(f" - {location}: {error.message}")
return 1

result = validate_binding(example, source_label=str(binding_path))
if result != 0:
return result

print(f"Scenario learning proposal binding validates against schema: {binding_path}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading