diff --git a/.github/workflows/prometheus-neurosymbolic-contracts.yml b/.github/workflows/prometheus-neurosymbolic-contracts.yml new file mode 100644 index 00000000..a6f02cde --- /dev/null +++ b/.github/workflows/prometheus-neurosymbolic-contracts.yml @@ -0,0 +1,28 @@ +name: PROMETHEUS neuro-symbolic contracts + +on: + pull_request: + paths: + - "contracts/prometheus/neurosymbolic-*.json" + - "docs/PROMETHEUS_NEUROSYMBOLIC_KERNEL.md" + - "tools/validate_prometheus_neurosymbolic_contracts.py" + - ".github/workflows/prometheus-neurosymbolic-contracts.yml" + push: + branches: + - main + paths: + - "contracts/prometheus/neurosymbolic-*.json" + - "docs/PROMETHEUS_NEUROSYMBOLIC_KERNEL.md" + - "tools/validate_prometheus_neurosymbolic_contracts.py" + - ".github/workflows/prometheus-neurosymbolic-contracts.yml" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Validate PROMETHEUS neuro-symbolic contracts + run: python3 tools/validate_prometheus_neurosymbolic_contracts.py diff --git a/contracts/prometheus/neurosymbolic-capability-catalog.v0.1.json b/contracts/prometheus/neurosymbolic-capability-catalog.v0.1.json new file mode 100644 index 00000000..3a593f6d --- /dev/null +++ b/contracts/prometheus/neurosymbolic-capability-catalog.v0.1.json @@ -0,0 +1,151 @@ +{ + "capabilities": [ + { + "applicationModes": [ + "scientific_law_discovery", + "equation_discovery", + "experiment_design" + ], + "candidateArtifactTypes": [ + "EquationCandidate", + "ProgramCandidate", + "ExperimentProposal" + ], + "capabilityId": "prometheus.nsr.symbolic_scientific_discovery.v0", + "controlAuthority": false, + "displayName": "Symbolic scientific discovery", + "executionPosture": "optional_engine_pending_runtime_pin", + "methodFamily": "ai_descartes", + "nonAuthorityDeclaration": "AI-Descartes-style output is candidate material only and is not a law, policy, controller, ontology assertion, or deployment authorization.", + "priorArtRef": "ibm:ai-descartes", + "prohibitedPromotions": [ + "law", + "ontology_assertion", + "policy", + "controller", + "deployment_authorization" + ], + "prometheusRole": "discovery_engine_candidate_factory", + "requiredGates": [ + "dataset_hash", + "replay_posture", + "dimensional_analysis", + "complexity_gate", + "semantic_review", + "policy_review" + ] + }, + { + "applicationModes": [ + "formula_review", + "contradiction_surface", + "advisory_verification" + ], + "candidateArtifactTypes": [ + "TruthBoundObservation", + "FormulaTrace", + "ContradictionState" + ], + "capabilityId": "prometheus.nsr.truth_bound_reasoning.v0", + "controlAuthority": false, + "displayName": "Logical truth-bound reasoning", + "executionPosture": "optional_engine_pending_runtime_pin", + "methodFamily": "lnn_truth_bounds", + "nonAuthorityDeclaration": "LNN-style truth bounds are advisory observations only and are not hard truth, global entailment, policy admission, or schema authority.", + "priorArtRef": "ibm:fol-lnn", + "prohibitedPromotions": [ + "hard_truth", + "global_entailment", + "policy_admission", + "schema_promotion" + ], + "prometheusRole": "verification_helper", + "requiredGates": [ + "source_evidence_ref", + "truth_region_calibration", + "contradiction_state", + "semantic_review", + "policy_review" + ] + }, + { + "applicationModes": [ + "semantic_parse", + "logic_candidate_generation" + ], + "candidateArtifactTypes": [ + "SemanticParseCandidate", + "LogicCandidate" + ], + "capabilityId": "prometheus.nsr.language_to_logic_grounding.v0", + "controlAuthority": false, + "displayName": "Language-to-logic grounding", + "executionPosture": "optional_engine_pending_runtime_pin", + "methodFamily": "amr_logic", + "nonAuthorityDeclaration": "AMR-to-logic output is a grounding candidate only and does not create schema, policy, ontology, or truth authority.", + "priorArtRef": "ibm:amr-to-logic", + "prohibitedPromotions": [ + "schema", + "policy", + "ontology_assertion", + "hard_truth" + ], + "prometheusRole": "source_to_logic_candidate_generator", + "requiredGates": [ + "source_evidence_ref", + "grounding_assessment", + "anti_leakage_check", + "semantic_review" + ] + }, + { + "applicationModes": [ + "background_theory_lookup", + "knowledge_graph_review" + ], + "candidateArtifactTypes": [ + "KnowledgeSubstrateRef", + "OntologyDeltaProposal" + ], + "capabilityId": "prometheus.nsr.knowledge_substrate.v0", + "controlAuthority": false, + "displayName": "Knowledge substrate reference", + "executionPosture": "read_only_reference_pending_adapter", + "methodFamily": "knowledge_substrate", + "nonAuthorityDeclaration": "Knowledge-substrate output is reference material only and does not mutate ontology, promote schemas, or turn embeddings into evidence.", + "priorArtRef": "ibm:ulkb-ergo", + "prohibitedPromotions": [ + "ontology_mutation", + "schema_promotion", + "evidence_promotion_from_embedding" + ], + "prometheusRole": "evidence_and_background_theory_reference", + "requiredGates": [ + "source_evidence_ref", + "ontology_owner_review", + "semantic_review" + ] + } + ], + "catalogId": "urn:prometheus:neurosymbolic-capability-catalog:v0.1", + "contractType": "PrometheusNeuroSymbolicCapabilityCatalog", + "globalBoundary": { + "controlAuthority": false, + "finalAdmissionAllowed": false, + "memoryPromotionAllowed": false, + "nonAuthorityDeclaration": "PROMETHEUS neuro-symbolic artifacts are not authority artifacts and do not become laws, policies, memory, ontology, schemas, or controllers until admitted by the proper authority planes.", + "ontologyMutationAllowed": false, + "policyMutationAllowed": false + }, + "issuedAt": "2026-06-03T12:00:00Z", + "ownerPlane": "SocioProphet/prophet-platform", + "schemaVersion": "0.1.0", + "sourcePolicy": { + "nonAuthorityDeclaration": "Prior-art sources guide PROMETHEUS capability design but do not authorize runtime execution, law admission, ontology mutation, policy admission, controller use, or memory promotion.", + "priorArtSources": [ + "IBM Neuro-Symbolic AI Toolkit" + ], + "runtimeVendoringApproved": false, + "vendorDependency": false + } +} diff --git a/contracts/prometheus/neurosymbolic-run-artifact.ai-descartes.example.json b/contracts/prometheus/neurosymbolic-run-artifact.ai-descartes.example.json new file mode 100644 index 00000000..abf0ff14 --- /dev/null +++ b/contracts/prometheus/neurosymbolic-run-artifact.ai-descartes.example.json @@ -0,0 +1,40 @@ +{ + "applicationMode": "scientific_law_discovery", + "artifactType": "PrometheusNeuroSymbolicRunArtifact", + "candidateRefs": [ + { + "artifactType": "EquationCandidate", + "candidateId": "urn:prometheus:candidate:equation:ai-descartes-fixture:001", + "complexity": 5, + "equationLatex": "y = 2x + 1", + "fitMetric": { + "name": "nmse", + "value": 0.0 + }, + "promotionState": "candidate", + "unitsStatus": "consistent" + } + ], + "chronosGovernanceFlags": [], + "controlAuthority": false, + "datasetRef": { + "contentHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "hashAlgorithm": "sha256", + "uri": "urn:dataset:prometheus:fixture:linear-law" + }, + "engineMode": "fixture_only", + "finalAdmissionRequested": false, + "issuedAt": "2026-06-03T12:00:00Z", + "methodFamily": "ai_descartes", + "nonAuthorityDeclaration": "This AI-Descartes-style fixture is not a law, ontology assertion, policy, controller, runtime authority, deployment authorization, or admitted SRAssertion.", + "priorArtRef": "ibm:ai-descartes", + "promotionState": "candidate", + "replayHash": { + "algorithm": "sha256", + "state": "fixture_verified", + "value": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "runId": "urn:prometheus:nsr-run:ai-descartes-fixture:001", + "schemaVersion": "0.1.0", + "semanticReviewSurface": "automated_shacl_gate" +} diff --git a/contracts/prometheus/neurosymbolic-run-artifact.fol-lnn.example.json b/contracts/prometheus/neurosymbolic-run-artifact.fol-lnn.example.json new file mode 100644 index 00000000..d8ab9f07 --- /dev/null +++ b/contracts/prometheus/neurosymbolic-run-artifact.fol-lnn.example.json @@ -0,0 +1,38 @@ +{ + "applicationMode": "formula_review", + "artifactType": "PrometheusNeuroSymbolicRunArtifact", + "candidateRefs": [ + { + "artifactType": "TruthBoundObservation", + "candidateId": "urn:prometheus:candidate:truth-bound:fol-lnn-fixture:001", + "contradictionState": "none_observed", + "formula": "forall x: observed(x) -> candidate(x)", + "promotionState": "candidate", + "truthLowerBound": 0.72, + "truthRegionCalibrationRef": "urn:prometheus:truth-region-calibration:fixture:001", + "truthUpperBound": 0.91 + } + ], + "chronosGovernanceFlags": [], + "controlAuthority": false, + "engineMode": "fixture_only", + "finalAdmissionRequested": false, + "issuedAt": "2026-06-03T12:00:00Z", + "methodFamily": "lnn_truth_bounds", + "nonAuthorityDeclaration": "This FOL-LNN-style fixture records advisory truth bounds only; it is not hard truth, global entailment, policy admission, schema authority, or runtime authorization.", + "priorArtRef": "ibm:fol-lnn", + "promotionState": "candidate", + "replayHash": { + "algorithm": "sha256", + "state": "fixture_verified", + "value": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + }, + "runId": "urn:prometheus:nsr-run:fol-lnn-fixture:001", + "schemaVersion": "0.1.0", + "semanticReviewSurface": "cli", + "sourceEvidenceRef": { + "contentHash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "hashAlgorithm": "sha256", + "uri": "urn:evidence:prometheus:fixture:formula-review" + } +} diff --git a/docs/PROMETHEUS_NEUROSYMBOLIC_KERNEL.md b/docs/PROMETHEUS_NEUROSYMBOLIC_KERNEL.md new file mode 100644 index 00000000..a042d6ea --- /dev/null +++ b/docs/PROMETHEUS_NEUROSYMBOLIC_KERNEL.md @@ -0,0 +1,111 @@ +# PROMETHEUS Neuro-Symbolic Kernel + +Status: v0.1 platform contract tranche. + +PROMETHEUS is the owning integration plane for neuro-symbolic discovery in Prophet Platform. IBM's Neuro-Symbolic AI Toolkit is useful prior art and a research index, but it is not a platform dependency, not a vendor runtime, and not an authority plane in this stack. + +The kernel starts with PROMETHEUS candidate emission and ends with PROMETHEUS evidence-bound proposal handling: + +```text +Observation or dataset corpus + -> PROMETHEUS discovery engine + -> candidate artifact + -> PROMETHEUS neuro-symbolic run artifact + -> AgentPlane replay/evidence reference + -> automated gate or human review surface + -> Ontogenesis semantic proposal + -> governance decision +``` + +## Position + +PROMETHEUS may integrate neuro-symbolic methods as discovery engines, candidate factories, verification helpers, semantic grounding helpers, or experimental-design helpers. + +PROMETHEUS must not treat any engine output as a law, ontology assertion, policy, controller, deployment authorization, memory promotion, or canonical schema. Every output remains candidate material until replay, dimensional analysis, semantic validation, and governance review are complete. + +## Initial capability map + +| Capability lane | Prior-art source | PROMETHEUS role | First artifact | +|---|---|---|---| +| symbolic scientific discovery | AI Descartes-style law discovery | primary discovery-engine family | `EquationCandidate` / `ProgramCandidate` | +| logical truth-bound reasoning | LNN-style formula bounds | advisory verification and contradiction surfacing | `TruthBoundObservation` | +| language-to-logic grounding | AMR-to-logic-style semantic parsing | source-to-logic candidate generator | `LogicCandidate` | +| commonsense/knowledge graph substrate | ULKB / ERGO-style substrate | evidence and background-theory lookup | `KnowledgeSubstrateRef` | +| theorem-search helper | TRAIL-style proof search | proof-attempt artifact source | `ProofAttemptCandidate` | +| symbolic action policy | LOA / NESTA-style policy learning | controller-candidate proposal only | `SymbolicPolicyCandidate` | + +## Engine doctrine + +The first implementation tranche is a catalog and contract lane only. It intentionally does not install IBM packages, PySR, Julia, theorem provers, graph stores, AMR parsers, or external model providers. + +The PROMETHEUS engine registry should evolve toward: + +```text +mvp_linear_fallback +optional_pysr +optional_sindy +optional_ai_descartes +optional_lnn_truth_bounds +optional_amr_logic +optional_theorem_search +``` + +Each optional engine must fail closed unless the runtime environment is explicitly pinned, the dataset or corpus is hashed, replay metadata exists, policy allows execution, and the output shape remains backward compatible with the PROMETHEUS candidate contract. + +## Required artifact boundary + +Every PROMETHEUS neuro-symbolic run artifact must carry: + +- `artifactType`; +- `schemaVersion`; +- `runId`; +- `methodFamily`; +- `applicationMode`; +- dataset or corpus evidence reference; +- candidate references; +- replay hash or pending replay state; +- semantic review surface; +- promotion state; +- `controlAuthority: false`; +- non-authority declaration; +- issued timestamp. + +If an artifact lacks evidence reference, replay posture, review surface, non-authority declaration, or `controlAuthority: false`, it is invalid. + +## Gates + +The default gates are: + +1. evidence reference exists; +2. dataset or corpus hash exists where applicable; +3. replay hash exists or replay state is explicitly `pending`; +4. units status is consistent or non-applicable; +5. no governance flags; +6. no final admission requested; +7. semantic review surface is configured; +8. control authority is false; +9. candidate remains `candidate`, `proposed_for_review`, `rejected`, or `failure_corpus`. + +## Non-goals + +This tranche does not vendor the IBM toolkit. + +This tranche does not create live execution. + +This tranche does not mutate Ontogenesis. + +This tranche does not create AgentPlane replay authority. + +This tranche does not promote a discovered equation, rule, policy, ontology edge, or logical formula to truth. + +This tranche does not add a controller path. + +## Validation + +Run: + +```bash +python3 tools/validate_prometheus_neurosymbolic_contracts.py +``` + +The validator checks the PROMETHEUS-owned neuro-symbolic capability catalog and the first AI-Descartes-style / LNN-style fixture run artifacts. diff --git a/tools/prometheus_ai_descartes_mvp.py b/tools/prometheus_ai_descartes_mvp.py new file mode 100644 index 00000000..55bcfbf3 --- /dev/null +++ b/tools/prometheus_ai_descartes_mvp.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +def sha256_file(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as fh: + for chunk in iter(lambda: fh.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def now_utc() -> str: + return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + + +def load_csv(path: Path) -> tuple[list[str], list[dict[str, float]]]: + rows: list[dict[str, float]] = [] + with path.open(newline="", encoding="utf-8") as fh: + reader = csv.DictReader(fh) + if reader.fieldnames is None: + raise ValueError("CSV requires header") + fields = list(reader.fieldnames) + for row in reader: + rows.append({k: float(v) for k, v in row.items() if k is not None and v not in (None, "")}) + if not rows: + raise ValueError("CSV requires at least one row") + return fields, rows + + +def fit_linear_one_feature(rows: list[dict[str, float]], x_name: str, y_name: str) -> tuple[float, float, float]: + xs = [row[x_name] for row in rows] + ys = [row[y_name] for row in rows] + x_mean = sum(xs) / len(xs) + y_mean = sum(ys) / len(ys) + denom = sum((x - x_mean) ** 2 for x in xs) + if denom == 0: + raise ValueError("feature has zero variance") + slope = sum((x - x_mean) * (y - y_mean) for x, y in zip(xs, ys)) / denom + intercept = y_mean - slope * x_mean + preds = [slope * x + intercept for x in xs] + mse = sum((y - p) ** 2 for y, p in zip(ys, preds)) / len(ys) + denom_y = sum((y - y_mean) ** 2 for y in ys) / len(ys) + nmse = mse / denom_y if denom_y else 0.0 + return slope, intercept, nmse + + +def equation_text(target: str, feature: str, slope: float, intercept: float) -> tuple[str, int]: + if abs(intercept) < 1e-12: + return f"{target} = {slope:.12g} {feature}", 3 + sign = "+" if intercept >= 0 else "-" + return f"{target} = {slope:.12g} {feature} {sign} {abs(intercept):.12g}", 5 + + +def units_status(target_unit: str | None, feature_units: dict[str, str], feature: str) -> str: + if not target_unit or feature not in feature_units: + return "unknown" + try: + import sympy as sp + from sympy.physics import units as u + except Exception: + return "unchecked" + namespace = {name: getattr(u, name) for name in dir(u) if not name.startswith("_")} + try: + target = sp.sympify(target_unit, locals=namespace) + source = sp.sympify(feature_units[feature], locals=namespace) + except Exception: + return "unknown" + return "consistent" if sp.simplify(target / source).is_number else "inconsistent" + + +def build_artifact(args: argparse.Namespace) -> dict[str, Any]: + if args.engine == "optional_ai_descartes" and not args.allow_fixture: + raise RuntimeError( + "optional_ai_descartes requested before runtime pinning is available; " + "use --allow-fixture to emit the deterministic PROMETHEUS-owned fixture" + ) + + data_path = Path(args.data) + fields, rows = load_csv(data_path) + if args.target not in fields: + raise ValueError(f"target not found: {args.target}") + features = [f for f in fields if f != args.target] + if len(features) != 1: + raise ValueError("AI-Descartes MVP fixture supports exactly one feature plus target") + feature = features[0] + + slope, intercept, nmse = fit_linear_one_feature(rows, feature, args.target) + latex, complexity = equation_text(args.target, feature, slope, intercept) + feature_units: dict[str, str] = {} + if args.feature_unit: + for pair in args.feature_unit: + name, unit = pair.split("=", 1) + feature_units[name] = unit + u_status = units_status(args.target_unit, feature_units, feature) + data_hash = sha256_file(data_path) + implementation_mode = "fixture_ai_descartes" if args.engine == "fixture_ai_descartes" else "optional_ai_descartes_fixture_fallback" + + return { + "artifactType": "EquationCandidate", + "applicationMode": "scientific_law_discovery", + "candidateId": f"urn:prometheus:equation-candidate:ai-descartes:{data_hash[:16]}", + "methodFamily": "ai_descartes", + "implementationMode": implementation_mode, + "engineMode": args.engine, + "vendorRuntimeUsed": False, + "priorArtRef": "ibm:ai-descartes", + "datasetRef": { + "uri": args.dataset_uri, + "contentHash": data_hash, + "hashAlgorithm": "sha256", + }, + "target": args.target, + "features": features, + "equationLatex": latex, + "fitMetric": {"name": "nmse", "value": nmse}, + "complexity": complexity, + "unitsStatus": u_status, + "backgroundTheoryRefs": [ + { + "uri": args.background_theory_uri, + "status": "fixture_only", + "nonAuthorityDeclaration": "Background theory is advisory fixture context only and does not authorize law admission.", + } + ], + "experimentDesignPosture": "not_requested", + "promotionState": "candidate" if u_status != "inconsistent" else "rejected", + "nonAuthorityDeclaration": "This AI-Descartes-style EquationCandidate is not a law, ontology assertion, policy, controller, runtime authority, deployment authorization, or admitted SRAssertion.", + "issuedAt": args.generated_at or now_utc(), + } + + +def main() -> int: + parser = argparse.ArgumentParser(description="PROMETHEUS AI-Descartes-style MVP candidate emitter") + parser.add_argument("--data", required=True) + parser.add_argument("--target", required=True) + parser.add_argument("--dataset-uri", required=True) + parser.add_argument("--target-unit") + parser.add_argument("--feature-unit", action="append", default=[]) + parser.add_argument("--background-theory-uri", default="urn:prometheus:background-theory:fixture:linear-proportionality") + parser.add_argument("--generated-at") + parser.add_argument("--output", required=True) + parser.add_argument("--engine", choices=["fixture_ai_descartes", "optional_ai_descartes"], default="fixture_ai_descartes") + parser.add_argument("--allow-fixture", action="store_true") + args = parser.parse_args() + artifact = build_artifact(args) + out = Path(args.output) + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps(artifact, indent=2, sort_keys=True) + "\n", encoding="utf-8") + print(json.dumps({"ok": True, "output": str(out), "unitsStatus": artifact["unitsStatus"], "implementationMode": artifact["implementationMode"]}, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/run_prometheus_local_demo.py b/tools/run_prometheus_local_demo.py index 4a96ff2c..3e63fca5 100644 --- a/tools/run_prometheus_local_demo.py +++ b/tools/run_prometheus_local_demo.py @@ -11,6 +11,10 @@ from typing import Any +PROMETHEUS_LINEAR_FIXTURE = "tests/fixtures/prometheus/pysr-mvp-linear.csv" +SINDY_LINEAR_FIXTURE = "tests/fixtures/prometheus/sindy-fast-path-linear.csv" + + def now_utc() -> str: return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") @@ -33,7 +37,7 @@ def artifact_record(kind: str, path: Path) -> dict[str, Any]: def build_manifest(output_dir: Path, issued_at: str, artifacts: list[dict[str, Any]]) -> dict[str, Any]: return { - "manifestVersion": "0.2.0", + "manifestVersion": "0.3.0", "kind": "PrometheusLocalDemoManifest", "issuedAt": issued_at, "nonAuthorityDeclaration": "PROMETHEUS local demo artifacts are evidence only. They are not laws, ontology assertions, policies, controllers, or deployment authorizations.", @@ -49,6 +53,15 @@ def build_manifest(output_dir: Path, issued_at: str, artifacts: list[dict[str, A "jsonldArtifact": str(output_dir / "pysr" / "sr.jsonld"), "controlAuthority": False, }, + { + "applicationMode": "scientific_law_discovery", + "methodFamily": "ai_descartes", + "candidateArtifact": str(output_dir / "ai-descartes" / "equation-candidate.json"), + "runArtifact": str(output_dir / "ai-descartes" / "sr-run-artifact.json"), + "gateEvaluationArtifact": str(output_dir / "ai-descartes" / "gate-evaluation.json"), + "jsonldArtifact": str(output_dir / "ai-descartes" / "sr.jsonld"), + "controlAuthority": False, + }, { "applicationMode": "platform_dynamics", "methodFamily": "sindy", @@ -60,95 +73,156 @@ def build_manifest(output_dir: Path, issued_at: str, artifacts: list[dict[str, A } -def main() -> int: - parser = argparse.ArgumentParser(description="Run consolidated PROMETHEUS local demo") - parser.add_argument("--output-dir", default="build/prometheus/local-demo") - parser.add_argument("--issued-at", default="2026-05-27T21:00:00Z") - args = parser.parse_args() - - output_dir = Path(args.output_dir) +def emit_pysr_lane(output_dir: Path, issued_at: str) -> list[dict[str, Any]]: pysr_dir = output_dir / "pysr" - sindy_dir = output_dir / "sindy" pysr_dir.mkdir(parents=True, exist_ok=True) - sindy_dir.mkdir(parents=True, exist_ok=True) - - pysr_candidate = pysr_dir / "equation-candidate.json" - pysr_run = pysr_dir / "sr-run-artifact.json" - pysr_gate = pysr_dir / "gate-evaluation.json" - pysr_jsonld = pysr_dir / "sr.jsonld" - sindy_candidate = sindy_dir / "platform-dynamics-candidate.json" - sindy_run = sindy_dir / "sr-run-artifact.json" + candidate = pysr_dir / "equation-candidate.json" + run = pysr_dir / "sr-run-artifact.json" + gate = pysr_dir / "gate-evaluation.json" + jsonld = pysr_dir / "sr.jsonld" run_command([ sys.executable, "tools/prometheus_pysr_mvp.py", "--engine", "mvp_linear_fallback", - "--data", "tests/fixtures/prometheus/pysr-mvp-linear.csv", + "--data", PROMETHEUS_LINEAR_FIXTURE, "--target", "y", "--dataset-uri", "urn:dataset:prometheus:pysr-mvp-linear", "--target-unit", "meter", "--feature-unit", "x=meter", - "--generated-at", args.issued_at, - "--output", str(pysr_candidate), + "--generated-at", issued_at, + "--output", str(candidate), ]) - run_command([ sys.executable, "tools/prometheus_emit_sr_run_artifact.py", - "--candidate", str(pysr_candidate), + "--candidate", str(candidate), "--run-id", "urn:prometheus:sr-run:pysr-local-demo:001", "--chronos-carrier-id", "urn:chronos:carrier:prometheus:local-demo:pysr", "--random-seed", "42", - "--issued-at", args.issued_at, - "--output", str(pysr_run), + "--issued-at", issued_at, + "--output", str(run), ]) - run_command([ sys.executable, "tools/emit_prometheus_gate_evaluation.py", - "--candidate", str(pysr_candidate), - "--run-artifact", str(pysr_run), - "--dataset", "tests/fixtures/prometheus/pysr-mvp-linear.csv", + "--candidate", str(candidate), + "--run-artifact", str(run), + "--dataset", PROMETHEUS_LINEAR_FIXTURE, "--evaluation-id", "urn:prometheus:gate-evaluation:pysr-local-demo:001", - "--issued-at", args.issued_at, - "--output", str(pysr_gate), + "--issued-at", issued_at, + "--output", str(gate), ]) - run_command([ sys.executable, "tools/emit_prometheus_jsonld_review.py", - "--candidate", str(pysr_candidate), - "--run-artifact", str(pysr_run), - "--gate-evaluation", str(pysr_gate), + "--candidate", str(candidate), + "--run-artifact", str(run), + "--gate-evaluation", str(gate), "--review-id", "urn:prometheus:jsonld:pysr-local-demo:001", "--review-surface", "automated_shacl_gate", - "--issued-at", args.issued_at, - "--output", str(pysr_jsonld), + "--issued-at", issued_at, + "--output", str(jsonld), + ]) + return [ + artifact_record("EquationCandidate", candidate), + artifact_record("SRRunArtifact", run), + artifact_record("AutomatedGateEvaluation", gate), + artifact_record("SRAssertionProposalJSONLD", jsonld), + ] + + +def emit_ai_descartes_lane(output_dir: Path, issued_at: str) -> list[dict[str, Any]]: + ai_dir = output_dir / "ai-descartes" + ai_dir.mkdir(parents=True, exist_ok=True) + candidate = ai_dir / "equation-candidate.json" + run = ai_dir / "sr-run-artifact.json" + gate = ai_dir / "gate-evaluation.json" + jsonld = ai_dir / "sr.jsonld" + + run_command([ + sys.executable, "tools/prometheus_ai_descartes_mvp.py", + "--engine", "fixture_ai_descartes", + "--data", PROMETHEUS_LINEAR_FIXTURE, + "--target", "y", + "--dataset-uri", "urn:dataset:prometheus:ai-descartes-fixture-linear", + "--target-unit", "meter", + "--feature-unit", "x=meter", + "--generated-at", issued_at, + "--output", str(candidate), + ]) + run_command([ + sys.executable, "tools/prometheus_emit_sr_run_artifact.py", + "--candidate", str(candidate), + "--run-id", "urn:prometheus:sr-run:ai-descartes-local-demo:001", + "--chronos-carrier-id", "urn:chronos:carrier:prometheus:local-demo:ai-descartes", + "--issued-at", issued_at, + "--output", str(run), + ]) + run_command([ + sys.executable, "tools/emit_prometheus_gate_evaluation.py", + "--candidate", str(candidate), + "--run-artifact", str(run), + "--dataset", PROMETHEUS_LINEAR_FIXTURE, + "--evaluation-id", "urn:prometheus:gate-evaluation:ai-descartes-local-demo:001", + "--issued-at", issued_at, + "--output", str(gate), + ]) + run_command([ + sys.executable, "tools/emit_prometheus_jsonld_review.py", + "--candidate", str(candidate), + "--run-artifact", str(run), + "--gate-evaluation", str(gate), + "--review-id", "urn:prometheus:jsonld:ai-descartes-local-demo:001", + "--review-surface", "automated_shacl_gate", + "--issued-at", issued_at, + "--output", str(jsonld), ]) + return [ + artifact_record("EquationCandidate", candidate), + artifact_record("SRRunArtifact", run), + artifact_record("AutomatedGateEvaluation", gate), + artifact_record("SRAssertionProposalJSONLD", jsonld), + ] + + +def emit_sindy_lane(output_dir: Path, issued_at: str) -> list[dict[str, Any]]: + sindy_dir = output_dir / "sindy" + sindy_dir.mkdir(parents=True, exist_ok=True) + candidate = sindy_dir / "platform-dynamics-candidate.json" + run = sindy_dir / "sr-run-artifact.json" run_command([ sys.executable, "tools/prometheus_sindy_fast_path.py", - "--data", "tests/fixtures/prometheus/sindy-fast-path-linear.csv", + "--data", SINDY_LINEAR_FIXTURE, "--time-column", "t", "--value-column", "q", "--dataset-uri", "urn:dataset:prometheus:sindy-fast-path-linear", - "--generated-at", args.issued_at, - "--output", str(sindy_candidate), + "--generated-at", issued_at, + "--output", str(candidate), ]) - run_command([ sys.executable, "tools/prometheus_emit_sr_run_artifact.py", - "--candidate", str(sindy_candidate), + "--candidate", str(candidate), "--run-id", "urn:prometheus:sr-run:sindy-local-demo:001", "--chronos-carrier-id", "urn:chronos:carrier:prometheus:local-demo:sindy", - "--issued-at", args.issued_at, - "--output", str(sindy_run), + "--issued-at", issued_at, + "--output", str(run), ]) - - artifacts = [ - artifact_record("EquationCandidate", pysr_candidate), - artifact_record("SRRunArtifact", pysr_run), - artifact_record("AutomatedGateEvaluation", pysr_gate), - artifact_record("SRAssertionProposalJSONLD", pysr_jsonld), - artifact_record("PlatformDynamicsCandidate", sindy_candidate), - artifact_record("SRRunArtifact", sindy_run), + return [ + artifact_record("PlatformDynamicsCandidate", candidate), + artifact_record("SRRunArtifact", run), ] + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run consolidated PROMETHEUS local demo") + parser.add_argument("--output-dir", default="build/prometheus/local-demo") + parser.add_argument("--issued-at", default="2026-05-27T21:00:00Z") + args = parser.parse_args() + + output_dir = Path(args.output_dir) + artifacts: list[dict[str, Any]] = [] + artifacts.extend(emit_pysr_lane(output_dir, args.issued_at)) + artifacts.extend(emit_ai_descartes_lane(output_dir, args.issued_at)) + artifacts.extend(emit_sindy_lane(output_dir, args.issued_at)) + manifest = build_manifest(output_dir, args.issued_at, artifacts) manifest_path = output_dir / "manifest.json" manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") diff --git a/tools/validate_prometheus_neurosymbolic_contracts.py b/tools/validate_prometheus_neurosymbolic_contracts.py new file mode 100644 index 00000000..488a5eb2 --- /dev/null +++ b/tools/validate_prometheus_neurosymbolic_contracts.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + +CATALOG = Path("contracts/prometheus/neurosymbolic-capability-catalog.v0.1.json") +ARTIFACTS = [ + Path("contracts/prometheus/neurosymbolic-run-artifact.ai-descartes.example.json"), + Path("contracts/prometheus/neurosymbolic-run-artifact.fol-lnn.example.json"), +] + +ALLOWED_PROMOTIONS = {"candidate", "proposed_for_review", "rejected", "failure_corpus"} +ALLOWED_REVIEW_SURFACES = {"automated_shacl_gate", "git_pr", "prophet_platform_ui", "cli", "sparql_editor", "webprotege"} +KNOWN_METHODS = {"ai_descartes", "lnn_truth_bounds", "amr_logic", "knowledge_substrate", "theorem_search", "symbolic_policy"} + + +def fail(message: str) -> None: + raise SystemExit(message) + + +def load_json(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + fail(f"{path}: expected JSON object") + return data + + +def require_hash(value: Any, label: str) -> None: + if not isinstance(value, str) or len(value) != 64 or any(ch not in "0123456789abcdef" for ch in value.lower()): + fail(f"{label}: expected 64-character sha256 hex string") + + +def require_non_authority(text: Any, path: Path) -> None: + if not isinstance(text, str): + fail(f"{path}: nonAuthorityDeclaration must be string") + lowered = text.lower() + if "not " not in lowered and "does not" not in lowered and "do not" not in lowered: + fail(f"{path}: nonAuthorityDeclaration must explicitly contain non-authority language") + + +def validate_catalog(path: Path) -> set[str]: + catalog = load_json(path) + if catalog.get("contractType") != "PrometheusNeuroSymbolicCapabilityCatalog": + fail(f"{path}: contractType mismatch") + if catalog.get("schemaVersion") != "0.1.0": + fail(f"{path}: schemaVersion must be 0.1.0") + source = catalog.get("sourcePolicy") + if not isinstance(source, dict): + fail(f"{path}: sourcePolicy must be object") + if source.get("vendorDependency") is not False: + fail(f"{path}: vendorDependency must be false") + if source.get("runtimeVendoringApproved") is not False: + fail(f"{path}: runtimeVendoringApproved must be false") + require_non_authority(source.get("nonAuthorityDeclaration"), path) + + capabilities = catalog.get("capabilities") + if not isinstance(capabilities, list) or len(capabilities) < 4: + fail(f"{path}: expected at least four capabilities") + + method_families: set[str] = set() + seen: set[str] = set() + for item in capabilities: + if not isinstance(item, dict): + fail(f"{path}: capability must be object") + cap_id = item.get("capabilityId") + if not isinstance(cap_id, str) or not cap_id.startswith("prometheus.nsr."): + fail(f"{path}: invalid capabilityId") + if cap_id in seen: + fail(f"{path}: duplicate capabilityId {cap_id}") + seen.add(cap_id) + method = item.get("methodFamily") + if method not in KNOWN_METHODS: + fail(f"{path}: unknown methodFamily {method}") + method_families.add(method) + if item.get("controlAuthority") is not False: + fail(f"{path}: capability {cap_id} must set controlAuthority false") + if item.get("executionPosture") not in {"optional_engine_pending_runtime_pin", "read_only_reference_pending_adapter"}: + fail(f"{path}: invalid executionPosture for {cap_id}") + for field in ("applicationModes", "candidateArtifactTypes", "requiredGates", "prohibitedPromotions"): + if not isinstance(item.get(field), list) or not item[field]: + fail(f"{path}: capability {cap_id} missing non-empty {field}") + require_non_authority(item.get("nonAuthorityDeclaration"), path) + + boundary = catalog.get("globalBoundary") + if not isinstance(boundary, dict): + fail(f"{path}: globalBoundary must be object") + for flag in ("controlAuthority", "finalAdmissionAllowed", "memoryPromotionAllowed", "ontologyMutationAllowed", "policyMutationAllowed"): + if boundary.get(flag) is not False: + fail(f"{path}: globalBoundary {flag} must be false") + require_non_authority(boundary.get("nonAuthorityDeclaration"), path) + return method_families + + +def evidence_ref(artifact: dict[str, Any], path: Path) -> dict[str, Any]: + dataset = artifact.get("datasetRef") + source = artifact.get("sourceEvidenceRef") + ref = dataset if isinstance(dataset, dict) else source + if not isinstance(ref, dict): + fail(f"{path}: expected datasetRef or sourceEvidenceRef") + require_hash(ref.get("contentHash"), f"{path}: evidence contentHash") + if ref.get("hashAlgorithm") != "sha256": + fail(f"{path}: evidence hashAlgorithm must be sha256") + return ref + + +def validate_candidate(candidate: dict[str, Any], path: Path, method: str) -> None: + if not isinstance(candidate.get("candidateId"), str) or not candidate["candidateId"].startswith("urn:prometheus:candidate:"): + fail(f"{path}: invalid candidateId") + if not isinstance(candidate.get("artifactType"), str): + fail(f"{path}: candidate artifactType required") + if candidate.get("promotionState") not in ALLOWED_PROMOTIONS: + fail(f"{path}: invalid candidate promotionState") + + if method == "ai_descartes": + if candidate.get("artifactType") not in {"EquationCandidate", "ProgramCandidate", "ExperimentProposal"}: + fail(f"{path}: ai_descartes candidate type mismatch") + if candidate.get("unitsStatus") != "consistent": + fail(f"{path}: ai_descartes fixture must use consistent units") + fit = candidate.get("fitMetric") + if not isinstance(fit, dict) or fit.get("name") != "nmse": + fail(f"{path}: ai_descartes candidate requires nmse fit metric") + if not isinstance(candidate.get("complexity"), int) or candidate["complexity"] <= 0: + fail(f"{path}: ai_descartes candidate complexity must be positive integer") + + if method == "lnn_truth_bounds": + if candidate.get("artifactType") != "TruthBoundObservation": + fail(f"{path}: lnn_truth_bounds candidate type mismatch") + lower = candidate.get("truthLowerBound") + upper = candidate.get("truthUpperBound") + if not isinstance(lower, (int, float)) or not isinstance(upper, (int, float)) or not (0 <= lower <= upper <= 1): + fail(f"{path}: invalid truth bound interval") + if not candidate.get("truthRegionCalibrationRef"): + fail(f"{path}: truthRegionCalibrationRef required") + + +def validate_artifact(path: Path, catalog_methods: set[str]) -> None: + artifact = load_json(path) + if artifact.get("artifactType") != "PrometheusNeuroSymbolicRunArtifact": + fail(f"{path}: artifactType mismatch") + if artifact.get("schemaVersion") != "0.1.0": + fail(f"{path}: schemaVersion must be 0.1.0") + method = artifact.get("methodFamily") + if method not in catalog_methods: + fail(f"{path}: methodFamily {method} is not declared in catalog") + if artifact.get("engineMode") != "fixture_only": + fail(f"{path}: first tranche must be fixture_only") + evidence_ref(artifact, path) + + replay = artifact.get("replayHash") + if not isinstance(replay, dict): + fail(f"{path}: replayHash must be object") + if replay.get("algorithm") != "sha256": + fail(f"{path}: replayHash algorithm must be sha256") + require_hash(replay.get("value"), f"{path}: replayHash") + if replay.get("state") not in {"fixture_verified", "pending"}: + fail(f"{path}: replayHash state invalid") + + if artifact.get("semanticReviewSurface") not in ALLOWED_REVIEW_SURFACES: + fail(f"{path}: invalid semanticReviewSurface") + if artifact.get("controlAuthority") is not False: + fail(f"{path}: controlAuthority must be false") + if artifact.get("finalAdmissionRequested") is not False: + fail(f"{path}: finalAdmissionRequested must be false") + if artifact.get("chronosGovernanceFlags") != []: + fail(f"{path}: fixture must have no CHRONOS governance flags") + if artifact.get("promotionState") not in ALLOWED_PROMOTIONS: + fail(f"{path}: invalid artifact promotionState") + require_non_authority(artifact.get("nonAuthorityDeclaration"), path) + + candidates = artifact.get("candidateRefs") + if not isinstance(candidates, list) or not candidates: + fail(f"{path}: candidateRefs must be non-empty") + for candidate in candidates: + if not isinstance(candidate, dict): + fail(f"{path}: candidate must be object") + validate_candidate(candidate, path, method) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate PROMETHEUS neuro-symbolic catalog and fixture artifacts") + parser.add_argument("--catalog", default=str(CATALOG)) + parser.add_argument("--artifact", action="append", default=[str(p) for p in ARTIFACTS]) + args = parser.parse_args() + + catalog_methods = validate_catalog(Path(args.catalog)) + for artifact in args.artifact: + validate_artifact(Path(artifact), catalog_methods) + + print(json.dumps({"valid": True, "catalog": args.catalog, "artifactCount": len(args.artifact)}, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())