Verification Result
-
- Status:{" "}
-
- {String(result.status ?? "unknown")}
-
- {result.overall_outcome != null && (
-
- Overall:{" "}
-
- {result.overall_outcome}
-
+ Provability Fabric VerificationResult.v0
+
+
+
+
+ status:{" "}
+
+ {String(result.status ?? "unknown")}
- )}
-
-
+ {result.overall_outcome != null && (
+
+ overall:{" "}
+
+ {result.overall_outcome}
+
+
+ )}
+
+
+
+
+
+
+
+
+ checks
+
diff --git a/portal/lib/pcsTypes.ts b/portal/lib/pcsTypes.ts
index 0a3de11..3fc8561 100644
--- a/portal/lib/pcsTypes.ts
+++ b/portal/lib/pcsTypes.ts
@@ -34,11 +34,15 @@ export type PcsVerificationCheck = {
name: string;
outcome: string;
detail?: string;
- guarantee_type?: string;
+ guarantee_type?: string | null;
};
export type PcsVerificationResult = {
+ verification_id?: string;
+ id?: string;
status?: string;
+ verifier?: string;
+ verifier_version?: string;
overall_outcome?: string;
signature_or_digest?: string;
source_repo?: string;
@@ -53,6 +57,14 @@ export type PcsHashRow = {
source_artifact?: string;
};
+export type PcsCanonicalDigests = {
+ claim_artifact: string;
+ runtime_receipt: string;
+ trace_certificate: string;
+ evidence_bundle: string;
+ signed_bundle: string;
+};
+
export type PcsClaimReadModel = {
schema_version: string;
claim_id: string;
@@ -60,8 +72,10 @@ export type PcsClaimReadModel = {
assumption_set: PcsNamedArtifact & { assumptions?: PcsAssumption[] };
runtime_receipt: PcsNamedArtifact;
trace_certificate: PcsNamedArtifact;
+ evidence_bundle?: PcsNamedArtifact;
verification_result?: PcsVerificationResult | null;
artifact_hashes: PcsHashRow[];
+ canonical_digests?: PcsCanonicalDigests;
source_repositories: { source_repo: string; source_commit: string }[];
reproduce_commands: string[];
verify_commands: string[];
diff --git a/portal/scripts/generate-search-index.mjs b/portal/scripts/generate-search-index.mjs
index ab8009d..185014d 100644
--- a/portal/scripts/generate-search-index.mjs
+++ b/portal/scripts/generate-search-index.mjs
@@ -38,7 +38,24 @@ for (const p of papers) {
}
}
-const searchIndex = { papers, claims };
+const pcsClaims = [];
+const pcsRoot = path.join(CORPUS, "pcs", "claims");
+if (fs.existsSync(pcsRoot)) {
+ for (const claimId of fs.readdirSync(pcsRoot)) {
+ const readModelPath = path.join(pcsRoot, claimId, "read_model.json");
+ if (!fs.existsSync(readModelPath)) continue;
+ const model = readJson(readModelPath, null);
+ if (!model?.claim_id) continue;
+ const text = String(model.claim?.text ?? "").slice(0, SNIPPET_LENGTH);
+ pcsClaims.push({
+ id: String(model.claim_id),
+ informal_text: text,
+ href: `/pcs/claims/${model.claim_id}`,
+ });
+ }
+}
+
+const searchIndex = { papers, claims, pcs_claims: pcsClaims };
const outDir = path.join(PORTAL_ROOT, "public");
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
fs.writeFileSync(
diff --git a/pyproject.toml b/pyproject.toml
index 18bdc61..c2866b6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,3 +13,4 @@ target-version = "py311"
[tool.pytest.ini_options]
testpaths = ["pipeline/tests", "kernels/adsorption/tests", "tests/pcs"]
+pythonpath = ["."]
diff --git a/schemas/pcs/AssumptionSet.v0.schema.json b/schemas/pcs/AssumptionSet.v0.schema.json
new file mode 100644
index 0000000..25cd295
--- /dev/null
+++ b/schemas/pcs/AssumptionSet.v0.schema.json
@@ -0,0 +1,53 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/AssumptionSet.v0.schema.json",
+ "title": "AssumptionSet.v0",
+ "type": "object",
+ "required": [
+ "assumption_set_id",
+ "schema_version",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "assumptions",
+ "human_review_status",
+ "status",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "assumption_set_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "created_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "assumptions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["assumption_id", "text", "kind", "status", "source_span_refs"],
+ "additionalProperties": false,
+ "properties": {
+ "assumption_id": { "type": "string", "minLength": 1 },
+ "text": { "type": "string", "minLength": 1 },
+ "kind": {
+ "type": "string",
+ "enum": ["domain", "operational", "formal", "empirical", "security", "policy"]
+ },
+ "status": { "$ref": "common.defs.json#/$defs/artifact_status" },
+ "source_span_refs": { "$ref": "common.defs.json#/$defs/ref_list" }
+ }
+ }
+ },
+ "human_review_status": {
+ "type": "string",
+ "enum": ["pending", "approved", "rejected", "not_required"]
+ },
+ "status": { "$ref": "common.defs.json#/$defs/artifact_status" },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/ClaimArtifact.v0.schema.json b/schemas/pcs/ClaimArtifact.v0.schema.json
new file mode 100644
index 0000000..360cd59
--- /dev/null
+++ b/schemas/pcs/ClaimArtifact.v0.schema.json
@@ -0,0 +1,54 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/ClaimArtifact.v0.schema.json",
+ "title": "ClaimArtifact.v0",
+ "type": "object",
+ "required": [
+ "artifact_id",
+ "artifact_type",
+ "schema_version",
+ "claim_text",
+ "claim_kind",
+ "status",
+ "assumption_set_ref",
+ "source_span_refs",
+ "formal_statement",
+ "certificate_refs",
+ "runtime_receipt_refs",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "artifact_id": { "type": "string", "minLength": 1 },
+ "artifact_type": { "const": "ClaimArtifact.v0" },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "claim_text": { "type": "string", "minLength": 1 },
+ "claim_kind": {
+ "type": "string",
+ "enum": [
+ "scientific_claim",
+ "protocol_safety_claim",
+ "runtime_safety_claim",
+ "temporal_claim",
+ "policy_claim"
+ ]
+ },
+ "status": { "$ref": "common.defs.json#/$defs/artifact_status" },
+ "assumption_set_ref": { "type": "string", "minLength": 1 },
+ "source_span_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "formal_statement": { "type": "string" },
+ "certificate_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "runtime_receipt_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "created_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/EvidenceBundle.v0.schema.json b/schemas/pcs/EvidenceBundle.v0.schema.json
new file mode 100644
index 0000000..93b707d
--- /dev/null
+++ b/schemas/pcs/EvidenceBundle.v0.schema.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/EvidenceBundle.v0.schema.json",
+ "title": "EvidenceBundle.v0",
+ "type": "object",
+ "required": [
+ "bundle_id",
+ "schema_version",
+ "claim_refs",
+ "assumption_set_refs",
+ "runtime_receipt_refs",
+ "certificate_refs",
+ "artifact_hashes",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "bundle_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "claim_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "assumption_set_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "runtime_receipt_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "certificate_refs": { "$ref": "common.defs.json#/$defs/ref_list" },
+ "artifact_hashes": { "$ref": "common.defs.json#/$defs/hash_map" },
+ "created_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/RuntimeReceipt.v0.schema.json b/schemas/pcs/RuntimeReceipt.v0.schema.json
new file mode 100644
index 0000000..98a034c
--- /dev/null
+++ b/schemas/pcs/RuntimeReceipt.v0.schema.json
@@ -0,0 +1,55 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/RuntimeReceipt.v0.schema.json",
+ "title": "RuntimeReceipt.v0",
+ "type": "object",
+ "required": [
+ "receipt_id",
+ "schema_version",
+ "run_id",
+ "environment",
+ "started_at",
+ "ended_at",
+ "status",
+ "run_outcome",
+ "final_reason_code",
+ "released",
+ "events_hash",
+ "policy_hash",
+ "trace_hash",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "input_hashes",
+ "output_hashes",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "receipt_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "run_id": { "type": "string", "minLength": 1 },
+ "environment": {
+ "type": "object",
+ "additionalProperties": { "type": "string" }
+ },
+ "started_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "ended_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "status": { "$ref": "common.defs.json#/$defs/artifact_status" },
+ "run_outcome": { "type": "string", "enum": ["passed", "failed"] },
+ "final_reason_code": { "type": "string", "minLength": 1 },
+ "released": { "type": "boolean" },
+ "events_hash": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "policy_hash": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "trace_hash": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "local_dev": { "type": "boolean" },
+ "input_hashes": { "$ref": "common.defs.json#/$defs/hash_map" },
+ "output_hashes": { "$ref": "common.defs.json#/$defs/hash_map" },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/SCHEMA_MIRROR.json b/schemas/pcs/SCHEMA_MIRROR.json
new file mode 100644
index 0000000..fdf59a9
--- /dev/null
+++ b/schemas/pcs/SCHEMA_MIRROR.json
@@ -0,0 +1,21 @@
+{
+ "source": "C:\\Users\\mateo\\pcs-core\\schemas",
+ "canonical_schemas": [
+ "SignedScienceClaimBundle.v0.schema.json",
+ "ScienceClaimBundle.v0.schema.json",
+ "VerificationResult.v0.schema.json",
+ "ClaimArtifact.v0.schema.json",
+ "AssumptionSet.v0.schema.json",
+ "RuntimeReceipt.v0.schema.json",
+ "TraceCertificate.v0.schema.json",
+ "EvidenceBundle.v0.schema.json",
+ "SourceSpan.v0.schema.json",
+ "common.defs.json"
+ ],
+ "legacy_aliases": {
+ "signed_science_claim_bundle.schema.json": "SignedScienceClaimBundle.v0.schema.json",
+ "science_claim_bundle.schema.json": "ScienceClaimBundle.v0.schema.json",
+ "verification_result.schema.json": "VerificationResult.v0.schema.json"
+ },
+ "note": "Scientific Memory mirrors pcs-core; pcs-core remains canonical."
+}
diff --git a/schemas/pcs/ScienceClaimBundle.v0.schema.json b/schemas/pcs/ScienceClaimBundle.v0.schema.json
new file mode 100644
index 0000000..e220949
--- /dev/null
+++ b/schemas/pcs/ScienceClaimBundle.v0.schema.json
@@ -0,0 +1,57 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/ScienceClaimBundle.v0.schema.json",
+ "title": "ScienceClaimBundle.v0",
+ "type": "object",
+ "required": [
+ "bundle_id",
+ "schema_version",
+ "claim_artifact",
+ "assumption_set",
+ "runtime_receipts",
+ "certificates",
+ "evidence_bundle",
+ "verification_policy",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "bundle_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "claim_artifact": { "$ref": "ClaimArtifact.v0.schema.json" },
+ "assumption_set": { "$ref": "AssumptionSet.v0.schema.json" },
+ "runtime_receipts": {
+ "type": "array",
+ "items": { "$ref": "RuntimeReceipt.v0.schema.json" },
+ "minItems": 1
+ },
+ "certificates": {
+ "type": "array",
+ "items": { "$ref": "TraceCertificate.v0.schema.json" }
+ },
+ "evidence_bundle": { "$ref": "EvidenceBundle.v0.schema.json" },
+ "verification_policy": {
+ "type": "object",
+ "required": ["policy_id", "required_checks"],
+ "additionalProperties": true,
+ "properties": {
+ "policy_id": { "type": "string", "minLength": 1 },
+ "required_checks": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "created_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/SignedScienceClaimBundle.v0.schema.json b/schemas/pcs/SignedScienceClaimBundle.v0.schema.json
new file mode 100644
index 0000000..e6293cb
--- /dev/null
+++ b/schemas/pcs/SignedScienceClaimBundle.v0.schema.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/SignedScienceClaimBundle.v0.schema.json",
+ "title": "SignedScienceClaimBundle.v0",
+ "type": "object",
+ "required": [
+ "schema_version",
+ "signed_bundle_id",
+ "science_claim_bundle",
+ "verification_result",
+ "signer",
+ "signed_at",
+ "source_repo",
+ "source_commit",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "schema_version": { "const": "SignedScienceClaimBundle.v0" },
+ "signed_bundle_id": { "type": "string", "minLength": 1 },
+ "science_claim_bundle": { "$ref": "ScienceClaimBundle.v0.schema.json" },
+ "verification_result": { "$ref": "VerificationResult.v0.schema.json" },
+ "signer": { "type": "string", "minLength": 1 },
+ "signed_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 1 },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "local_dev": { "type": "boolean" }
+ }
+}
diff --git a/schemas/pcs/SourceSpan.v0.schema.json b/schemas/pcs/SourceSpan.v0.schema.json
new file mode 100644
index 0000000..fe2e606
--- /dev/null
+++ b/schemas/pcs/SourceSpan.v0.schema.json
@@ -0,0 +1,55 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/SourceSpan.v0.schema.json",
+ "title": "SourceSpan.v0",
+ "type": "object",
+ "required": [
+ "source_span_id",
+ "schema_version",
+ "source_type",
+ "source_uri",
+ "start",
+ "end",
+ "hash",
+ "description"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "source_span_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "source_type": {
+ "type": "string",
+ "enum": [
+ "paper",
+ "policy_file",
+ "trace_event",
+ "lean_file",
+ "protocol_file",
+ "runtime_log",
+ "manual_note",
+ "source_code"
+ ]
+ },
+ "source_uri": { "type": "string", "minLength": 1 },
+ "start": {
+ "type": "object",
+ "required": ["line", "column"],
+ "additionalProperties": false,
+ "properties": {
+ "line": { "type": "integer", "minimum": 1 },
+ "column": { "type": "integer", "minimum": 0 }
+ }
+ },
+ "end": {
+ "type": "object",
+ "required": ["line", "column"],
+ "additionalProperties": false,
+ "properties": {
+ "line": { "type": "integer", "minimum": 1 },
+ "column": { "type": "integer", "minimum": 0 }
+ }
+ },
+ "hash": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "description": { "type": "string" }
+ }
+}
diff --git a/schemas/pcs/TraceCertificate.v0.schema.json b/schemas/pcs/TraceCertificate.v0.schema.json
new file mode 100644
index 0000000..a4d344f
--- /dev/null
+++ b/schemas/pcs/TraceCertificate.v0.schema.json
@@ -0,0 +1,43 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/TraceCertificate.v0.schema.json",
+ "title": "TraceCertificate.v0",
+ "type": "object",
+ "required": [
+ "certificate_id",
+ "schema_version",
+ "trace_hash",
+ "spec_hash",
+ "property_id",
+ "checker",
+ "checker_version",
+ "status",
+ "counterexample_ref",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "certificate_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "trace_hash": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "spec_hash": { "$ref": "common.defs.json#/$defs/hex_digest" },
+ "property_id": { "type": "string", "minLength": 1 },
+ "checker": { "type": "string", "minLength": 1 },
+ "checker_version": { "type": "string", "minLength": 1 },
+ "status": { "$ref": "common.defs.json#/$defs/trace_certificate_status" },
+ "counterexample_ref": {
+ "oneOf": [{ "type": "null" }, { "type": "string", "minLength": 1 }]
+ },
+ "created_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/VerificationResult.v0.schema.json b/schemas/pcs/VerificationResult.v0.schema.json
new file mode 100644
index 0000000..be045ed
--- /dev/null
+++ b/schemas/pcs/VerificationResult.v0.schema.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/VerificationResult.v0.schema.json",
+ "title": "VerificationResult.v0",
+ "type": "object",
+ "required": [
+ "verification_id",
+ "schema_version",
+ "bundle_id",
+ "verifier",
+ "verifier_version",
+ "status",
+ "checks",
+ "created_at",
+ "source_repo",
+ "source_commit",
+ "signature_or_digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "verification_id": { "type": "string", "minLength": 1 },
+ "schema_version": { "$ref": "common.defs.json#/$defs/schema_version" },
+ "bundle_id": { "type": "string", "minLength": 1 },
+ "verifier": { "type": "string", "minLength": 1 },
+ "verifier_version": { "type": "string", "minLength": 1 },
+ "status": { "$ref": "common.defs.json#/$defs/artifact_status" },
+ "checks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["check_id", "description", "status", "details"],
+ "additionalProperties": false,
+ "properties": {
+ "check_id": { "type": "string", "minLength": 1 },
+ "description": { "type": "string", "minLength": 1 },
+ "status": {
+ "type": "string",
+ "enum": ["passed", "failed", "skipped", "warning"]
+ },
+ "details": { "type": "object" }
+ }
+ }
+ },
+ "created_at": { "$ref": "common.defs.json#/$defs/iso8601_datetime" },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "signature_or_digest": { "$ref": "common.defs.json#/$defs/hex_digest" }
+ }
+}
diff --git a/schemas/pcs/common.defs.json b/schemas/pcs/common.defs.json
new file mode 100644
index 0000000..20f6f47
--- /dev/null
+++ b/schemas/pcs/common.defs.json
@@ -0,0 +1,73 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://pcs.sentinelops.ci/schemas/common.defs.json",
+ "title": "PCS Common Definitions",
+ "$defs": {
+ "schema_version": {
+ "type": "string",
+ "pattern": "^v0$"
+ },
+ "iso8601_datetime": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "hex_digest": {
+ "type": "string",
+ "pattern": "^sha256:[a-f0-9]{64}$"
+ },
+ "artifact_status": {
+ "type": "string",
+ "enum": [
+ "Draft",
+ "Extracted",
+ "HumanReviewed",
+ "Formalized",
+ "ProofPending",
+ "ProofChecked",
+ "CertificatePending",
+ "CertificateChecked",
+ "RuntimeObserved",
+ "RuntimeChecked",
+ "Rejected",
+ "EmpiricalOnly",
+ "Deprecated",
+ "Stale"
+ ]
+ },
+ "trace_certificate_status": {
+ "type": "string",
+ "enum": ["CertificatePending", "CertificateChecked", "Rejected", "Stale"]
+ },
+ "producer_metadata": {
+ "type": "object",
+ "required": [
+ "schema_version",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "status",
+ "signature_or_digest"
+ ],
+ "properties": {
+ "schema_version": { "$ref": "#/$defs/schema_version" },
+ "created_at": { "$ref": "#/$defs/iso8601_datetime" },
+ "producer": { "type": "string", "minLength": 1 },
+ "producer_version": { "type": "string", "minLength": 1 },
+ "source_repo": { "type": "string", "format": "uri" },
+ "source_commit": { "type": "string", "minLength": 7 },
+ "status": { "$ref": "#/$defs/artifact_status" },
+ "signature_or_digest": { "$ref": "#/$defs/hex_digest" }
+ }
+ },
+ "hash_map": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#/$defs/hex_digest" }
+ },
+ "ref_list": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 1 }
+ }
+ }
+}
diff --git a/schemas/pcs/legacy/LabTrust.ScienceClaimBundle.v0.schema.json b/schemas/pcs/legacy/LabTrust.ScienceClaimBundle.v0.schema.json
new file mode 100644
index 0000000..27ca1c6
--- /dev/null
+++ b/schemas/pcs/legacy/LabTrust.ScienceClaimBundle.v0.schema.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://scientific-memory.org/schemas/pcs/legacy/LabTrust.ScienceClaimBundle.v0.schema.json",
+ "title": "LabTrust legacy ScienceClaimBundle",
+ "type": "object",
+ "additionalProperties": true,
+ "required": [
+ "schema_version",
+ "created_at",
+ "producer",
+ "producer_version",
+ "source_repo",
+ "source_commit",
+ "status",
+ "signature_or_digest",
+ "claim",
+ "assumption_set",
+ "runtime_receipt",
+ "trace_certificate"
+ ],
+ "properties": {
+ "schema_version": { "const": "ScienceClaimBundle.v0" },
+ "claim": { "type": "object" },
+ "assumption_set": { "type": "object" },
+ "runtime_receipt": { "type": "object" },
+ "trace_certificate": { "type": "object" },
+ "evidence_bundle": { "type": "object" },
+ "limitations": { "type": "array", "items": { "type": "string" } },
+ "reproduce_commands": { "type": "array", "items": { "type": "string" } },
+ "verify_commands": { "type": "array", "items": { "type": "string" } }
+ }
+}
diff --git a/schemas/pcs/legacy/LabTrust.SignedScienceClaimBundle.v0.schema.json b/schemas/pcs/legacy/LabTrust.SignedScienceClaimBundle.v0.schema.json
new file mode 100644
index 0000000..496dc67
--- /dev/null
+++ b/schemas/pcs/legacy/LabTrust.SignedScienceClaimBundle.v0.schema.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://scientific-memory.org/schemas/pcs/legacy/LabTrust.SignedScienceClaimBundle.v0.schema.json",
+ "title": "LabTrust legacy signed bundle (portal import)",
+ "description": "Legacy mirror for Scientific Memory portal imports. Canonical PCS: pcs-core SignedScienceClaimBundle.v0.",
+ "type": "object",
+ "additionalProperties": true,
+ "required": ["schema_version", "science_claim_bundle", "signature_or_digest"],
+ "properties": {
+ "schema_version": {
+ "type": "string",
+ "anyOf": [
+ { "const": "SignedScienceClaimBundle.v0" },
+ { "const": "v0" }
+ ]
+ },
+ "science_claim_bundle": { "type": "object" },
+ "verification_result": { "type": "object" },
+ "signature_or_digest": { "type": "string", "minLength": 1 },
+ "reproduce_commands": { "type": "array", "items": { "type": "string" } },
+ "verify_commands": { "type": "array", "items": { "type": "string" } }
+ }
+}
diff --git a/schemas/pcs/legacy/LabTrust.VerificationResult.v0.schema.json b/schemas/pcs/legacy/LabTrust.VerificationResult.v0.schema.json
new file mode 100644
index 0000000..356f356
--- /dev/null
+++ b/schemas/pcs/legacy/LabTrust.VerificationResult.v0.schema.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://scientific-memory.org/schemas/pcs/legacy/LabTrust.VerificationResult.v0.schema.json",
+ "title": "LabTrust legacy VerificationResult",
+ "type": "object",
+ "additionalProperties": true,
+ "properties": {
+ "schema_version": {
+ "type": "string",
+ "anyOf": [{ "const": "VerificationResult.v0" }, { "const": "v0" }]
+ },
+ "id": { "type": "string" },
+ "verification_id": { "type": "string" },
+ "verifier": { "type": "string" },
+ "verifier_version": { "type": "string" },
+ "producer": { "type": "string" },
+ "producer_version": { "type": "string" },
+ "status": { "type": "string" },
+ "source_repo": { "type": "string" },
+ "source_commit": { "type": "string" },
+ "signature_or_digest": { "type": "string" },
+ "checks": { "type": "array" },
+ "overall_outcome": { "type": "string" }
+ }
+}
diff --git a/schemas/pcs/science_claim_bundle.schema.json b/schemas/pcs/science_claim_bundle.schema.json
index 05dbce0..787a0ee 100644
--- a/schemas/pcs/science_claim_bundle.schema.json
+++ b/schemas/pcs/science_claim_bundle.schema.json
@@ -1,107 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://scientific-memory.org/schemas/pcs/science_claim_bundle.schema.json",
- "title": "ScienceClaimBundle.v0",
- "type": "object",
- "additionalProperties": true,
- "required": [
- "schema_version",
- "created_at",
- "producer",
- "producer_version",
- "source_repo",
- "source_commit",
- "status",
- "signature_or_digest",
- "claim",
- "assumption_set",
- "runtime_receipt",
- "trace_certificate"
- ],
- "properties": {
- "schema_version": { "const": "ScienceClaimBundle.v0" },
- "claim": { "$ref": "#/$defs/claimArtifact" },
- "assumption_set": { "$ref": "#/$defs/assumptionSet" },
- "runtime_receipt": { "$ref": "#/$defs/namedArtifact" },
- "trace_certificate": { "$ref": "#/$defs/namedArtifact" },
- "evidence_bundle": { "$ref": "#/$defs/namedArtifact" },
- "verification_result": { "$ref": "#/$defs/namedArtifact" },
- "limitations": {
- "type": "array",
- "items": { "type": "string" }
- },
- "reproduce_commands": {
- "type": "array",
- "items": { "type": "string" }
- },
- "verify_commands": {
- "type": "array",
- "items": { "type": "string" }
- }
- },
- "$defs": {
- "artifactBase": {
- "$ref": "https://scientific-memory.org/schemas/pcs/artifact_base.schema.json"
- },
- "claimArtifact": {
- "allOf": [
- { "$ref": "#/$defs/artifactBase" },
- {
- "type": "object",
- "required": ["id", "claim_text", "guarantee_types"],
- "properties": {
- "schema_version": { "const": "ClaimArtifact.v0" },
- "id": { "type": "string", "minLength": 1 },
- "claim_text": { "type": "string", "minLength": 1 },
- "guarantee_types": {
- "type": "object",
- "additionalProperties": { "type": "boolean" }
- }
- }
- }
- ]
- },
- "assumptionSet": {
- "allOf": [
- { "$ref": "#/$defs/artifactBase" },
- {
- "type": "object",
- "required": ["id", "assumptions"],
- "properties": {
- "schema_version": { "const": "AssumptionSet.v0" },
- "id": { "type": "string", "minLength": 1 },
- "assumptions": {
- "type": "array",
- "minItems": 1,
- "items": {
- "type": "object",
- "required": ["id", "text"],
- "properties": {
- "id": { "type": "string" },
- "text": { "type": "string" },
- "kind": { "type": "string" },
- "status": { "type": "string" }
- },
- "additionalProperties": true
- }
- }
- }
- }
- ]
- },
- "namedArtifact": {
- "allOf": [
- { "$ref": "#/$defs/artifactBase" },
- {
- "type": "object",
- "required": ["id"],
- "properties": {
- "id": { "type": "string", "minLength": 1 },
- "payload": { "type": "object" },
- "summary": { "type": "string" }
- }
- }
- ]
- }
- }
+ "title": "ScienceClaimBundle (legacy alias)",
+ "description": "DEPRECATED alias. Use ScienceClaimBundle.v0.schema.json (pcs-core canonical).",
+ "$ref": "https://pcs-core/schemas/ScienceClaimBundle.v0.schema.json"
}
diff --git a/schemas/pcs/signed_science_claim_bundle.schema.json b/schemas/pcs/signed_science_claim_bundle.schema.json
index 558cd0a..2163bdc 100644
--- a/schemas/pcs/signed_science_claim_bundle.schema.json
+++ b/schemas/pcs/signed_science_claim_bundle.schema.json
@@ -1,34 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://scientific-memory.org/schemas/pcs/signed_science_claim_bundle.schema.json",
- "title": "SignedScienceClaimBundle (LabTrust v0.1)",
- "type": "object",
- "additionalProperties": true,
- "required": ["schema_version", "science_claim_bundle", "signature_or_digest"],
- "properties": {
- "schema_version": {
- "type": "string",
- "pattern": "^SignedScienceClaimBundle\\.v0$"
- },
- "bundle_id": { "type": "string", "minLength": 1 },
- "bundle_digest": { "type": "string", "minLength": 1 },
- "signed_at": { "type": "string", "minLength": 1 },
- "science_claim_bundle": {
- "type": "object",
- "description": "Validated by pcs-core or schemas/pcs/science_claim_bundle.schema.json"
- },
- "verification_result": {
- "type": "object",
- "description": "Validated by pcs-core or schemas/pcs/verification_result.schema.json"
- },
- "signature_or_digest": { "type": "string", "minLength": 1 },
- "reproduce_commands": {
- "type": "array",
- "items": { "type": "string" }
- },
- "verify_commands": {
- "type": "array",
- "items": { "type": "string" }
- }
- }
+ "title": "SignedScienceClaimBundle (legacy alias)",
+ "description": "DEPRECATED alias. Use SignedScienceClaimBundle.v0.schema.json (pcs-core canonical).",
+ "$ref": "https://pcs-core/schemas/SignedScienceClaimBundle.v0.schema.json"
}
diff --git a/schemas/pcs/verification_result.schema.json b/schemas/pcs/verification_result.schema.json
index 50fde71..b5df207 100644
--- a/schemas/pcs/verification_result.schema.json
+++ b/schemas/pcs/verification_result.schema.json
@@ -1,48 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://scientific-memory.org/schemas/pcs/verification_result.schema.json",
- "title": "VerificationResult.v0",
- "type": "object",
- "additionalProperties": true,
- "required": [
- "schema_version",
- "created_at",
- "producer",
- "producer_version",
- "source_repo",
- "source_commit",
- "status",
- "signature_or_digest",
- "checks"
- ],
- "properties": {
- "schema_version": { "const": "VerificationResult.v0" },
- "checks": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["id", "name", "outcome"],
- "properties": {
- "id": { "type": "string" },
- "name": { "type": "string" },
- "outcome": {
- "type": "string",
- "enum": ["pass", "fail", "skip", "warn"]
- },
- "detail": { "type": "string" },
- "guarantee_type": { "type": "string" }
- },
- "additionalProperties": true
- }
- },
- "overall_outcome": {
- "type": "string",
- "enum": ["pass", "fail", "partial"]
- }
- },
- "allOf": [
- {
- "$ref": "https://scientific-memory.org/schemas/pcs/artifact_base.schema.json"
- }
- ]
+ "title": "VerificationResult (legacy alias)",
+ "description": "DEPRECATED alias. Use VerificationResult.v0.schema.json (pcs-core canonical).",
+ "$ref": "https://pcs-core/schemas/VerificationResult.v0.schema.json"
}
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
index 91e238e..43675b5 100644
--- a/scripts/bootstrap.sh
+++ b/scripts/bootstrap.sh
@@ -3,6 +3,10 @@ set -euo pipefail
echo "==> installing python deps"
uv sync --all-packages
+if [ -d "../pcs-core/python" ] || [ -d "../../pcs-core/python" ] || [ -d "pcs-core/python" ]; then
+ echo "==> installing pcs-core extra (pipeline)"
+ uv sync --project pipeline --extra pcs || true
+fi
echo "==> installing node deps"
pnpm install
diff --git a/scripts/just_env.sh b/scripts/just_env.sh
index 2b297a8..da6f5a6 100755
--- a/scripts/just_env.sh
+++ b/scripts/just_env.sh
@@ -1,5 +1,7 @@
#!/usr/bin/env bash
# Shared PATH bootstrap for non-interactive bash (just recipes, smoke tests).
+# Prefer OS TLS trust store for uv (Windows CRYPT_E_NO_REVOCATION_CHECK / UnknownIssuer).
+export UV_NATIVE_TLS="${UV_NATIVE_TLS:-1}"
# Git Bash on Windows usually inherits a full PATH; WSL/bash may not (uv only).
_winroot=""
for r in /c /mnt/c; do
diff --git a/scripts/run_pcs_integration_tests.sh b/scripts/run_pcs_integration_tests.sh
new file mode 100644
index 0000000..5ada091
--- /dev/null
+++ b/scripts/run_pcs_integration_tests.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# PCS integration tests: live pcs-core validation (PCS_INTEGRATION=1 disables test mocks).
+set -euo pipefail
+_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+# shellcheck source=just_env.sh
+source "$_root/scripts/just_env.sh"
+
+export PCS_INTEGRATION=1
+export PYTHONPATH="$_root/pipeline/src${PYTHONPATH:+:$PYTHONPATH}"
+
+for py in python python3; do
+ if command -v "$py" >/dev/null 2>&1 && "$py" -c "import pytest" 2>/dev/null; then
+ exec "$py" -m pytest "$_root/tests/pcs/test_pcs_integration.py" -v "$@"
+ fi
+done
+
+exec uv run --no-sync --native-tls pytest "$_root/tests/pcs/test_pcs_integration.py" -v "$@"
diff --git a/scripts/sm_python.sh b/scripts/sm_python.sh
new file mode 100644
index 0000000..d58ebf2
--- /dev/null
+++ b/scripts/sm_python.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+# Run pipeline CLI with an interpreter that already has deps (avoids PyPI on every just recipe).
+set -euo pipefail
+_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+# shellcheck source=just_env.sh
+source "$_root/scripts/just_env.sh"
+
+export PYTHONPATH="$_root/pipeline/src${PYTHONPATH:+:$PYTHONPATH}"
+
+_python_candidates() {
+ # Prefer active conda/system Python (has pipeline deps); root .venv often lacks sm-pipeline.
+ command -v python 2>/dev/null || true
+ command -v python3 2>/dev/null || true
+ if [ -x "$_root/pipeline/.venv/Scripts/python.exe" ]; then
+ echo "$_root/pipeline/.venv/Scripts/python.exe"
+ fi
+ if [ -x "$_root/pipeline/.venv/bin/python" ]; then
+ echo "$_root/pipeline/.venv/bin/python"
+ fi
+ if [ -x "$_root/.venv/Scripts/python.exe" ]; then
+ echo "$_root/.venv/Scripts/python.exe"
+ fi
+ if [ -x "$_root/.venv/bin/python" ]; then
+ echo "$_root/.venv/bin/python"
+ fi
+}
+
+if [ "${SM_FORCE_UV:-0}" != "1" ]; then
+ while IFS= read -r py; do
+ [ -n "$py" ] || continue
+ if "$py" -c "import typer" 2>/dev/null; then
+ exec "$py" "$@"
+ fi
+ done < <(_python_candidates)
+fi
+
+if uv run --help 2>/dev/null | grep -q -- '--no-sync'; then
+ exec uv run --no-sync --native-tls --project "$_root/pipeline" python "$@"
+fi
+
+exec uv run --native-tls --project "$_root/pipeline" python "$@"
diff --git a/scripts/sync_pcs_schemas.py b/scripts/sync_pcs_schemas.py
new file mode 100644
index 0000000..7cbf885
--- /dev/null
+++ b/scripts/sync_pcs_schemas.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""Copy canonical PCS schemas from pcs-core into scientific-memory (consumer mirror)."""
+
+from __future__ import annotations
+
+import json
+import shutil
+import sys
+from pathlib import Path
+
+# Canonical v0.1 artifacts Scientific Memory imports/renders.
+CANONICAL_SCHEMAS = (
+ "SignedScienceClaimBundle.v0.schema.json",
+ "ScienceClaimBundle.v0.schema.json",
+ "VerificationResult.v0.schema.json",
+ "ClaimArtifact.v0.schema.json",
+ "AssumptionSet.v0.schema.json",
+ "RuntimeReceipt.v0.schema.json",
+ "TraceCertificate.v0.schema.json",
+ "EvidenceBundle.v0.schema.json",
+ "SourceSpan.v0.schema.json",
+ "common.defs.json",
+)
+
+LEGACY_ALIASES = {
+ "signed_science_claim_bundle.schema.json": "SignedScienceClaimBundle.v0.schema.json",
+ "science_claim_bundle.schema.json": "ScienceClaimBundle.v0.schema.json",
+ "verification_result.schema.json": "VerificationResult.v0.schema.json",
+}
+
+
+def _find_pcs_core_schemas(repo_root: Path) -> Path:
+ candidates = [
+ repo_root / "pcs-core" / "schemas",
+ repo_root.parent / "pcs-core" / "schemas",
+ Path(__file__).resolve().parents[1].parent / "pcs-core" / "schemas",
+ ]
+ for path in candidates:
+ if (path / "SignedScienceClaimBundle.v0.schema.json").is_file():
+ return path
+ raise FileNotFoundError(
+ "pcs-core schemas not found. Clone pcs-core adjacent to scientific-memory "
+ "or set PCS_CORE_SCHEMAS_DIR."
+ )
+
+
+def main() -> int:
+ repo_root = Path(__file__).resolve().parents[1]
+ src = Path(sys.argv[1]) if len(sys.argv) > 1 else _find_pcs_core_schemas(repo_root)
+ dest = repo_root / "schemas" / "pcs"
+ dest.mkdir(parents=True, exist_ok=True)
+ # Never overwrite LabTrust legacy mirrors (schemas/pcs/legacy/).
+ (dest / "legacy").mkdir(exist_ok=True)
+
+ copied: list[str] = []
+ for name in CANONICAL_SCHEMAS:
+ source = src / name
+ if not source.is_file():
+ print(f"skip missing: {name}", file=sys.stderr)
+ continue
+ shutil.copy2(source, dest / name)
+ copied.append(name)
+
+ manifest = {
+ "source": str(src.resolve()),
+ "canonical_schemas": copied,
+ "legacy_aliases": LEGACY_ALIASES,
+ "note": "Scientific Memory mirrors pcs-core; pcs-core remains canonical.",
+ }
+ (dest / "SCHEMA_MIRROR.json").write_text(
+ json.dumps(manifest, indent=2) + "\n",
+ encoding="utf-8",
+ )
+
+ print(f"Synced {len(copied)} schemas from {src} -> {dest}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tests/pcs/__init__.py b/tests/pcs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/pcs/conftest.py b/tests/pcs/conftest.py
index 215e1c4..ff00ab5 100644
--- a/tests/pcs/conftest.py
+++ b/tests/pcs/conftest.py
@@ -1,8 +1,38 @@
-"""Ensure pipeline package is importable when running PCS tests from repo root."""
+"""PCS tests: pipeline on PYTHONPATH; unit tests use mirrors unless PCS_INTEGRATION=1."""
+import os
+import shutil
import sys
from pathlib import Path
+import pytest
+
_PIPELINE_SRC = Path(__file__).resolve().parents[2] / "pipeline" / "src"
if str(_PIPELINE_SRC) not in sys.path:
sys.path.insert(0, str(_PIPELINE_SRC))
+_PCS_TESTS = Path(__file__).resolve().parent
+if str(_PCS_TESTS) not in sys.path:
+ sys.path.insert(0, str(_PCS_TESTS))
+
+
+def copy_pcs_schemas(root: Path) -> None:
+ dest = root / "schemas" / "pcs"
+ dest.mkdir(parents=True, exist_ok=True)
+ src = REPO_ROOT / "schemas" / "pcs"
+ for f in src.glob("*.json"):
+ shutil.copy(f, dest / f.name)
+ legacy_dest = dest / "legacy"
+ legacy_dest.mkdir(exist_ok=True)
+ for f in src.glob("legacy/*.json"):
+ shutil.copy(f, legacy_dest / f.name)
+
+
+@pytest.fixture(autouse=True)
+def _unit_tests_use_schema_mirrors(monkeypatch: pytest.MonkeyPatch) -> None:
+ """Contract tests use vendored mirrors; set PCS_INTEGRATION=1 for live pcs-core."""
+ if os.environ.get("PCS_INTEGRATION") == "1":
+ return
+ monkeypatch.setattr(
+ "sm_pipeline.pcs_validate.validator.validate_with_pcs_core",
+ lambda _bundle: [],
+ )
diff --git a/tests/pcs/fixtures/failed_verification_result.json b/tests/pcs/fixtures/failed_verification_result.json
new file mode 100644
index 0000000..53c554c
--- /dev/null
+++ b/tests/pcs/fixtures/failed_verification_result.json
@@ -0,0 +1,81 @@
+{
+ "schema_version": "SignedScienceClaimBundle.v0",
+ "signature_or_digest": "sha256:failed-vr",
+ "verification_result": {
+ "schema_version": "VerificationResult.v0",
+ "id": "vr-failed",
+ "created_at": "2026-05-01T12:00:00Z",
+ "producer": "provability-fabric",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/SentinelOps-CI/provability-fabric",
+ "source_commit": "abc123def456",
+ "status": "failed",
+ "signature_or_digest": "sha256:vr-failed",
+ "overall_outcome": "fail",
+ "checks": [
+ {
+ "id": "check-1",
+ "name": "Bundle signature",
+ "outcome": "fail",
+ "detail": "Signature mismatch."
+ }
+ ]
+ },
+ "science_claim_bundle": {
+ "schema_version": "ScienceClaimBundle.v0",
+ "created_at": "2026-05-01T11:30:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "labtrust-demo",
+ "status": "RuntimeChecked",
+ "signature_or_digest": "sha256:bundle",
+ "claim": {
+ "schema_version": "ClaimArtifact.v0",
+ "id": "claim-failed-vr",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "labtrust-demo",
+ "status": "RuntimeChecked",
+ "signature_or_digest": "sha256:claim",
+ "claim_text": "Verification failed.",
+ "guarantee_types": {}
+ },
+ "assumption_set": {
+ "schema_version": "AssumptionSet.v0",
+ "id": "assumptions",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "labtrust-demo",
+ "status": "RuntimeObserved",
+ "signature_or_digest": "sha256:assumptions",
+ "assumptions": [{ "id": "a1", "text": "Simulation only." }]
+ },
+ "runtime_receipt": {
+ "schema_version": "RuntimeReceipt.v0",
+ "id": "receipt",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "labtrust-demo",
+ "status": "RuntimeChecked",
+ "signature_or_digest": "sha256:receipt"
+ },
+ "trace_certificate": {
+ "schema_version": "TraceCertificate.v0",
+ "id": "cert",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "CertifyEdge",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/CertifyEdge",
+ "source_commit": "cert-demo",
+ "status": "CertificateChecked",
+ "signature_or_digest": "sha256:cert"
+ }
+ }
+}
diff --git a/tests/pcs/fixtures/missing_assumption_set.json b/tests/pcs/fixtures/missing_assumption_set.json
new file mode 100644
index 0000000..c5fb5d7
--- /dev/null
+++ b/tests/pcs/fixtures/missing_assumption_set.json
@@ -0,0 +1,62 @@
+{
+ "schema_version": "SignedScienceClaimBundle.v0",
+ "signature_or_digest": "sha256:missing-assumption-set",
+ "verification_result": {
+ "schema_version": "VerificationResult.v0",
+ "id": "vr-1",
+ "created_at": "2026-05-01T12:00:00Z",
+ "producer": "provability-fabric",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/SentinelOps-CI/provability-fabric",
+ "source_commit": "abc123def456",
+ "status": "ProofChecked",
+ "signature_or_digest": "sha256:vr",
+ "overall_outcome": "pass",
+ "checks": [{ "id": "c1", "name": "ok", "outcome": "pass" }]
+ },
+ "science_claim_bundle": {
+ "schema_version": "ScienceClaimBundle.v0",
+ "created_at": "2026-05-01T11:30:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "bad-commit",
+ "status": "RuntimeChecked",
+ "signature_or_digest": "sha256:bundle",
+ "claim": {
+ "schema_version": "ClaimArtifact.v0",
+ "id": "claim-no-assumption-set",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "bad-commit",
+ "status": "RuntimeChecked",
+ "signature_or_digest": "sha256:claim",
+ "claim_text": "Missing assumption set.",
+ "guarantee_types": {}
+ },
+ "runtime_receipt": {
+ "schema_version": "RuntimeReceipt.v0",
+ "id": "receipt",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "LabTrust-Gym",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/LabTrust-Gym",
+ "source_commit": "bad-commit",
+ "status": "RuntimeChecked",
+ "signature_or_digest": "sha256:receipt"
+ },
+ "trace_certificate": {
+ "schema_version": "TraceCertificate.v0",
+ "id": "cert",
+ "created_at": "2026-05-01T11:00:00Z",
+ "producer": "CertifyEdge",
+ "producer_version": "0.1.0",
+ "source_repo": "https://github.com/fraware/CertifyEdge",
+ "source_commit": "bad-commit",
+ "status": "CertificateChecked",
+ "signature_or_digest": "sha256:cert"
+ }
+ }
+}
diff --git a/tests/pcs/fixtures/valid_pf_handoff_bundle.json b/tests/pcs/fixtures/valid_pf_handoff_bundle.json
new file mode 100644
index 0000000..482ede0
--- /dev/null
+++ b/tests/pcs/fixtures/valid_pf_handoff_bundle.json
@@ -0,0 +1,9 @@
+{
+ "schema_version": "v0",
+ "signed_bundle_id": "signed-scb-qc-release-pf",
+ "science_claim_bundle": {},
+ "verification_result": {},
+ "signer": "Provability Fabric",
+ "signed_at": "2026-05-16T12:30:00Z",
+ "signature_or_digest": "sha256:pf-handoff-placeholder"
+}
diff --git a/tests/pcs/fixtures/valid_signed_pcs_core_bundle.json b/tests/pcs/fixtures/valid_signed_pcs_core_bundle.json
index fcd4f0b..de78ab0 100644
--- a/tests/pcs/fixtures/valid_signed_pcs_core_bundle.json
+++ b/tests/pcs/fixtures/valid_signed_pcs_core_bundle.json
@@ -1,48 +1,6 @@
{
"schema_version": "SignedScienceClaimBundle.v0",
- "bundle_id": "scb-qc-release-v0.1",
- "bundle_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
- "signed_at": "2026-05-16T12:25:00Z",
- "signature_or_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
- "reproduce_commands": [
- "labtrust run-demo qc-release",
- "labtrust export-trace --run runs/qc-release --out trace.json",
- "labtrust export-runtime-receipt --run runs/qc-release --out runtime_receipt.json",
- "labtrust export-pcs --run runs/qc-release --out science_claim_bundle.pending.json"
- ],
- "verify_commands": [
- "pf verify science-claim signed_science_claim_bundle.json",
- "pf sign science-claim science_claim_bundle.certified.json --out signed_science_claim_bundle.json",
- "just pcs-validate-bundle BUNDLE=signed_science_claim_bundle.json"
- ],
- "verification_result": {
- "verification_id": "verify-scb-qc-release-v0.1",
- "schema_version": "v0",
- "bundle_id": "scb-qc-release-v0.1",
- "verifier": "provability-fabric",
- "verifier_version": "0.1.0",
- "status": "ProofChecked",
- "checks": [
- {
- "check_id": "schema-valid",
- "description": "ScienceClaimBundle conforms to ScienceClaimBundle.v0 schema",
- "status": "passed",
- "details": {}
- },
- {
- "check_id": "trace-hash-alignment",
- "description": "Runtime receipt trace_hash matches certificate trace_hash",
- "status": "passed",
- "details": {
- "trace_hash": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
- }
- }
- ],
- "created_at": "2026-05-16T12:20:00Z",
- "source_repo": "https://github.com/SentinelOps-CI/provability-fabric",
- "source_commit": "cccccccccccccccccccccccccccccccccccccccc",
- "signature_or_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- },
+ "signed_bundle_id": "signed-scb-qc-release-v0.1",
"science_claim_bundle": {
"bundle_id": "scb-qc-release-v0.1",
"schema_version": "v0",
@@ -95,6 +53,9 @@
"started_at": "2026-05-16T11:58:00Z",
"ended_at": "2026-05-16T12:00:00Z",
"status": "RuntimeObserved",
+ "run_outcome": "passed",
+ "final_reason_code": "ok",
+ "released": true,
"events_hash": "sha256:3333333333333333333333333333333333333333333333333333333333333333",
"policy_hash": "sha256:4444444444444444444444444444444444444444444444444444444444444444",
"trace_hash": "sha256:5555555555555555555555555555555555555555555555555555555555555555",
@@ -157,5 +118,44 @@
"source_repo": "https://github.com/fraware/LabTrust-Gym",
"source_commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"signature_or_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
- }
+ },
+ "verification_result": {
+ "verification_id": "verify-scb-qc-release-v0.1",
+ "schema_version": "v0",
+ "bundle_id": "scb-qc-release-v0.1",
+ "verifier": "provability-fabric",
+ "verifier_version": "0.1.0",
+ "status": "ProofChecked",
+ "checks": [
+ {
+ "check_id": "schema-valid",
+ "description": "ScienceClaimBundle conforms to schema",
+ "status": "passed",
+ "details": {}
+ },
+ {
+ "check_id": "trace-hash-alignment",
+ "description": "Receipt trace_hash matches certificate",
+ "status": "passed",
+ "details": {}
+ }
+ ],
+ "created_at": "2026-05-16T12:20:00Z",
+ "source_repo": "https://github.com/SentinelOps-CI/provability-fabric",
+ "source_commit": "cccccccccccccccccccccccccccccccccccccccc",
+ "signature_or_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ },
+ "signer": "Provability Fabric",
+ "signed_at": "2026-05-16T12:25:00Z",
+ "source_repo": "https://github.com/SentinelOps-CI/provability-fabric",
+ "source_commit": "cccccccccccccccccccccccccccccccccccccccc",
+ "signature_or_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
+ "reproduce_commands": [
+ "labtrust run-demo qc-release",
+ "pf verify science-claim signed_science_claim_bundle.json"
+ ],
+ "verify_commands": [
+ "pf verify science-claim signed_science_claim_bundle.json",
+ "just pcs-validate-bundle BUNDLE=signed_science_claim_bundle.json"
+ ]
}
diff --git a/tests/pcs/schema_fixtures.py b/tests/pcs/schema_fixtures.py
new file mode 100644
index 0000000..9610283
--- /dev/null
+++ b/tests/pcs/schema_fixtures.py
@@ -0,0 +1,20 @@
+"""Copy PCS schema mirrors into a temp repo root for isolated tests."""
+
+from __future__ import annotations
+
+import shutil
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+
+
+def copy_pcs_schemas(root: Path) -> None:
+ dest = root / "schemas" / "pcs"
+ dest.mkdir(parents=True, exist_ok=True)
+ src = REPO_ROOT / "schemas" / "pcs"
+ for f in src.glob("*.json"):
+ shutil.copy(f, dest / f.name)
+ legacy_dest = dest / "legacy"
+ legacy_dest.mkdir(exist_ok=True)
+ for f in src.glob("legacy/*.json"):
+ shutil.copy(f, legacy_dest / f.name)
diff --git a/tests/pcs/test_import_labtrust_bundle.py b/tests/pcs/test_import_labtrust_bundle.py
deleted file mode 100644
index c4b10b2..0000000
--- a/tests/pcs/test_import_labtrust_bundle.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import json
-import shutil
-import tempfile
-from pathlib import Path
-
-from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
-
-FIXTURES = Path(__file__).resolve().parent / "fixtures"
-REPO_ROOT = Path(__file__).resolve().parents[2]
-
-
-def test_valid_bundle_imports() -> None:
- bundle_path = FIXTURES / "valid_signed_science_claim_bundle.json"
- with tempfile.TemporaryDirectory() as tmp:
- root = Path(tmp)
- _copy_schemas(root)
- result = import_signed_bundle(
- bundle_path,
- repo_root=root,
- write=True,
- )
- assert result.claim_id == "labtrust-qc-release-claim-001"
- read_model_path = (
- root / "corpus" / "pcs" / "claims" / result.claim_id / "read_model.json"
- )
- assert read_model_path.is_file()
- read_model = json.loads(read_model_path.read_text(encoding="utf-8"))
- assert read_model["claim"]["text"]
- assert read_model["source_repositories"]
- assert any(
- s["source_repo"] == "https://github.com/fraware/LabTrust-Gym"
- for s in read_model["source_repositories"]
- )
-
-
-def test_import_preserves_source_repo_and_commit() -> None:
- bundle_path = FIXTURES / "valid_signed_science_claim_bundle.json"
- raw = json.loads(bundle_path.read_text(encoding="utf-8"))
- with tempfile.TemporaryDirectory() as tmp:
- root = Path(tmp)
- _copy_schemas(root)
- import_signed_bundle(bundle_path, repo_root=root, write=True)
- stored = json.loads(
- (root / "corpus" / "pcs" / "claims" / "labtrust-qc-release-claim-001" / "signed_bundle.json").read_text(
- encoding="utf-8"
- )
- )
- scb = stored["science_claim_bundle"]
- assert scb["claim"]["source_repo"] == raw["science_claim_bundle"]["claim"]["source_repo"]
- assert scb["claim"]["source_commit"] == raw["science_claim_bundle"]["claim"]["source_commit"]
- assert scb["claim"]["signature_or_digest"] == raw["science_claim_bundle"]["claim"]["signature_or_digest"]
-
-
-def _copy_schemas(root: Path) -> None:
- dest = root / "schemas" / "pcs"
- dest.mkdir(parents=True)
- for f in (REPO_ROOT / "schemas" / "pcs").glob("*.json"):
- shutil.copy(f, dest / f.name)
diff --git a/tests/pcs/test_import_pcs_core_bundle.py b/tests/pcs/test_import_pcs_core_bundle.py
deleted file mode 100644
index 49bcf88..0000000
--- a/tests/pcs/test_import_pcs_core_bundle.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import json
-import shutil
-import tempfile
-from pathlib import Path
-
-from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
-
-FIXTURES = Path(__file__).resolve().parent / "fixtures"
-REPO_ROOT = Path(__file__).resolve().parents[2]
-
-
-def test_pcs_core_signed_bundle_imports() -> None:
- bundle_path = FIXTURES / "valid_signed_pcs_core_bundle.json"
- with tempfile.TemporaryDirectory() as tmp:
- root = Path(tmp)
- _copy_schemas(root)
- result = import_signed_bundle(bundle_path, repo_root=root, write=True)
- assert result.claim_id == "claim-qc-release-v0.1"
- read_model = json.loads(
- (root / "corpus" / "pcs" / "claims" / result.claim_id / "read_model.json").read_text(
- encoding="utf-8"
- )
- )
- assert read_model["claim"]["text"]
- assert read_model["trace_certificate"]["status"] == "CertificateChecked"
- assert read_model["verification_result"] is not None
- assert read_model["verification_result"]["checks"][0]["outcome"] == "pass"
- assert any(h["name"] == "trace_hash" for h in read_model["artifact_hashes"])
-
-
-def _copy_schemas(root: Path) -> None:
- dest = root / "schemas" / "pcs"
- dest.mkdir(parents=True)
- for f in (REPO_ROOT / "schemas" / "pcs").glob("*.json"):
- shutil.copy(f, dest / f.name)
diff --git a/tests/pcs/test_import_verification_result.py b/tests/pcs/test_import_verification_result.py
deleted file mode 100644
index 4966318..0000000
--- a/tests/pcs/test_import_verification_result.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import json
-import shutil
-import tempfile
-from pathlib import Path
-
-from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
-from sm_pipeline.pcs_import.verification_result_importer import merge_verification_result
-from sm_pipeline.pcs_validate.validator import collect_import_warnings
-
-FIXTURES = Path(__file__).resolve().parent / "fixtures"
-REPO_ROOT = Path(__file__).resolve().parents[2]
-
-
-def test_missing_verification_result_warning() -> None:
- bundle = json.loads(
- (FIXTURES / "missing_verification_result.json").read_text(encoding="utf-8")
- )
- warnings = collect_import_warnings(bundle)
- assert any("VerificationResult is absent" in w for w in warnings)
-
- with tempfile.TemporaryDirectory() as tmp:
- root = Path(tmp)
- _copy_schemas(root)
- result = import_signed_bundle(
- FIXTURES / "missing_verification_result.json",
- repo_root=root,
- write=True,
- )
- assert any("VerificationResult is absent" in w for w in result.warnings)
- read_model = json.loads(
- (root / "corpus" / "pcs" / "claims" / result.claim_id / "read_model.json").read_text(
- encoding="utf-8"
- )
- )
- assert read_model.get("verification_result") is None
-
-
-def test_merge_verification_result_preserves_checks() -> None:
- bundle = json.loads(
- (FIXTURES / "missing_verification_result.json").read_text(encoding="utf-8")
- )
- vr = {
- "schema_version": "VerificationResult.v0",
- "created_at": "2026-05-01T12:00:00Z",
- "producer": "provability-fabric",
- "producer_version": "0.1.0",
- "source_repo": "https://github.com/SentinelOps-CI/provability-fabric",
- "source_commit": "abc",
- "status": "ProofChecked",
- "signature_or_digest": "sha256:vr",
- "checks": [{"id": "c1", "name": "test", "outcome": "pass"}],
- }
- merged = merge_verification_result(bundle, vr)
- assert merged["verification_result"]["checks"][0]["id"] == "c1"
-
-
-def _copy_schemas(root: Path) -> None:
- dest = root / "schemas" / "pcs"
- dest.mkdir(parents=True)
- for f in (REPO_ROOT / "schemas" / "pcs").glob("*.json"):
- shutil.copy(f, dest / f.name)
diff --git a/tests/pcs/test_pcs_import.py b/tests/pcs/test_pcs_import.py
new file mode 100644
index 0000000..4a4ffb4
--- /dev/null
+++ b/tests/pcs/test_pcs_import.py
@@ -0,0 +1,130 @@
+"""PCS import contract tests (canonical pcs-core schema names)."""
+
+import json
+import shutil
+import tempfile
+from pathlib import Path
+
+import pytest
+
+from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
+from sm_pipeline.pcs_validate.schema_registry import (
+ SIGNED_BUNDLE_SCHEMA,
+ resolve_schema_path,
+)
+from sm_pipeline.pcs_validate.validator import BundleValidationError, validate_signed_bundle
+
+from schema_fixtures import copy_pcs_schemas
+
+FIXTURES = Path(__file__).resolve().parent / "fixtures"
+REPO_ROOT = Path(__file__).resolve().parents[2]
+
+
+def test_import_signed_science_claim_bundle_valid() -> None:
+ bundle_path = FIXTURES / "valid_signed_science_claim_bundle.json"
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ result = import_signed_bundle(bundle_path, repo_root=root, write=True)
+ assert result.claim_id == "labtrust-qc-release-claim-001"
+ report_path = (
+ root
+ / "corpus"
+ / "pcs"
+ / "claims"
+ / result.claim_id
+ / "scientific_memory_import_report.json"
+ )
+ assert report_path.is_file()
+ report = json.loads(report_path.read_text(encoding="utf-8"))
+ assert report["verification_status"] == "passed"
+ assert report["render_path"] == f"/pcs/claims/{result.claim_id}"
+
+
+def test_import_pcs_core_signed_bundle_valid() -> None:
+ bundle_path = FIXTURES / "valid_signed_pcs_core_bundle.json"
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ result = import_signed_bundle(bundle_path, repo_root=root, write=True)
+ assert result.claim_id == "claim-qc-release-v0.1"
+ read_model = json.loads(
+ (root / "corpus" / "pcs" / "claims" / result.claim_id / "read_model.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ vr = read_model["verification_result"]
+ assert vr["verification_id"] == "verify-scb-qc-release-v0.1"
+ assert vr["verifier"] == "provability-fabric"
+ assert read_model["canonical_digests"]["signed_bundle"]
+
+
+def test_import_rejects_missing_assumption_set() -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ with pytest.raises(BundleValidationError):
+ import_signed_bundle(
+ FIXTURES / "missing_assumption_set.json",
+ repo_root=root,
+ write=False,
+ )
+
+
+def test_import_rejects_empty_assumptions() -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ with pytest.raises(BundleValidationError):
+ import_signed_bundle(
+ FIXTURES / "missing_assumptions.json",
+ repo_root=root,
+ write=False,
+ )
+
+
+def test_import_rejects_failed_verification_result() -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ with pytest.raises(BundleValidationError):
+ import_signed_bundle(
+ FIXTURES / "failed_verification_result.json",
+ repo_root=root,
+ write=False,
+ )
+
+
+def test_import_warns_or_rejects_missing_verification_result_depending_on_strict() -> None:
+ bundle_path = FIXTURES / "missing_verification_result.json"
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ with pytest.raises(BundleValidationError):
+ import_signed_bundle(bundle_path, repo_root=root, strict=True, write=False)
+
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ _copy_schemas(root)
+ result = import_signed_bundle(bundle_path, repo_root=root, strict=False, write=True)
+ assert any("VerificationResult is absent" in w for w in result.warnings)
+ report = json.loads(
+ (
+ root
+ / "corpus"
+ / "pcs"
+ / "claims"
+ / result.claim_id
+ / "scientific_memory_import_report.json"
+ ).read_text(encoding="utf-8")
+ )
+ assert report["verification_status"] == "absent"
+
+
+def test_canonical_schema_files_exist() -> None:
+ schemas_dir = REPO_ROOT / "schemas" / "pcs"
+ assert resolve_schema_path(schemas_dir, SIGNED_BUNDLE_SCHEMA).name == SIGNED_BUNDLE_SCHEMA
+
+
+def _copy_schemas(root: Path) -> None:
+ copy_pcs_schemas(root)
diff --git a/tests/pcs/test_pcs_integration.py b/tests/pcs/test_pcs_integration.py
new file mode 100644
index 0000000..38838c0
--- /dev/null
+++ b/tests/pcs/test_pcs_integration.py
@@ -0,0 +1,57 @@
+"""Integration tests with live pcs-core (run: PCS_INTEGRATION=1 pytest tests/pcs/test_pcs_integration.py)."""
+
+from __future__ import annotations
+
+import json
+import os
+from pathlib import Path
+
+import pytest
+
+pytestmark = pytest.mark.skipif(
+ os.environ.get("PCS_INTEGRATION") != "1",
+ reason="Set PCS_INTEGRATION=1 to run live pcs-core validation",
+)
+
+pcs_core = pytest.importorskip("pcs_core")
+
+from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
+from sm_pipeline.pcs_validate.pcs_core_hook import validate_with_pcs_core
+
+from schema_fixtures import copy_pcs_schemas
+
+FIXTURES = Path(__file__).resolve().parent / "fixtures"
+REPO_ROOT = Path(__file__).resolve().parents[2]
+
+
+def test_pcs_core_validates_official_example() -> None:
+ bundle = json.loads(
+ (FIXTURES / "valid_signed_pcs_core_bundle.json").read_text(encoding="utf-8")
+ )
+ errors = validate_with_pcs_core(bundle)
+ assert errors == []
+
+
+def test_pcs_core_bundle_imports_end_to_end(tmp_path: Path) -> None:
+ root = tmp_path
+ _copy_schemas(root)
+ result = import_signed_bundle(
+ FIXTURES / "valid_signed_pcs_core_bundle.json",
+ repo_root=root,
+ write=True,
+ )
+ report = json.loads(
+ (
+ root
+ / "corpus"
+ / "pcs"
+ / "claims"
+ / result.claim_id
+ / "scientific_memory_import_report.json"
+ ).read_text(encoding="utf-8")
+ )
+ assert report["verification_status"] == "passed"
+
+
+def _copy_schemas(root: Path) -> None:
+ copy_pcs_schemas(root)
diff --git a/tests/pcs/test_render_pcs_claim.py b/tests/pcs/test_pcs_render.py
similarity index 65%
rename from tests/pcs/test_render_pcs_claim.py
rename to tests/pcs/test_pcs_render.py
index ce39abc..c5292a9 100644
--- a/tests/pcs/test_render_pcs_claim.py
+++ b/tests/pcs/test_pcs_render.py
@@ -1,3 +1,5 @@
+"""PCS portal read-model / rendering contract tests."""
+
import json
import shutil
import tempfile
@@ -6,6 +8,8 @@
from sm_pipeline.pcs_import.artifact_normalizer import LIMITATION_NOTICE, normalize_signed_bundle
from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
+from schema_fixtures import copy_pcs_schemas
+
FIXTURES = Path(__file__).resolve().parent / "fixtures"
REPO_ROOT = Path(__file__).resolve().parents[2]
@@ -14,7 +18,10 @@
"assumption_set",
"runtime_receipt",
"trace_certificate",
+ "evidence_bundle",
+ "verification_result",
"artifact_hashes",
+ "canonical_digests",
"source_repositories",
"reproduce_commands",
"verify_commands",
@@ -22,8 +29,16 @@
"limitation_notice",
)
+CANONICAL_DIGEST_KEYS = (
+ "claim_artifact",
+ "runtime_receipt",
+ "trace_certificate",
+ "evidence_bundle",
+ "signed_bundle",
+)
+
-def test_portal_read_model_has_all_required_sections() -> None:
+def test_render_claim_includes_all_required_sections() -> None:
bundle = json.loads(
(FIXTURES / "valid_signed_science_claim_bundle.json").read_text(encoding="utf-8")
)
@@ -32,7 +47,7 @@ def test_portal_read_model_has_all_required_sections() -> None:
assert key in read_model, f"missing read_model.{key}"
-def test_limitations_notice_present() -> None:
+def test_render_claim_includes_limitation_notice() -> None:
bundle = json.loads(
(FIXTURES / "valid_signed_science_claim_bundle.json").read_text(encoding="utf-8")
)
@@ -41,16 +56,7 @@ def test_limitations_notice_present() -> None:
assert LIMITATION_NOTICE in read_model["limitations"]
-def test_artifact_hashes_displayed() -> None:
- bundle = json.loads(
- (FIXTURES / "valid_signed_science_claim_bundle.json").read_text(encoding="utf-8")
- )
- read_model = normalize_signed_bundle(bundle)
- assert len(read_model["artifact_hashes"]) >= 1
- assert all("digest" in row and "name" in row for row in read_model["artifact_hashes"])
-
-
-def test_source_repo_and_commit_displayed() -> None:
+def test_render_claim_displays_source_repo_and_source_commit() -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
_copy_schemas(root)
@@ -70,20 +76,20 @@ def test_source_repo_and_commit_displayed() -> None:
assert "labtrust-qc-release-demo" in commits
-def test_status_values_preserved() -> None:
+def test_render_claim_displays_artifact_hashes() -> None:
bundle = json.loads(
- (FIXTURES / "valid_signed_science_claim_bundle.json").read_text(encoding="utf-8")
+ (FIXTURES / "valid_signed_pcs_core_bundle.json").read_text(encoding="utf-8")
)
read_model = normalize_signed_bundle(bundle)
- assert read_model["claim"]["status"] == "RuntimeChecked"
- assert read_model["trace_certificate"]["status"] == "CertificateChecked"
- vr = read_model["verification_result"]
- assert vr is not None
- assert vr["status"] == "ProofChecked"
+ digests = read_model["canonical_digests"]
+ for key in CANONICAL_DIGEST_KEYS:
+ assert key in digests
+ assert digests["claim_artifact"].startswith("sha256:")
+ assert digests["runtime_receipt"].startswith("sha256:")
+ assert digests["trace_certificate"].startswith("sha256:")
+ assert digests["signed_bundle"].startswith("sha256:")
+ assert len(read_model["artifact_hashes"]) >= 5
def _copy_schemas(root: Path) -> None:
- dest = root / "schemas" / "pcs"
- dest.mkdir(parents=True)
- for f in (REPO_ROOT / "schemas" / "pcs").glob("*.json"):
- shutil.copy(f, dest / f.name)
+ copy_pcs_schemas(root)
diff --git a/tests/pcs/test_reject_invalid_bundle.py b/tests/pcs/test_reject_invalid_bundle.py
deleted file mode 100644
index 7ec566f..0000000
--- a/tests/pcs/test_reject_invalid_bundle.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import shutil
-import tempfile
-from pathlib import Path
-
-import pytest
-
-from sm_pipeline.pcs_import.science_claim_bundle_importer import import_signed_bundle
-from sm_pipeline.pcs_validate.validator import BundleValidationError, validate_signed_bundle
-
-FIXTURES = Path(__file__).resolve().parent / "fixtures"
-REPO_ROOT = Path(__file__).resolve().parents[2]
-
-
-def test_invalid_bundle_rejected() -> None:
- import json
-
- bundle = json.loads(
- (FIXTURES / "invalid_missing_signature.json").read_text(encoding="utf-8")
- )
- with tempfile.TemporaryDirectory() as tmp:
- root = Path(tmp)
- _copy_schemas(root)
- with pytest.raises(BundleValidationError):
- validate_signed_bundle(bundle, repo_root=root, strict=True)
-
-
-def test_missing_assumption_rejected() -> None:
- with tempfile.TemporaryDirectory() as tmp:
- root = Path(tmp)
- _copy_schemas(root)
- with pytest.raises(BundleValidationError):
- import_signed_bundle(
- FIXTURES / "missing_assumptions.json",
- repo_root=root,
- write=False,
- )
-
-
-def _copy_schemas(root: Path) -> None:
- dest = root / "schemas" / "pcs"
- dest.mkdir(parents=True)
- for f in (REPO_ROOT / "schemas" / "pcs").glob("*.json"):
- shutil.copy(f, dest / f.name)
diff --git a/uv.lock b/uv.lock
index 4b8e856..fe0c417 100644
--- a/uv.lock
+++ b/uv.lock
@@ -400,6 +400,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 },
]
+[[package]]
+name = "pcs-core"
+version = "0.1.0"
+source = { editable = "../pcs-core/python" }
+dependencies = [
+ { name = "jsonschema" },
+ { name = "referencing" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "jsonschema", specifier = ">=4.23.0" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" },
+ { name = "referencing", specifier = ">=0.35.0,<0.37.0" },
+ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6.0" },
+]
+provides-extras = ["dev"]
+
[[package]]
name = "pluggy"
version = "1.6.0"
@@ -614,16 +632,16 @@ wheels = [
[[package]]
name = "referencing"
-version = "0.37.0"
+version = "0.36.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 }
+sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 },
+ { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 },
]
[[package]]
@@ -786,6 +804,9 @@ dependencies = [
mcp = [
{ name = "mcp" },
]
+pcs = [
+ { name = "pcs-core" },
+]
[package.metadata]
requires-dist = [
@@ -793,13 +814,14 @@ requires-dist = [
{ name = "jsonschema", specifier = ">=4.23" },
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0" },
{ name = "networkx", specifier = ">=3.3" },
+ { name = "pcs-core", marker = "extra == 'pcs'", editable = "../pcs-core/python" },
{ name = "pydantic", specifier = ">=2.8" },
{ name = "python-dotenv", specifier = ">=1.0" },
{ name = "referencing", specifier = ">=0.35" },
{ name = "rich", specifier = ">=13.7" },
{ name = "typer", specifier = ">=0.12" },
]
-provides-extras = ["mcp"]
+provides-extras = ["mcp", "pcs"]
[[package]]
name = "sortedcontainers"
From df2ae28a2db9ab29d346b09a99769fa7b5e0d739 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 17 May 2026 07:59:44 +0000
Subject: [PATCH 4/4] Update mcp requirement from >=1.0 to >=1.27.1 in
/pipeline
Updates the requirements on [mcp](https://github.com/modelcontextprotocol/python-sdk) to permit the latest version.
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.0.0...v1.27.1)
---
updated-dependencies:
- dependency-name: mcp
dependency-version: 1.27.0
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
---
pipeline/pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pipeline/pyproject.toml b/pipeline/pyproject.toml
index 6eb0228..011c8e6 100644
--- a/pipeline/pyproject.toml
+++ b/pipeline/pyproject.toml
@@ -14,7 +14,7 @@ dependencies = [
]
[project.optional-dependencies]
-mcp = ["mcp>=1.0"]
+mcp = ["mcp>=1.27.1"]
# Install when pcs-core is available: uv sync --project pipeline --extra pcs
pcs = ["pcs-core>=0.1.0"]