scpn-studio-platform 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Commercial license available
3
+ # © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4
+ # © Code 2020–2026 Miroslav Šotek. All rights reserved.
5
+ # ORCID: 0009-0009-3560-0851
6
+ # Contact: www.anulum.li | protoscience@anulum.li
7
+ # SCPN Studio Platform — package root
8
+ """Domain-neutral SDK for the SCPN Studio ecosystem.
9
+
10
+ The package implements the locked SCPN Studio v1 contract: studios build their
11
+ domain verticals on this shared platform, and the federating Hub shell composes
12
+ them. The platform is the open-core foundation; the managed multi-tenant Hub is
13
+ the paid layer.
14
+
15
+ Subpackages (the v1 §5 SDK surface)
16
+ -----------------------------------
17
+ evidence
18
+ The differentiator: the ``studio.*.v1`` provenance-first evidence bundle —
19
+ a PROV-O graph, RO-Crate-profiled, in-toto-signed, graded by an empirical
20
+ evidence level (0-3) and an orthogonal ``evidence_kind``
21
+ (measured / curated / formally-proven), with a seven-state claim-boundary
22
+ lattice, content-addressed cross-studio derivation, and reproducibility
23
+ metadata (regenerated_by + host).
24
+ manifest
25
+ Content-addressed, language-agnostic, deterministic capability manifest —
26
+ how a studio advertises its verbs, evidence types, transport profile, and
27
+ federated UI module.
28
+ verbs
29
+ The verb taxonomy and per-verb attribute contract (safety tier, fidelity,
30
+ timing, side-effect class, formal-proof block).
31
+ identity
32
+ Tenant-aware opaque-identity and session primitives shared by the
33
+ local-first (free) and multi-tenant (paid) transport profiles.
34
+ jobs
35
+ Bounded, fail-closed job and pipeline workers, with the timing/determinism
36
+ contract for real-time verbs.
37
+
38
+ The contract this package implements is recorded internally; public API
39
+ stability follows the era-versioned network contract plus SemVer on this package.
40
+ """
41
+
42
+ __version__ = "0.1.0"
43
+ __all__ = ["__version__"]
@@ -0,0 +1,64 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Commercial license available
3
+ # © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4
+ # © Code 2020–2026 Miroslav Šotek. All rights reserved.
5
+ # ORCID: 0009-0009-3560-0851
6
+ # Contact: www.anulum.li | protoscience@anulum.li
7
+ # SCPN Studio Platform — evidence subpackage public API
8
+ """The provenance-first ``studio.*.v1`` evidence model (the differentiator).
9
+
10
+ This subpackage implements schema B of the locked SCPN Studio v1 contract: the
11
+ evidence bundle a studio emits for every claim, gradeable on two orthogonal axes,
12
+ bounded by a seven-state lattice, numerically provenanced, optionally
13
+ formally-certified, signed, and content-addressed for durable cross-studio
14
+ derivation.
15
+ """
16
+
17
+ from .bundle import CaseResult, EvidenceBundle
18
+ from .claim_boundary import (
19
+ AdmissionDecision,
20
+ BlockedOn,
21
+ ClaimBoundary,
22
+ ClaimStatus,
23
+ FailClosedBoundary,
24
+ )
25
+ from .levels import EvidenceKind, EvidenceLevel, FormalCertificate
26
+ from .numeric import Convergence, ConvergenceStatus, Exactness, NumericProvenance, ParityCheck
27
+ from .provenance import (
28
+ Attestation,
29
+ DerivedEdge,
30
+ DerivedKind,
31
+ PhysicalContract,
32
+ ProvActivity,
33
+ ProvAgent,
34
+ ProvEntity,
35
+ RecomputeEnvironment,
36
+ VerifiedCitation,
37
+ )
38
+
39
+ __all__ = [
40
+ "AdmissionDecision",
41
+ "Attestation",
42
+ "BlockedOn",
43
+ "CaseResult",
44
+ "ClaimBoundary",
45
+ "ClaimStatus",
46
+ "Convergence",
47
+ "ConvergenceStatus",
48
+ "DerivedEdge",
49
+ "DerivedKind",
50
+ "EvidenceBundle",
51
+ "EvidenceKind",
52
+ "EvidenceLevel",
53
+ "Exactness",
54
+ "FailClosedBoundary",
55
+ "FormalCertificate",
56
+ "NumericProvenance",
57
+ "ParityCheck",
58
+ "PhysicalContract",
59
+ "ProvActivity",
60
+ "ProvAgent",
61
+ "ProvEntity",
62
+ "RecomputeEnvironment",
63
+ "VerifiedCitation",
64
+ ]
@@ -0,0 +1,221 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Commercial license available
3
+ # © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4
+ # © Code 2020–2026 Miroslav Šotek. All rights reserved.
5
+ # ORCID: 0009-0009-3560-0851
6
+ # Contact: www.anulum.li | protoscience@anulum.li
7
+ # SCPN Studio Platform — the studio.*.v1 evidence bundle (schema B)
8
+ """The ``studio.*.v1`` evidence bundle — the aggregate result envelope.
9
+
10
+ An :class:`EvidenceBundle` composes the PROV-O graph, the evidence grading (level
11
+ + kind + optional formal certificates), the claim boundary, the numeric
12
+ provenance, optional per-case results, the physical contract, the recompute
13
+ environment, verified citations, the signed attestation, and content-addressed
14
+ derivation edges.
15
+
16
+ The aggregate enforces the cross-field honesty invariants of the v1 contract:
17
+ formally-proven evidence must carry at least one certificate; a bundle is
18
+ renderable as "validated" only when its claim boundary is admissible. The Hub
19
+ relies on :meth:`EvidenceBundle.renders_as_validated` so a bounded, blocked,
20
+ roadmap, or merely-attested number is never presented as validated.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, field
26
+ from typing import Any
27
+
28
+ from .claim_boundary import ClaimBoundary
29
+ from .levels import EvidenceKind, EvidenceLevel, FormalCertificate
30
+ from .numeric import NumericProvenance
31
+ from .provenance import (
32
+ Attestation,
33
+ DerivedEdge,
34
+ PhysicalContract,
35
+ ProvActivity,
36
+ ProvAgent,
37
+ ProvEntity,
38
+ RecomputeEnvironment,
39
+ VerifiedCitation,
40
+ )
41
+
42
+
43
+ @dataclass(frozen=True, slots=True)
44
+ class CaseResult:
45
+ """One row of a per-case coverage map.
46
+
47
+ A battery of probes (operation family x dimension) must not be flattened to a
48
+ single number; each case keeps its own status and error so the coverage map
49
+ survives into the bundle.
50
+
51
+ Parameters
52
+ ----------
53
+ operation_family
54
+ The operation family, e.g. ``"GS-solve"``.
55
+ dimension
56
+ The case dimension/size.
57
+ status
58
+ The per-case claim status value.
59
+ error
60
+ The per-case error, if applicable.
61
+ """
62
+
63
+ operation_family: str
64
+ dimension: int
65
+ status: str
66
+ error: float | None = None
67
+
68
+ def __post_init__(self) -> None:
69
+ """Validate the family name."""
70
+ if not self.operation_family.strip():
71
+ raise ValueError("CaseResult.operation_family must be non-empty")
72
+
73
+ def to_dict(self) -> dict[str, Any]:
74
+ """Return the JSON-serialisable mapping."""
75
+ out: dict[str, Any] = {
76
+ "operation_family": self.operation_family,
77
+ "dimension": self.dimension,
78
+ "status": self.status,
79
+ }
80
+ if self.error is not None:
81
+ out["error"] = self.error
82
+ return out
83
+
84
+
85
+ @dataclass(frozen=True, slots=True)
86
+ class EvidenceBundle:
87
+ """A ``studio.*.v1`` evidence bundle (schema B).
88
+
89
+ Parameters
90
+ ----------
91
+ schema
92
+ The concrete schema name, e.g. ``"studio.transport-run.v1"``.
93
+ entity
94
+ The PROV entity (result + content digest).
95
+ activity
96
+ The PROV activity that produced it.
97
+ agent
98
+ The PROV agent responsible.
99
+ evidence_level
100
+ The empirical completeness level (0-3).
101
+ evidence_kind
102
+ The orthogonal modality (measured / curated / formally-proven).
103
+ claim_boundary
104
+ The claim's boundary on the seven-state lattice.
105
+ numeric_provenance
106
+ How the number was produced and checked, if numeric.
107
+ attestation
108
+ The signed in-toto attestation, if present.
109
+ formal_certificates
110
+ Machine-checked proof certificates; required when ``evidence_kind`` is
111
+ formally-proven, forbidden otherwise.
112
+ cases
113
+ Per-case coverage rows.
114
+ physical_contract
115
+ Units/grid/timestep for a physical result.
116
+ recompute_environment
117
+ External toolchain needed to recompute the result.
118
+ verified_citations
119
+ Verified-at-source citations.
120
+ derived_from
121
+ Content-addressed (and/or coordination) derivation edges.
122
+ ro_crate_profile
123
+ The RO-Crate profile the bundle conforms to.
124
+
125
+ Raises
126
+ ------
127
+ ValueError
128
+ If the schema is malformed, or the evidence-kind/certificate invariant is
129
+ violated.
130
+ """
131
+
132
+ schema: str
133
+ entity: ProvEntity
134
+ activity: ProvActivity
135
+ agent: ProvAgent
136
+ evidence_level: EvidenceLevel
137
+ evidence_kind: EvidenceKind
138
+ claim_boundary: ClaimBoundary
139
+ numeric_provenance: NumericProvenance | None = None
140
+ attestation: Attestation | None = None
141
+ formal_certificates: tuple[FormalCertificate, ...] = field(default_factory=tuple)
142
+ cases: tuple[CaseResult, ...] = field(default_factory=tuple)
143
+ physical_contract: PhysicalContract | None = None
144
+ recompute_environment: RecomputeEnvironment | None = None
145
+ verified_citations: tuple[VerifiedCitation, ...] = field(default_factory=tuple)
146
+ derived_from: tuple[DerivedEdge, ...] = field(default_factory=tuple)
147
+ ro_crate_profile: str = "scpn-studio/0.1"
148
+
149
+ def __post_init__(self) -> None:
150
+ """Validate the schema name and the evidence-kind/certificate invariant."""
151
+ if not self.schema.endswith(".v1"):
152
+ raise ValueError(f"schema must be a versioned 'studio.*.v1' name, got {self.schema!r}")
153
+ proven = self.evidence_kind is EvidenceKind.FORMALLY_PROVEN
154
+ if proven and not self.formal_certificates:
155
+ raise ValueError("evidence_kind formally-proven requires at least one formal_certificate")
156
+ if not proven and self.formal_certificates:
157
+ raise ValueError("formal_certificate is only valid when evidence_kind is formally-proven")
158
+
159
+ @property
160
+ def renders_as_validated(self) -> bool:
161
+ """Whether the Hub may present this bundle as a validated claim.
162
+
163
+ Returns
164
+ -------
165
+ bool
166
+ ``True`` only when the claim boundary is admissible. A bounded,
167
+ blocked, roadmap, or merely-attested bundle returns ``False`` — the
168
+ Hub renders its boundary verbatim instead.
169
+ """
170
+ return self.claim_boundary.is_admissible
171
+
172
+ def proof_voided_by(self, live_subject_digest: str) -> bool:
173
+ """Whether a formal proof is voided by drift of its subject.
174
+
175
+ Parameters
176
+ ----------
177
+ live_subject_digest
178
+ SHA-256 of the subject as it currently is.
179
+
180
+ Returns
181
+ -------
182
+ bool
183
+ ``True`` if the bundle is formally-proven and at least one certificate
184
+ no longer covers the live subject; ``False`` otherwise (including for
185
+ non-proven bundles).
186
+ """
187
+ if self.evidence_kind is not EvidenceKind.FORMALLY_PROVEN:
188
+ return False
189
+ return any(not cert.covers(live_subject_digest) for cert in self.formal_certificates)
190
+
191
+ def to_dict(self) -> dict[str, Any]:
192
+ """Return the full JSON-serialisable schema-B mapping."""
193
+ out: dict[str, Any] = {
194
+ "schema": self.schema,
195
+ "ro_crate_profile": self.ro_crate_profile,
196
+ "prov": {
197
+ "entity": self.entity.to_dict(),
198
+ "activity": self.activity.to_dict(),
199
+ "agent": self.agent.to_dict(),
200
+ },
201
+ "scpn_evidence_level": int(self.evidence_level),
202
+ "evidence_kind": self.evidence_kind.value,
203
+ "claim_boundary": self.claim_boundary.to_dict(),
204
+ }
205
+ if self.numeric_provenance is not None:
206
+ out["numeric_provenance"] = self.numeric_provenance.to_dict()
207
+ if self.formal_certificates:
208
+ out["formal_certificate"] = [c.to_dict() for c in self.formal_certificates]
209
+ if self.cases:
210
+ out["cases"] = [c.to_dict() for c in self.cases]
211
+ if self.physical_contract is not None:
212
+ out["physical_contract"] = self.physical_contract.to_dict()
213
+ if self.recompute_environment is not None:
214
+ out["recompute_environment"] = self.recompute_environment.to_dict()
215
+ if self.verified_citations:
216
+ out["verified_citations"] = [c.to_dict() for c in self.verified_citations]
217
+ if self.attestation is not None:
218
+ out["attestation"] = self.attestation.to_dict()
219
+ if self.derived_from:
220
+ out["derived_from"] = [d.to_dict() for d in self.derived_from]
221
+ return out
@@ -0,0 +1,188 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Commercial license available
3
+ # © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4
+ # © Code 2020–2026 Miroslav Šotek. All rights reserved.
5
+ # ORCID: 0009-0009-3560-0851
6
+ # Contact: www.anulum.li | protoscience@anulum.li
7
+ # SCPN Studio Platform — claim-boundary lattice (honesty-as-product)
8
+ """The seven-state claim-boundary lattice.
9
+
10
+ A number can be fully tool-backed yet still not admissible as a facility-grade
11
+ claim. The lattice makes that distinction first-class so the Hub renders a claim's
12
+ boundary verbatim and never upgrades a bounded, blocked, roadmap, or gated number
13
+ to "validated". This is the honesty-as-product surface.
14
+
15
+ The seven states were contributed by the fleet teardown: the empirical four
16
+ (reference-validated, bounded-model, validation-gap, external-dependency-blocked)
17
+ plus ``bounded-support`` (fail-closed by design — not a defect),
18
+ ``roadmap`` (declared not-yet-built), and ``toolchain-gated`` (blocked on a
19
+ checker/tool absent on this host). Only :data:`ClaimStatus.REFERENCE_VALIDATED`
20
+ is admissible without qualification.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, field
26
+ from enum import StrEnum
27
+ from typing import Any
28
+
29
+
30
+ class ClaimStatus(StrEnum):
31
+ """The boundary state of a claim.
32
+
33
+ Attributes
34
+ ----------
35
+ REFERENCE_VALIDATED
36
+ Established against a trusted reference; admissible.
37
+ BOUNDED_MODEL
38
+ A reduced/approximate model within stated bounds; not facility-grade.
39
+ BOUNDED_SUPPORT
40
+ Fail-closed by design outside a supported domain (NOT a defect).
41
+ VALIDATION_GAP
42
+ Validation was attempted and is incomplete or failed.
43
+ EXTERNAL_DEPENDENCY_BLOCKED
44
+ Cannot be established because a declared external dependency is absent.
45
+ ROADMAP
46
+ Declared as planned but not yet built.
47
+ TOOLCHAIN_GATED
48
+ Blocked on a checker/tool unavailable on this host.
49
+ """
50
+
51
+ REFERENCE_VALIDATED = "reference-validated"
52
+ BOUNDED_MODEL = "bounded-model"
53
+ BOUNDED_SUPPORT = "bounded-support"
54
+ VALIDATION_GAP = "validation-gap"
55
+ EXTERNAL_DEPENDENCY_BLOCKED = "external-dependency-blocked"
56
+ ROADMAP = "roadmap"
57
+ TOOLCHAIN_GATED = "toolchain-gated"
58
+
59
+ @property
60
+ def is_admissible(self) -> bool:
61
+ """Whether a claim in this state may be presented as validated.
62
+
63
+ Returns
64
+ -------
65
+ bool
66
+ ``True`` only for :data:`REFERENCE_VALIDATED`. Every other state is a
67
+ boundary the Hub must render explicitly rather than upgrade.
68
+ """
69
+ return self is ClaimStatus.REFERENCE_VALIDATED
70
+
71
+
72
+ class AdmissionDecision(StrEnum):
73
+ """Whether a claim was admitted by the runtime admission gate."""
74
+
75
+ ADMITTED = "admitted"
76
+ REJECTED = "rejected"
77
+
78
+
79
+ @dataclass(frozen=True, slots=True)
80
+ class BlockedOn:
81
+ """A single declared dependency a claim is blocked on.
82
+
83
+ Parameters
84
+ ----------
85
+ dependency
86
+ Name of the missing dependency, e.g. ``"TORAX-ref"``.
87
+ kind
88
+ Category of dependency, e.g. ``"dataset"``, ``"toolchain"``, ``"hardware"``.
89
+ """
90
+
91
+ dependency: str
92
+ kind: str
93
+
94
+ def __post_init__(self) -> None:
95
+ """Validate non-empty fields."""
96
+ if not self.dependency.strip() or not self.kind.strip():
97
+ raise ValueError("BlockedOn.dependency and .kind must be non-empty")
98
+
99
+ def to_dict(self) -> dict[str, str]:
100
+ """Return the JSON-serialisable mapping."""
101
+ return {"dependency": self.dependency, "kind": self.kind}
102
+
103
+
104
+ @dataclass(frozen=True, slots=True)
105
+ class FailClosedBoundary:
106
+ """A fail-closed-by-design support boundary for one operation family.
107
+
108
+ Parameters
109
+ ----------
110
+ family
111
+ The operation family, e.g. ``"native-det"``.
112
+ first_unsupported_size
113
+ The smallest problem size at which the family deliberately fail-closes.
114
+ """
115
+
116
+ family: str
117
+ first_unsupported_size: int
118
+
119
+ def __post_init__(self) -> None:
120
+ """Validate the family name and the size bound."""
121
+ if not self.family.strip():
122
+ raise ValueError("FailClosedBoundary.family must be non-empty")
123
+ if self.first_unsupported_size < 1:
124
+ raise ValueError("FailClosedBoundary.first_unsupported_size must be >= 1")
125
+
126
+ def to_dict(self) -> dict[str, Any]:
127
+ """Return the JSON-serialisable mapping."""
128
+ return {"family": self.family, "first_unsupported_size": self.first_unsupported_size}
129
+
130
+
131
+ @dataclass(frozen=True, slots=True)
132
+ class ClaimBoundary:
133
+ """The boundary of a claim: its status plus the qualifiers that explain it.
134
+
135
+ Parameters
136
+ ----------
137
+ status
138
+ The :class:`ClaimStatus` lattice state.
139
+ admission
140
+ The runtime admission decision for the claim.
141
+ certificate_ref
142
+ Optional reference to a runtime safety certificate that admitted the claim.
143
+ fail_closed_boundaries
144
+ Support boundaries when ``status`` is :data:`ClaimStatus.BOUNDED_SUPPORT`.
145
+ blocked_on
146
+ Declared dependencies when ``status`` is
147
+ :data:`ClaimStatus.EXTERNAL_DEPENDENCY_BLOCKED` or
148
+ :data:`ClaimStatus.TOOLCHAIN_GATED`.
149
+
150
+ Raises
151
+ ------
152
+ ValueError
153
+ If the qualifiers are inconsistent with the status (e.g. a blocked status
154
+ without any ``blocked_on`` entry, or ``blocked_on`` on an unrelated status).
155
+ """
156
+
157
+ status: ClaimStatus
158
+ admission: AdmissionDecision
159
+ certificate_ref: str | None = None
160
+ fail_closed_boundaries: tuple[FailClosedBoundary, ...] = field(default_factory=tuple)
161
+ blocked_on: tuple[BlockedOn, ...] = field(default_factory=tuple)
162
+
163
+ _BLOCKED_STATES = (ClaimStatus.EXTERNAL_DEPENDENCY_BLOCKED, ClaimStatus.TOOLCHAIN_GATED)
164
+
165
+ def __post_init__(self) -> None:
166
+ """Validate qualifier/status consistency."""
167
+ if self.status in self._BLOCKED_STATES and not self.blocked_on:
168
+ raise ValueError(f"status {self.status} requires at least one blocked_on entry")
169
+ if self.blocked_on and self.status not in self._BLOCKED_STATES:
170
+ raise ValueError(f"blocked_on is only valid for {self._BLOCKED_STATES}, not {self.status}")
171
+ if self.fail_closed_boundaries and self.status is not ClaimStatus.BOUNDED_SUPPORT:
172
+ raise ValueError("fail_closed_boundaries is only valid for status bounded-support")
173
+
174
+ @property
175
+ def is_admissible(self) -> bool:
176
+ """Whether the claim may be presented as validated (status AND admission)."""
177
+ return self.status.is_admissible and self.admission is AdmissionDecision.ADMITTED
178
+
179
+ def to_dict(self) -> dict[str, Any]:
180
+ """Return the JSON-serialisable mapping for this boundary."""
181
+ out: dict[str, Any] = {"status": self.status.value, "admission": self.admission.value}
182
+ if self.certificate_ref is not None:
183
+ out["certificate"] = self.certificate_ref
184
+ if self.fail_closed_boundaries:
185
+ out["fail_closed_boundaries"] = [b.to_dict() for b in self.fail_closed_boundaries]
186
+ if self.blocked_on:
187
+ out["blocked_on"] = [b.to_dict() for b in self.blocked_on]
188
+ return out
@@ -0,0 +1,150 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Commercial license available
3
+ # © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4
+ # © Code 2020–2026 Miroslav Šotek. All rights reserved.
5
+ # ORCID: 0009-0009-3560-0851
6
+ # Contact: www.anulum.li | protoscience@anulum.li
7
+ # SCPN Studio Platform — evidence grading: empirical level + orthogonal modality
8
+ """Evidence grading along two orthogonal axes.
9
+
10
+ The SCPN Studio v1 contract grades evidence on two independent axes, kept
11
+ orthogonal so that a machine-checked proof is never rendered as if it were a
12
+ high-confidence measurement:
13
+
14
+ - :class:`EvidenceLevel` — the *empirical* completeness ladder (0-3), an
15
+ SLSA-analogue measuring how thoroughly a claim has been established by
16
+ observation/curation.
17
+ - :class:`EvidenceKind` — the *modality* of the evidence (measured, curated,
18
+ formally-proven). A formal proof is a different kind of evidence, not a higher
19
+ rung of the empirical ladder.
20
+
21
+ When :class:`EvidenceKind` is ``FORMALLY_PROVEN`` the bundle carries one or more
22
+ :class:`FormalCertificate` records. Each binds the proof artifact
23
+ (``proof_digest``) *and* the subject it was proven against (``subject_digest``),
24
+ so the Hub voids the proof the moment the live subject drifts from the proven one.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass
30
+ from enum import IntEnum, StrEnum
31
+ from typing import Any
32
+
33
+
34
+ class EvidenceLevel(IntEnum):
35
+ """Empirical completeness of a claim (orthogonal to :class:`EvidenceKind`).
36
+
37
+ The ladder measures how thoroughly a claim is established empirically; it does
38
+ not encode whether the evidence is measured, curated, or proven.
39
+
40
+ Attributes
41
+ ----------
42
+ EXISTS
43
+ The artifact exists but is uncharacterised (level 0).
44
+ TAXONOMY
45
+ Classified/typed but not scientifically curated (level 1).
46
+ SCIENTIFICALLY_CURATED
47
+ Curated against the literature with verified provenance (level 2).
48
+ ENGINEERING_VERIFIED
49
+ Reproduced end-to-end against a reference or golden trace (level 3).
50
+ """
51
+
52
+ EXISTS = 0
53
+ TAXONOMY = 1
54
+ SCIENTIFICALLY_CURATED = 2
55
+ ENGINEERING_VERIFIED = 3
56
+
57
+
58
+ class EvidenceKind(StrEnum):
59
+ """Modality of evidence — the *kind*, orthogonal to :class:`EvidenceLevel`.
60
+
61
+ Attributes
62
+ ----------
63
+ MEASURED
64
+ Established by execution/observation against a reference or analytic value.
65
+ CURATED
66
+ Established by literature curation with verified-at-source citations.
67
+ FORMALLY_PROVEN
68
+ Established by a machine-checked formal proof; requires at least one
69
+ :class:`FormalCertificate`.
70
+ """
71
+
72
+ MEASURED = "measured"
73
+ CURATED = "curated"
74
+ FORMALLY_PROVEN = "formally-proven"
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class FormalCertificate:
79
+ """A single machine-checked proof certificate.
80
+
81
+ One logical theorem may carry several certificates from independent checkers
82
+ (for example a SymbiYosys RTL proof and a Lean re-proof of the same
83
+ obligation); the bundle holds them as a list so parallel independent
84
+ attestations of one claim are representable.
85
+
86
+ Parameters
87
+ ----------
88
+ checker
89
+ The proof engine, e.g. ``"lean"``, ``"z3"``, ``"symbiyosys"``,
90
+ ``"prism"``, ``"tla+"``.
91
+ checker_version
92
+ Exact version string of the engine, for reproducibility.
93
+ theorem_id
94
+ Identifier of the proven property/theorem.
95
+ proof_digest
96
+ SHA-256 of the proof artifact itself.
97
+ subject_digest
98
+ SHA-256 of *what was proven* (e.g. the RTL/topology). The Hub voids the
99
+ certificate when the live subject's digest differs from this value — the
100
+ load-bearing field for safety-relevant proofs.
101
+ non_vacuous
102
+ Whether the proof was checked to be non-vacuous (a vacuous cover witness
103
+ is worthless). Defaults to ``False`` until explicitly established.
104
+
105
+ Raises
106
+ ------
107
+ ValueError
108
+ If any required string field is empty.
109
+ """
110
+
111
+ checker: str
112
+ checker_version: str
113
+ theorem_id: str
114
+ proof_digest: str
115
+ subject_digest: str
116
+ non_vacuous: bool = False
117
+
118
+ def __post_init__(self) -> None:
119
+ """Validate that the identifying fields are non-empty."""
120
+ for name in ("checker", "checker_version", "theorem_id", "proof_digest", "subject_digest"):
121
+ if not getattr(self, name).strip():
122
+ raise ValueError(f"FormalCertificate.{name} must be a non-empty string")
123
+
124
+ def covers(self, live_subject_digest: str) -> bool:
125
+ """Return whether this certificate is valid for a live subject.
126
+
127
+ Parameters
128
+ ----------
129
+ live_subject_digest
130
+ SHA-256 of the subject as it currently is.
131
+
132
+ Returns
133
+ -------
134
+ bool
135
+ ``True`` iff the live subject matches the proven subject. A ``False``
136
+ result means the proof is void and the Hub must not present the claim
137
+ as proven.
138
+ """
139
+ return live_subject_digest == self.subject_digest
140
+
141
+ def to_dict(self) -> dict[str, Any]:
142
+ """Return the JSON-serialisable mapping for this certificate."""
143
+ return {
144
+ "checker": self.checker,
145
+ "checker_version": self.checker_version,
146
+ "theorem_id": self.theorem_id,
147
+ "proof_digest": self.proof_digest,
148
+ "subject_digest": self.subject_digest,
149
+ "non_vacuous": self.non_vacuous,
150
+ }