validibot-shared 0.4.4__tar.gz → 0.5.1__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.
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/PKG-INFO +4 -4
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/pyproject.toml +3 -3
- validibot_shared-0.5.1/tests/test_evidence_manifest.py +160 -0
- validibot_shared-0.5.1/validibot_shared/__init__.py +32 -0
- validibot_shared-0.5.1/validibot_shared/evidence/__init__.py +26 -0
- validibot_shared-0.5.1/validibot_shared/evidence/manifest.py +237 -0
- validibot_shared-0.4.4/validibot_shared/__init__.py +0 -16
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/.gitignore +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/LICENSE +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/NOTICE +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/README.md +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/__init__.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/test_energyplus_envelopes.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/test_energyplus_models.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/test_fmu_envelopes.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/test_fmu_models.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/test_package_init.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/tests/test_validations_envelopes.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/energyplus/__init__.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/energyplus/envelopes.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/energyplus/models.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/fmu/__init__.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/fmu/envelopes.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/fmu/models.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/py.typed +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/validibot_shared/validations/__init__.py +0 -0
- {validibot_shared-0.4.4 → validibot_shared-0.5.1}/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.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -24,10 +24,10 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
24
24
|
Classifier: Topic :: Scientific/Engineering
|
|
25
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
26
|
Requires-Python: >=3.10
|
|
27
|
-
Requires-Dist: pydantic
|
|
27
|
+
Requires-Dist: pydantic<3.0,>=2.13
|
|
28
28
|
Provides-Extra: dev
|
|
29
|
-
Requires-Dist: pytest==9.0.
|
|
30
|
-
Requires-Dist: ruff==0.
|
|
29
|
+
Requires-Dist: pytest==9.0.3; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff==0.15.12; 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.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "Shared library for data interchange between Validibot and validator containers"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -32,10 +32,10 @@ classifiers = [
|
|
|
32
32
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
33
|
"Topic :: Scientific/Engineering",
|
|
34
34
|
]
|
|
35
|
-
dependencies = ["pydantic
|
|
35
|
+
dependencies = ["pydantic>=2.13,<3.0"]
|
|
36
36
|
|
|
37
37
|
[project.optional-dependencies]
|
|
38
|
-
dev = ["pytest==9.0.
|
|
38
|
+
dev = ["pytest==9.0.3", "ruff==0.15.12"]
|
|
39
39
|
|
|
40
40
|
[project.urls]
|
|
41
41
|
Homepage = "https://validibot.com"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Tests for ``validibot_shared.evidence`` schema (validibot.evidence.v1).
|
|
2
|
+
|
|
3
|
+
ADR-2026-04-27 Phase 4 Session A: pin the schema's contract at the
|
|
4
|
+
shared-package boundary. The Validibot Django app is one consumer;
|
|
5
|
+
external verifiers (validibot-pro, third-party tools) are others.
|
|
6
|
+
These tests verify the shape stays stable across releases.
|
|
7
|
+
|
|
8
|
+
What this file covers
|
|
9
|
+
=====================
|
|
10
|
+
|
|
11
|
+
1. ``schema_version`` is pinned to ``validibot.evidence.v1`` and
|
|
12
|
+
rejected if other values are passed (Literal type-narrows).
|
|
13
|
+
2. The model rejects unknown top-level keys (``extra="forbid"``).
|
|
14
|
+
3. Frozen Pydantic models — instances cannot be mutated after
|
|
15
|
+
construction, which is the property the producer's hash
|
|
16
|
+
computation depends on.
|
|
17
|
+
4. Optional fields default sensibly so producers can build a
|
|
18
|
+
minimal Session-A manifest without populating Session-B fields.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
from pydantic import ValidationError
|
|
25
|
+
|
|
26
|
+
from validibot_shared.evidence import (
|
|
27
|
+
SCHEMA_VERSION,
|
|
28
|
+
EvidenceManifest,
|
|
29
|
+
ManifestPayloadDigests,
|
|
30
|
+
ManifestRetentionInfo,
|
|
31
|
+
StepValidatorRecord,
|
|
32
|
+
WorkflowContractSnapshot,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _minimal_manifest_kwargs():
|
|
37
|
+
"""Build the smallest set of kwargs that produces a valid manifest."""
|
|
38
|
+
return {
|
|
39
|
+
"run_id": "abc-123",
|
|
40
|
+
"workflow_id": 1,
|
|
41
|
+
"workflow_slug": "compliance",
|
|
42
|
+
"workflow_version": "1.0",
|
|
43
|
+
"org_id": 42,
|
|
44
|
+
"executed_at": "2026-05-02T12:00:00+00:00",
|
|
45
|
+
"status": "SUCCEEDED",
|
|
46
|
+
"workflow_contract": WorkflowContractSnapshot(
|
|
47
|
+
allowed_file_types=["json"],
|
|
48
|
+
),
|
|
49
|
+
"retention": ManifestRetentionInfo(retention_class="STORE_30_DAYS"),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
54
|
+
# Schema version contract
|
|
55
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestSchemaVersion:
|
|
59
|
+
def test_default_schema_version_is_v1(self):
|
|
60
|
+
manifest = EvidenceManifest(**_minimal_manifest_kwargs())
|
|
61
|
+
assert manifest.schema_version == "validibot.evidence.v1"
|
|
62
|
+
|
|
63
|
+
def test_schema_version_constant_matches(self):
|
|
64
|
+
assert SCHEMA_VERSION == "validibot.evidence.v1"
|
|
65
|
+
|
|
66
|
+
def test_rejects_other_schema_versions(self):
|
|
67
|
+
"""Literal narrowing — passing a non-v1 string fails validation."""
|
|
68
|
+
with pytest.raises(ValidationError):
|
|
69
|
+
EvidenceManifest(
|
|
70
|
+
**_minimal_manifest_kwargs(),
|
|
71
|
+
schema_version="validibot.evidence.v2",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
76
|
+
# Strict shape (extra="forbid") and frozen instances
|
|
77
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestStrictShape:
|
|
81
|
+
def test_unknown_top_level_keys_rejected(self):
|
|
82
|
+
"""Producers can't accidentally smuggle in undocumented data."""
|
|
83
|
+
with pytest.raises(ValidationError):
|
|
84
|
+
EvidenceManifest(
|
|
85
|
+
**_minimal_manifest_kwargs(),
|
|
86
|
+
extra_undeclared_field="oops",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def test_manifest_is_frozen(self):
|
|
90
|
+
"""Instances cannot be mutated post-construction.
|
|
91
|
+
|
|
92
|
+
The hash producers compute over canonical JSON depends on
|
|
93
|
+
the fact that you can't tweak a field after the model is
|
|
94
|
+
built. ``frozen=True`` enforces that at the Pydantic level.
|
|
95
|
+
"""
|
|
96
|
+
manifest = EvidenceManifest(**_minimal_manifest_kwargs())
|
|
97
|
+
with pytest.raises(ValidationError):
|
|
98
|
+
manifest.run_id = "tampered"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
102
|
+
# Optional fields and defaults (Session A vs Session B distinction)
|
|
103
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TestOptionalFields:
|
|
107
|
+
def test_payload_digests_defaults_to_all_none(self):
|
|
108
|
+
"""Session A: producers leave digests empty."""
|
|
109
|
+
manifest = EvidenceManifest(**_minimal_manifest_kwargs())
|
|
110
|
+
assert manifest.payload_digests.input_sha256 is None
|
|
111
|
+
assert manifest.payload_digests.output_envelope_sha256 is None
|
|
112
|
+
|
|
113
|
+
def test_session_b_can_populate_digests(self):
|
|
114
|
+
"""Session B's redaction path drops in real hashes."""
|
|
115
|
+
manifest = EvidenceManifest(
|
|
116
|
+
**_minimal_manifest_kwargs(),
|
|
117
|
+
payload_digests=ManifestPayloadDigests(
|
|
118
|
+
input_sha256="a" * 64,
|
|
119
|
+
output_envelope_sha256="b" * 64,
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
assert manifest.payload_digests.input_sha256 == "a" * 64
|
|
123
|
+
|
|
124
|
+
def test_input_schema_optional(self):
|
|
125
|
+
"""Workflows without structured input contract leave it None."""
|
|
126
|
+
manifest = EvidenceManifest(**_minimal_manifest_kwargs())
|
|
127
|
+
assert manifest.input_schema is None
|
|
128
|
+
|
|
129
|
+
def test_steps_default_to_empty_list(self):
|
|
130
|
+
"""A workflow with no steps still produces a valid manifest."""
|
|
131
|
+
manifest = EvidenceManifest(**_minimal_manifest_kwargs())
|
|
132
|
+
assert manifest.steps == []
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
136
|
+
# StepValidatorRecord — semantic_digest can be None for legacy validators
|
|
137
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestStepValidatorRecord:
|
|
141
|
+
def test_semantic_digest_optional(self):
|
|
142
|
+
"""Custom validators with no digest -> None in the manifest."""
|
|
143
|
+
record = StepValidatorRecord(
|
|
144
|
+
step_id=1,
|
|
145
|
+
step_order=0,
|
|
146
|
+
validator_slug="custom-validator",
|
|
147
|
+
validator_version="1.0",
|
|
148
|
+
)
|
|
149
|
+
assert record.validator_semantic_digest is None
|
|
150
|
+
|
|
151
|
+
def test_records_are_frozen(self):
|
|
152
|
+
"""Step records cannot be mutated after construction."""
|
|
153
|
+
record = StepValidatorRecord(
|
|
154
|
+
step_id=1,
|
|
155
|
+
step_order=0,
|
|
156
|
+
validator_slug="x",
|
|
157
|
+
validator_version="1.0",
|
|
158
|
+
)
|
|
159
|
+
with pytest.raises(ValidationError):
|
|
160
|
+
record.validator_slug = "tampered"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Shared domain models and utilities for Validibot."""
|
|
2
|
+
|
|
3
|
+
from validibot_shared.energyplus.models import (
|
|
4
|
+
EnergyPlusSimulationLogs,
|
|
5
|
+
EnergyPlusSimulationMetrics,
|
|
6
|
+
EnergyPlusSimulationOutputs,
|
|
7
|
+
)
|
|
8
|
+
from validibot_shared.evidence import (
|
|
9
|
+
SCHEMA_VERSION as EVIDENCE_SCHEMA_VERSION,
|
|
10
|
+
)
|
|
11
|
+
from validibot_shared.evidence import (
|
|
12
|
+
EvidenceManifest,
|
|
13
|
+
ManifestPayloadDigests,
|
|
14
|
+
ManifestRetentionInfo,
|
|
15
|
+
StepValidatorRecord,
|
|
16
|
+
WorkflowContractSnapshot,
|
|
17
|
+
)
|
|
18
|
+
from validibot_shared.fmu.models import FMUProbeResult, FMUVariableMeta
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"EVIDENCE_SCHEMA_VERSION",
|
|
22
|
+
"EnergyPlusSimulationLogs",
|
|
23
|
+
"EnergyPlusSimulationMetrics",
|
|
24
|
+
"EnergyPlusSimulationOutputs",
|
|
25
|
+
"EvidenceManifest",
|
|
26
|
+
"FMUProbeResult",
|
|
27
|
+
"FMUVariableMeta",
|
|
28
|
+
"ManifestPayloadDigests",
|
|
29
|
+
"ManifestRetentionInfo",
|
|
30
|
+
"StepValidatorRecord",
|
|
31
|
+
"WorkflowContractSnapshot",
|
|
32
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Evidence-manifest schemas (``validibot.evidence.vN``).
|
|
2
|
+
|
|
3
|
+
Public API for both the Validibot Django app (which generates
|
|
4
|
+
manifests when validation runs complete) and the Pro / external
|
|
5
|
+
verifiers (which sign / verify them). The schemas are intentionally
|
|
6
|
+
storage-agnostic and Django-free so any consumer can validate a
|
|
7
|
+
manifest without pulling in the application stack.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from validibot_shared.evidence.manifest import (
|
|
11
|
+
SCHEMA_VERSION,
|
|
12
|
+
EvidenceManifest,
|
|
13
|
+
ManifestPayloadDigests,
|
|
14
|
+
ManifestRetentionInfo,
|
|
15
|
+
StepValidatorRecord,
|
|
16
|
+
WorkflowContractSnapshot,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"SCHEMA_VERSION",
|
|
21
|
+
"EvidenceManifest",
|
|
22
|
+
"ManifestPayloadDigests",
|
|
23
|
+
"ManifestRetentionInfo",
|
|
24
|
+
"StepValidatorRecord",
|
|
25
|
+
"WorkflowContractSnapshot",
|
|
26
|
+
]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Pydantic schema for validibot evidence manifests (``validibot.evidence.v1``).
|
|
2
|
+
|
|
3
|
+
ADR-2026-04-27 (Validibot Trust ADR), Phase 4: every completed
|
|
4
|
+
validation run produces a manifest documenting "these are the rules
|
|
5
|
+
and inputs run X was operating under." The manifest is the data
|
|
6
|
+
structure that makes evidence bundles, signed credentials, and
|
|
7
|
+
external verification possible.
|
|
8
|
+
|
|
9
|
+
Why this lives in ``validibot-shared``
|
|
10
|
+
======================================
|
|
11
|
+
|
|
12
|
+
The manifest is the contract between *manifest producers* (the
|
|
13
|
+
Validibot Django app) and *verifiers* (the validibot-pro signing /
|
|
14
|
+
verification flow, or any external party that receives an exported
|
|
15
|
+
bundle). Pinning the schema in the shared package:
|
|
16
|
+
|
|
17
|
+
1. Lets external verifiers depend on a single PyPI package without
|
|
18
|
+
pulling in the Django stack.
|
|
19
|
+
2. Keeps producer and verifier locked to the same version of the
|
|
20
|
+
shape — additive changes preserve v1 by policy; breaking changes
|
|
21
|
+
require a v2 module beside this one.
|
|
22
|
+
|
|
23
|
+
Schema versioning policy
|
|
24
|
+
========================
|
|
25
|
+
|
|
26
|
+
The string ``validibot.evidence.v1`` in :data:`SCHEMA_VERSION` is the
|
|
27
|
+
*compatibility contract*. Additive changes (new optional fields,
|
|
28
|
+
loosened validators) preserve v1. Removing or renaming fields, or
|
|
29
|
+
tightening a validator in a way that rejects previously-valid input,
|
|
30
|
+
requires a v2 module placed alongside this one (``manifest_v2.py``).
|
|
31
|
+
|
|
32
|
+
What's in the schema (Phase 4 phasing)
|
|
33
|
+
======================================
|
|
34
|
+
|
|
35
|
+
Session A (this module): identity, workflow contract snapshot,
|
|
36
|
+
validator metadata per step, input contract, retention class, and
|
|
37
|
+
schema slots for fields Sessions B + C will populate.
|
|
38
|
+
|
|
39
|
+
Session B (deferred): the ``payload_digests`` slot gets populated
|
|
40
|
+
with input + output hashes; ``retention.redactions_applied`` lists
|
|
41
|
+
fields stripped under retention policy. The schema shape itself
|
|
42
|
+
does not change, only the values landing in optional fields.
|
|
43
|
+
|
|
44
|
+
Session C (deferred): credentials sign the canonical-JSON bytes of
|
|
45
|
+
this manifest externally. The signature does NOT live inside the
|
|
46
|
+
manifest — verifiers receive the manifest + a separate signature
|
|
47
|
+
file in the export bundle.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from __future__ import annotations
|
|
51
|
+
|
|
52
|
+
from typing import Literal
|
|
53
|
+
|
|
54
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
55
|
+
|
|
56
|
+
# The string consumers parse to know what manifest shape to expect.
|
|
57
|
+
# Bump only on breaking changes; additive changes stay v1.
|
|
58
|
+
SCHEMA_VERSION = "validibot.evidence.v1"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class StepValidatorRecord(BaseModel):
|
|
62
|
+
"""Per-step validator identity at run time.
|
|
63
|
+
|
|
64
|
+
Pinned values: a step's validator is referenced by foreign key
|
|
65
|
+
on the application side, but the *meaning* of that reference
|
|
66
|
+
depends on the validator's ``semantic_digest`` at run time. We
|
|
67
|
+
snapshot the digest into the manifest so future verifiers can
|
|
68
|
+
prove "the validator behind this step had digest D when this run
|
|
69
|
+
executed."
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
model_config = ConfigDict(frozen=True)
|
|
73
|
+
|
|
74
|
+
step_id: int = Field(
|
|
75
|
+
description="Producer-side WorkflowStep primary key at run time."
|
|
76
|
+
)
|
|
77
|
+
step_order: int = Field(description="Execution position (lower = earlier).")
|
|
78
|
+
validator_slug: str = Field(description="Validator's stable slug.")
|
|
79
|
+
validator_version: str = Field(description="Validator's version string.")
|
|
80
|
+
# Optional because custom org validators have empty digests by
|
|
81
|
+
# design (no ValidatorConfig to hash). Producers' auditors flag
|
|
82
|
+
# this as a coverage gap.
|
|
83
|
+
validator_semantic_digest: str | None = Field(
|
|
84
|
+
default=None,
|
|
85
|
+
description=(
|
|
86
|
+
"SHA-256 hex of the validator's semantic config. None for "
|
|
87
|
+
"custom validators that aren't synced from a "
|
|
88
|
+
"ValidatorConfig — the manifest documents the absence so "
|
|
89
|
+
"verifiers know to expect a legacy-versioning gap there."
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class WorkflowContractSnapshot(BaseModel):
|
|
95
|
+
"""Frozen snapshot of the workflow's launch-contract fields.
|
|
96
|
+
|
|
97
|
+
These are the fields that determine *what the workflow does*
|
|
98
|
+
when it runs (file types accepted, retention policy, agent
|
|
99
|
+
publication state). Pinning them in the manifest lets a future
|
|
100
|
+
verifier answer "what rules was this run operating under?"
|
|
101
|
+
without consulting the live producer database (which may have
|
|
102
|
+
moved on to a newer workflow version).
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
model_config = ConfigDict(frozen=True)
|
|
106
|
+
|
|
107
|
+
allowed_file_types: list[str] = Field(default_factory=list)
|
|
108
|
+
data_retention: str = ""
|
|
109
|
+
output_retention: str = ""
|
|
110
|
+
agent_billing_mode: str = ""
|
|
111
|
+
agent_price_cents: int | None = None
|
|
112
|
+
agent_max_launches_per_hour: int | None = None
|
|
113
|
+
agent_public_discovery: bool = False
|
|
114
|
+
agent_access_enabled: bool = False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ManifestRetentionInfo(BaseModel):
|
|
118
|
+
"""Retention class + applied-redaction summary.
|
|
119
|
+
|
|
120
|
+
Session A populates ``retention_class`` from the workflow's
|
|
121
|
+
``data_retention`` so consumers know what the run agreed to.
|
|
122
|
+
Session B will populate ``redactions_applied`` with the list of
|
|
123
|
+
fields the retention policy stripped before serialisation. For
|
|
124
|
+
Session A the field is empty.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
model_config = ConfigDict(frozen=True)
|
|
128
|
+
|
|
129
|
+
retention_class: str = Field(
|
|
130
|
+
description=(
|
|
131
|
+
"Workflow data-retention setting at run time "
|
|
132
|
+
"(e.g. 'DO_NOT_STORE', 'STORE_30_DAYS')."
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
redactions_applied: list[str] = Field(
|
|
136
|
+
default_factory=list,
|
|
137
|
+
description=(
|
|
138
|
+
"Names of manifest fields that were redacted under this "
|
|
139
|
+
"retention class. Session A leaves this empty; Session B "
|
|
140
|
+
"fills it in when the redaction policy lands."
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ManifestPayloadDigests(BaseModel):
|
|
146
|
+
"""Hashes of the run's payload data.
|
|
147
|
+
|
|
148
|
+
Session A leaves these as None — the field exists in the schema
|
|
149
|
+
so consumers can write code that handles both the Session A
|
|
150
|
+
("not yet computed") and Session B ("populated from the run's
|
|
151
|
+
submission and output envelope") cases without a v2 bump.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
model_config = ConfigDict(frozen=True)
|
|
155
|
+
|
|
156
|
+
input_sha256: str | None = Field(
|
|
157
|
+
default=None,
|
|
158
|
+
description=(
|
|
159
|
+
"SHA-256 hex of the submission's primary file bytes at run "
|
|
160
|
+
"time. Always present in Session B — even when "
|
|
161
|
+
"DO_NOT_STORE purges the bytes themselves, the hash is "
|
|
162
|
+
"the immutable proof the run consumed input matching it."
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
output_envelope_sha256: str | None = Field(
|
|
166
|
+
default=None,
|
|
167
|
+
description=(
|
|
168
|
+
"SHA-256 hex of the canonical output envelope JSON. None "
|
|
169
|
+
"for runs whose retention policy excludes outputs."
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class EvidenceManifest(BaseModel):
|
|
175
|
+
"""Top-level evidence manifest for a single validation run.
|
|
176
|
+
|
|
177
|
+
The canonical JSON form of this model — produced by serialising
|
|
178
|
+
the dict from ``model_dump(mode="json")`` via ``json.dumps`` with
|
|
179
|
+
``sort_keys=True`` and ``separators=(",",":")`` — is what gets
|
|
180
|
+
hashed and persisted by the producer.
|
|
181
|
+
|
|
182
|
+
Producers store ``SHA-256(canonical_json_bytes)`` alongside the
|
|
183
|
+
file. Tampering with the manifest after persistence is detectable
|
|
184
|
+
via re-fetch + re-hash.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
188
|
+
|
|
189
|
+
schema_version: Literal["validibot.evidence.v1"] = Field(
|
|
190
|
+
default=SCHEMA_VERSION,
|
|
191
|
+
description="Pinned schema-version string. Verifiers reject other values.",
|
|
192
|
+
)
|
|
193
|
+
run_id: str = Field(description="Producer-side run UUID (hex string).")
|
|
194
|
+
workflow_id: int = Field(description="Workflow primary key at run time.")
|
|
195
|
+
workflow_slug: str = Field(description="Workflow slug at run time.")
|
|
196
|
+
workflow_version: str = Field(description="Workflow version at run time.")
|
|
197
|
+
org_id: int = Field(description="Organization primary key that owned the run.")
|
|
198
|
+
executed_at: str = Field(
|
|
199
|
+
description=(
|
|
200
|
+
"ISO 8601 UTC timestamp of run completion. String rather "
|
|
201
|
+
"than datetime so canonical-JSON bytes are stable across "
|
|
202
|
+
"Python versions and timezone-aware/naïve serialisation "
|
|
203
|
+
"differences."
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
status: str = Field(
|
|
207
|
+
description=("Run status at completion (SUCCEEDED / FAILED / CANCELED)."),
|
|
208
|
+
)
|
|
209
|
+
workflow_contract: WorkflowContractSnapshot
|
|
210
|
+
steps: list[StepValidatorRecord] = Field(
|
|
211
|
+
default_factory=list,
|
|
212
|
+
description="Per-step validator records, ordered by step.order.",
|
|
213
|
+
)
|
|
214
|
+
input_schema: dict | None = Field(
|
|
215
|
+
default=None,
|
|
216
|
+
description=(
|
|
217
|
+
"Workflow's structured input contract (canonical JSON Schema) "
|
|
218
|
+
"at run time. None when the workflow has no structured input "
|
|
219
|
+
"contract. The schema itself is the contract; the input "
|
|
220
|
+
"hash in payload_digests is the proof of conformance."
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
retention: ManifestRetentionInfo
|
|
224
|
+
payload_digests: ManifestPayloadDigests = Field(
|
|
225
|
+
default_factory=ManifestPayloadDigests,
|
|
226
|
+
description="Session A: empty. Session B: input + output hashes.",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__all__ = [
|
|
231
|
+
"SCHEMA_VERSION",
|
|
232
|
+
"EvidenceManifest",
|
|
233
|
+
"ManifestPayloadDigests",
|
|
234
|
+
"ManifestRetentionInfo",
|
|
235
|
+
"StepValidatorRecord",
|
|
236
|
+
"WorkflowContractSnapshot",
|
|
237
|
+
]
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"""Shared domain models and utilities for Simple Validations."""
|
|
2
|
-
|
|
3
|
-
from validibot_shared.energyplus.models import (
|
|
4
|
-
EnergyPlusSimulationLogs,
|
|
5
|
-
EnergyPlusSimulationMetrics,
|
|
6
|
-
EnergyPlusSimulationOutputs,
|
|
7
|
-
)
|
|
8
|
-
from validibot_shared.fmu.models import FMUProbeResult, FMUVariableMeta
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"EnergyPlusSimulationLogs",
|
|
12
|
-
"EnergyPlusSimulationMetrics",
|
|
13
|
-
"EnergyPlusSimulationOutputs",
|
|
14
|
-
"FMUProbeResult",
|
|
15
|
-
"FMUVariableMeta",
|
|
16
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|