validibot-shared 0.9.2__tar.gz → 0.10.0__tar.gz

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.
Files changed (29) hide show
  1. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/PKG-INFO +3 -3
  2. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/pyproject.toml +2 -2
  3. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_evidence_manifest.py +112 -0
  4. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/__init__.py +4 -0
  5. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/evidence/__init__.py +4 -0
  6. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/evidence/manifest.py +71 -1
  7. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/.gitignore +0 -0
  8. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/LICENSE +0 -0
  9. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/NOTICE +0 -0
  10. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/README.md +0 -0
  11. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/__init__.py +0 -0
  12. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_energyplus_envelopes.py +0 -0
  13. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_energyplus_models.py +0 -0
  14. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_fmu_envelopes.py +0 -0
  15. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_fmu_models.py +0 -0
  16. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_package_init.py +0 -0
  17. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_shacl_envelopes.py +0 -0
  18. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/tests/test_validations_envelopes.py +0 -0
  19. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/energyplus/__init__.py +0 -0
  20. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/energyplus/envelopes.py +0 -0
  21. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/energyplus/models.py +0 -0
  22. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/fmu/__init__.py +0 -0
  23. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/fmu/envelopes.py +0 -0
  24. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/fmu/models.py +0 -0
  25. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/py.typed +0 -0
  26. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/shacl/__init__.py +0 -0
  27. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/shacl/envelopes.py +0 -0
  28. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/validations/__init__.py +0 -0
  29. {validibot_shared-0.9.2 → validibot_shared-0.10.0}/validibot_shared/validations/envelopes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: validibot-shared
3
- Version: 0.9.2
3
+ Version: 0.10.0
4
4
  Summary: Shared library for data interchange between Validibot and validator containers
5
5
  Project-URL: Homepage, https://validibot.com
6
6
  Project-URL: Documentation, https://docs.validibot.com
@@ -26,8 +26,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
26
  Requires-Python: >=3.10
27
27
  Requires-Dist: pydantic<3.0,>=2.13
28
28
  Provides-Extra: dev
29
- Requires-Dist: pytest==9.0.3; extra == 'dev'
30
- Requires-Dist: ruff==0.15.15; extra == 'dev'
29
+ Requires-Dist: pytest==9.1.1; extra == 'dev'
30
+ Requires-Dist: ruff==0.15.18; extra == 'dev'
31
31
  Description-Content-Type: text/markdown
32
32
 
33
33
  <div align="center">
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "validibot-shared"
7
- version = "0.9.2"
7
+ version = "0.10.0"
8
8
  description = "Shared library for data interchange between Validibot and validator containers"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -37,7 +37,7 @@ classifiers = [
37
37
  dependencies = ["pydantic>=2.13,<3.0"]
38
38
 
39
39
  [project.optional-dependencies]
40
- dev = ["pytest==9.0.3", "ruff==0.15.15"]
40
+ dev = ["pytest==9.1.1", "ruff==0.15.18"]
41
41
 
42
42
  [project.urls]
43
43
  Homepage = "https://validibot.com"
@@ -25,6 +25,8 @@ from pydantic import ValidationError
25
25
 
26
26
  from validibot_shared.evidence import (
27
27
  SCHEMA_VERSION,
28
+ ContractConstant,
29
+ ContractSignalMapping,
28
30
  EvidenceManifest,
29
31
  ManifestPayloadDigests,
30
32
  ManifestRetentionInfo,
@@ -165,6 +167,116 @@ class TestOptionalFields:
165
167
  assert manifest.schema_version == "validibot.evidence.v1"
166
168
 
167
169
 
170
+ # ──────────────────────────────────────────────────────────────────────
171
+ # WorkflowContractSnapshot — constants, signal-mapping defs, definition hash
172
+ # ──────────────────────────────────────────────────────────────────────
173
+ #
174
+ # ADR-2026-06-18. These three fields are additive and optional: a producer
175
+ # predating them (or a workflow with none) leaves them empty, and the schema
176
+ # stays ``validibot.evidence.v1``. Recording constants + signal-mapping
177
+ # *definitions* + the definition hash is the transparency half of the constants
178
+ # feature — it lets EVERY run (not just Pro-signed ones) carry "checked against
179
+ # these constants". Resolved ``s.*`` values are deliberately NOT here (they are
180
+ # submission-derived and retention-gated); only the definition is.
181
+
182
+
183
+ class TestWorkflowContractPrimitives:
184
+ def test_new_fields_default_to_empty(self):
185
+ """A producer predating this change (or a bare workflow) leaves them empty.
186
+
187
+ Back-compat is the whole reason these are additive-optional: an older
188
+ manifest that never set them must still validate.
189
+ """
190
+ snap = WorkflowContractSnapshot(allowed_file_types=["json"])
191
+ assert snap.constants == []
192
+ assert snap.signal_mappings == []
193
+ assert snap.workflow_definition_hash == ""
194
+
195
+ def test_constant_value_is_preserved_verbatim(self):
196
+ """A NUMBER constant's decimal string is stored exactly (no float coercion).
197
+
198
+ The attested precision (``"0.40"``) must survive into the snapshot, or the
199
+ credential's "checked against c.energy_price = 0.40" claim would drift
200
+ from the value actually recorded.
201
+ """
202
+ snap = WorkflowContractSnapshot(
203
+ constants=[
204
+ ContractConstant(
205
+ name="energy_price",
206
+ data_type="NUMBER",
207
+ value="0.40",
208
+ ),
209
+ ContractConstant(
210
+ name="allowed_currencies",
211
+ data_type="LIST",
212
+ value=["EUR", "GBP"],
213
+ ),
214
+ ],
215
+ )
216
+ assert snap.constants[0].value == "0.40"
217
+ assert snap.constants[1].value == ["EUR", "GBP"]
218
+
219
+ def test_signal_mapping_records_definition_not_value(self):
220
+ """The snapshot carries a signal's DEFINITION, never a resolved value.
221
+
222
+ Publishing ``source_path``/``on_missing``/``default`` is safe; a resolved
223
+ ``s.*`` value is submission-derived and must not appear in the
224
+ always-publishable contract.
225
+ """
226
+ snap = WorkflowContractSnapshot(
227
+ signal_mappings=[
228
+ ContractSignalMapping(
229
+ name="reported_total",
230
+ source_path="$.total",
231
+ on_missing="error",
232
+ ),
233
+ ],
234
+ )
235
+ mapping = snap.signal_mappings[0]
236
+ assert mapping.name == "reported_total"
237
+ assert mapping.source_path == "$.total"
238
+ # There is no place to smuggle a resolved value in — the model has no
239
+ # such field.
240
+ assert not hasattr(mapping, "resolved_value")
241
+
242
+ def test_primitives_survive_json_round_trip(self):
243
+ """The fields serialize and re-validate identically (canonical evidence).
244
+
245
+ The manifest is hashed and signed as JSON, so the new fields must
246
+ round-trip through ``model_dump``/``model_validate`` unchanged.
247
+ """
248
+ snap = WorkflowContractSnapshot(
249
+ constants=[ContractConstant(name="p", data_type="NUMBER", value="0.40")],
250
+ signal_mappings=[ContractSignalMapping(name="t", source_path="$.t")],
251
+ workflow_definition_hash="sha256:deadbeef",
252
+ )
253
+ restored = WorkflowContractSnapshot.model_validate(
254
+ snap.model_dump(mode="json"),
255
+ )
256
+ assert restored == snap
257
+
258
+ def test_additive_fields_preserve_v1_schema(self):
259
+ """A manifest carrying constants still reports schema_version v1.
260
+
261
+ Additive optional fields preserve v1 per the module's own policy;
262
+ bumping SCHEMA_VERSION here would be wrong.
263
+ """
264
+ manifest = EvidenceManifest(
265
+ **{
266
+ **_minimal_manifest_kwargs(),
267
+ "workflow_contract": WorkflowContractSnapshot(
268
+ allowed_file_types=["json"],
269
+ constants=[
270
+ ContractConstant(name="p", data_type="NUMBER", value="0.40"),
271
+ ],
272
+ workflow_definition_hash="sha256:abc",
273
+ ),
274
+ },
275
+ )
276
+ assert manifest.schema_version == "validibot.evidence.v1"
277
+ assert manifest.workflow_contract.constants[0].name == "p"
278
+
279
+
168
280
  # ──────────────────────────────────────────────────────────────────────
169
281
  # StepValidatorRecord — semantic_digest can be None for legacy validators
170
282
  # ──────────────────────────────────────────────────────────────────────
@@ -9,6 +9,8 @@ from validibot_shared.evidence import (
9
9
  SCHEMA_VERSION as EVIDENCE_SCHEMA_VERSION,
10
10
  )
11
11
  from validibot_shared.evidence import (
12
+ ContractConstant,
13
+ ContractSignalMapping,
12
14
  EvidenceManifest,
13
15
  ManifestPayloadDigests,
14
16
  ManifestRetentionInfo,
@@ -26,6 +28,8 @@ from validibot_shared.shacl.envelopes import (
26
28
 
27
29
  __all__ = [
28
30
  "EVIDENCE_SCHEMA_VERSION",
31
+ "ContractConstant",
32
+ "ContractSignalMapping",
29
33
  "EnergyPlusSimulationLogs",
30
34
  "EnergyPlusSimulationMetrics",
31
35
  "EnergyPlusSimulationOutputs",
@@ -9,6 +9,8 @@ manifest without pulling in the application stack.
9
9
 
10
10
  from validibot_shared.evidence.manifest import (
11
11
  SCHEMA_VERSION,
12
+ ContractConstant,
13
+ ContractSignalMapping,
12
14
  EvidenceManifest,
13
15
  ManifestPayloadDigests,
14
16
  ManifestRetentionInfo,
@@ -18,6 +20,8 @@ from validibot_shared.evidence.manifest import (
18
20
 
19
21
  __all__ = [
20
22
  "SCHEMA_VERSION",
23
+ "ContractConstant",
24
+ "ContractSignalMapping",
21
25
  "EvidenceManifest",
22
26
  "ManifestPayloadDigests",
23
27
  "ManifestRetentionInfo",
@@ -49,7 +49,7 @@ file in the export bundle.
49
49
 
50
50
  from __future__ import annotations
51
51
 
52
- from typing import Literal
52
+ from typing import Any, Literal
53
53
 
54
54
  from pydantic import BaseModel, ConfigDict, Field
55
55
 
@@ -110,6 +110,44 @@ class StepValidatorRecord(BaseModel):
110
110
  )
111
111
 
112
112
 
113
+ class ContractConstant(BaseModel):
114
+ """A workflow Constant (the ``c.*`` namespace) recorded in the contract.
115
+
116
+ A Constant (ADR-2026-06-18) is a workflow-defined *fixed literal*, so its
117
+ name **and value** are always safe to publish in the contract snapshot —
118
+ unlike a resolved ``s.*`` signal value, which is submission-derived and must
119
+ stay in the retention-gated run record. ``value`` is the stored form: a
120
+ canonical decimal *string* for NUMBER (so ``"0.40"`` keeps its precision), a
121
+ ``str`` for STRING, a ``bool`` for BOOLEAN, and a ``list``/``dict`` for
122
+ LIST/OBJECT.
123
+ """
124
+
125
+ model_config = ConfigDict(frozen=True)
126
+
127
+ name: str
128
+ data_type: str = ""
129
+ value: Any = None
130
+
131
+
132
+ class ContractSignalMapping(BaseModel):
133
+ """A workflow signal-mapping *definition* recorded in the contract.
134
+
135
+ The workflow-defined config for an ``s.<name>`` signal — NOT its resolved
136
+ runtime value. Publishing the definition (how the signal is sourced) is safe
137
+ and lets a verifier see the contract; the *resolved* value is
138
+ submission-derived and belongs only in the retention-gated run record
139
+ (ADR-2026-06-18).
140
+ """
141
+
142
+ model_config = ConfigDict(frozen=True)
143
+
144
+ name: str
145
+ source_path: str = ""
146
+ on_missing: str = ""
147
+ default_value: Any = None
148
+ data_type: str = ""
149
+
150
+
113
151
  class WorkflowContractSnapshot(BaseModel):
114
152
  """Frozen snapshot of the workflow's launch-contract fields.
115
153
 
@@ -139,6 +177,38 @@ class WorkflowContractSnapshot(BaseModel):
139
177
  agent_public_discovery: bool = False
140
178
  agent_access_enabled: bool = False
141
179
 
180
+ # ── Workflow contract primitives (ADR-2026-06-18) ────────────────────────
181
+ # Additive, optional fields — the schema stays ``validibot.evidence.v1``
182
+ # (additive changes preserve v1 by the policy documented at the top of this
183
+ # module; a bump would be wrong here). Producers that predate this change
184
+ # simply leave them empty, and older consumers ignore unknown-to-them fields.
185
+ constants: list[ContractConstant] = Field(
186
+ default_factory=list,
187
+ description=(
188
+ "Workflow Constants (c.* namespace) with their fixed values — the "
189
+ "named thresholds this run was judged against. Empty for workflows "
190
+ "with no constants or for producers predating this field."
191
+ ),
192
+ )
193
+ signal_mappings: list[ContractSignalMapping] = Field(
194
+ default_factory=list,
195
+ description=(
196
+ "Workflow signal-mapping DEFINITIONS (name + source_path + "
197
+ "on_missing + default + type) — never resolved s.* runtime values, "
198
+ "which are submission-derived and retention-gated."
199
+ ),
200
+ )
201
+ workflow_definition_hash: str = Field(
202
+ default="",
203
+ description=(
204
+ "The workflow-definition digest (e.g. 'sha256:...') covering the "
205
+ "semantic contract — constants, signal-mapping definitions, and "
206
+ "per-step validator + effective-ruleset + assertions. Lets a "
207
+ "verifier confirm the run's contract matches a claimed version. "
208
+ "Empty for producers predating this field."
209
+ ),
210
+ )
211
+
142
212
 
143
213
  class ManifestRetentionInfo(BaseModel):
144
214
  """Retention class + applied-redaction summary.