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.
- scpn_studio_platform/__init__.py +43 -0
- scpn_studio_platform/evidence/__init__.py +64 -0
- scpn_studio_platform/evidence/bundle.py +221 -0
- scpn_studio_platform/evidence/claim_boundary.py +188 -0
- scpn_studio_platform/evidence/levels.py +150 -0
- scpn_studio_platform/evidence/numeric.py +193 -0
- scpn_studio_platform/evidence/provenance.py +284 -0
- scpn_studio_platform/identity/__init__.py +19 -0
- scpn_studio_platform/identity/principal.py +113 -0
- scpn_studio_platform/identity/session.py +108 -0
- scpn_studio_platform/jobs/__init__.py +25 -0
- scpn_studio_platform/jobs/budget.py +133 -0
- scpn_studio_platform/jobs/job.py +163 -0
- scpn_studio_platform/manifest/__init__.py +26 -0
- scpn_studio_platform/manifest/components.py +105 -0
- scpn_studio_platform/manifest/digest.py +68 -0
- scpn_studio_platform/manifest/manifest.py +116 -0
- scpn_studio_platform/py.typed +0 -0
- scpn_studio_platform/verbs/__init__.py +38 -0
- scpn_studio_platform/verbs/attributes.py +200 -0
- scpn_studio_platform/verbs/verb.py +122 -0
- scpn_studio_platform-0.1.0.dist-info/METADATA +70 -0
- scpn_studio_platform-0.1.0.dist-info/RECORD +26 -0
- scpn_studio_platform-0.1.0.dist-info/WHEEL +5 -0
- scpn_studio_platform-0.1.0.dist-info/licenses/LICENSE +663 -0
- scpn_studio_platform-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
}
|