validibot-shared 0.13.0__tar.gz → 0.14.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 (40) hide show
  1. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/PKG-INFO +1 -1
  2. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/pyproject.toml +1 -1
  3. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_evidence_manifest.py +78 -0
  4. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/evidence/__init__.py +6 -0
  5. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/evidence/manifest.py +180 -0
  6. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/.gitignore +0 -0
  7. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/LICENSE +0 -0
  8. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/NOTICE +0 -0
  9. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/README.md +0 -0
  10. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/__init__.py +0 -0
  11. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_artifacts.py +0 -0
  12. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_canonicalization.py +0 -0
  13. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_energyplus_envelopes.py +0 -0
  14. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_energyplus_models.py +0 -0
  15. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_fmu_envelopes.py +0 -0
  16. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_fmu_models.py +0 -0
  17. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_package_init.py +0 -0
  18. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_schematron_envelopes.py +0 -0
  19. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_schematron_svrl.py +0 -0
  20. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_shacl_envelopes.py +0 -0
  21. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/tests/test_validations_envelopes.py +0 -0
  22. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/__init__.py +0 -0
  23. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/_vendor/__init__.py +0 -0
  24. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/_vendor/jcs.py +0 -0
  25. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/canonicalization.py +0 -0
  26. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/energyplus/__init__.py +0 -0
  27. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/energyplus/envelopes.py +0 -0
  28. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/energyplus/models.py +0 -0
  29. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/fmu/__init__.py +0 -0
  30. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/fmu/envelopes.py +0 -0
  31. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/fmu/models.py +0 -0
  32. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/py.typed +0 -0
  33. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/schematron/__init__.py +0 -0
  34. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/schematron/envelopes.py +0 -0
  35. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/schematron/svrl.py +0 -0
  36. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/shacl/__init__.py +0 -0
  37. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/shacl/envelopes.py +0 -0
  38. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/validations/__init__.py +0 -0
  39. {validibot_shared-0.13.0 → validibot_shared-0.14.0}/validibot_shared/validations/artifacts.py +0 -0
  40. {validibot_shared-0.13.0 → validibot_shared-0.14.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.13.0
3
+ Version: 0.14.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "validibot-shared"
7
- version = "0.13.0"
7
+ version = "0.14.0"
8
8
  description = "Shared library for data interchange between Validibot and validator containers"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -16,6 +16,9 @@ What this file covers
16
16
  computation depends on.
17
17
  4. Optional fields default sensibly so producers can build a
18
18
  minimal Session-A manifest without populating Session-B fields.
19
+ 5. Artifact lineage fields are additive, evidence-safe projections:
20
+ hashes and stable IDs are allowed; private storage URIs are not part
21
+ of the public schema.
19
22
  """
20
23
 
21
24
  from __future__ import annotations
@@ -28,7 +31,10 @@ from validibot_shared.evidence import (
28
31
  ContractConstant,
29
32
  ContractSignalMapping,
30
33
  EvidenceManifest,
34
+ ManifestArtifactInputBinding,
35
+ ManifestArtifactLineageEdge,
31
36
  ManifestPayloadDigests,
37
+ ManifestProducedArtifact,
32
38
  ManifestRetentionInfo,
33
39
  StepValidatorRecord,
34
40
  WorkflowContractSnapshot,
@@ -167,6 +173,78 @@ class TestOptionalFields:
167
173
  assert manifest.schema_version == "validibot.evidence.v1"
168
174
 
169
175
 
176
+ # ──────────────────────────────────────────────────────────────────────
177
+ # Artifact lineage — produced artifacts, consumed file-port bindings, edges
178
+ # ──────────────────────────────────────────────────────────────────────
179
+ #
180
+ # ADR-2026-07-06. These fields make step artifacts externally verifiable
181
+ # without exposing producer-private storage paths. They are additive and
182
+ # default-empty, so the evidence schema string remains ``validibot.evidence.v1``.
183
+
184
+
185
+ class TestArtifactLineageFields:
186
+ def test_lineage_fields_default_to_empty(self):
187
+ """Older producers and runs without artifacts remain valid."""
188
+ manifest = EvidenceManifest(**_minimal_manifest_kwargs())
189
+
190
+ assert manifest.produced_artifacts == []
191
+ assert manifest.artifact_input_bindings == []
192
+ assert manifest.artifact_lineage_edges == []
193
+
194
+ def test_artifact_lineage_records_round_trip_without_storage_uris(self):
195
+ """The public projection keeps IDs/hashes but has no URI slot.
196
+
197
+ Storage URIs are internal deployment details. Evidence needs the durable
198
+ proof material: artifact id, producer/consumer coordinates, filenames,
199
+ and SHA-256 hashes.
200
+ """
201
+ produced = ManifestProducedArtifact(
202
+ artifact_id="artifact-1",
203
+ run_id="run-1",
204
+ step_run_id="step-run-1",
205
+ producer_step_id=10,
206
+ producer_step_key="build_model",
207
+ contract_key="generated_model",
208
+ filename="model.epjson",
209
+ sha256="a" * 64,
210
+ )
211
+ binding = ManifestArtifactInputBinding(
212
+ target_step_id=20,
213
+ target_step_key="simulate",
214
+ target_step_run_id="step-run-2",
215
+ target_port_key="primary_model",
216
+ source_scope="upstream_artifact",
217
+ source_artifact_id="artifact-1",
218
+ source_filename="model.epjson",
219
+ source_sha256="a" * 64,
220
+ producer_step_key="build_model",
221
+ producer_contract_key="generated_model",
222
+ resolved=True,
223
+ )
224
+ edge = ManifestArtifactLineageEdge(
225
+ source_artifact_id="artifact-1",
226
+ source_step_key="build_model",
227
+ source_contract_key="generated_model",
228
+ source_sha256="a" * 64,
229
+ target_step_key="simulate",
230
+ target_port_key="primary_model",
231
+ target_step_run_id="step-run-2",
232
+ )
233
+
234
+ manifest = EvidenceManifest(
235
+ **_minimal_manifest_kwargs(),
236
+ produced_artifacts=[produced],
237
+ artifact_input_bindings=[binding],
238
+ artifact_lineage_edges=[edge],
239
+ )
240
+ restored = EvidenceManifest.model_validate(manifest.model_dump(mode="json"))
241
+
242
+ assert restored == manifest
243
+ assert restored.schema_version == "validibot.evidence.v1"
244
+ assert not hasattr(restored.produced_artifacts[0], "storage_uri")
245
+ assert not hasattr(restored.artifact_input_bindings[0], "uri")
246
+
247
+
170
248
  # ──────────────────────────────────────────────────────────────────────
171
249
  # WorkflowContractSnapshot — constants, signal-mapping defs, definition hash
172
250
  # ──────────────────────────────────────────────────────────────────────
@@ -12,7 +12,10 @@ from validibot_shared.evidence.manifest import (
12
12
  ContractConstant,
13
13
  ContractSignalMapping,
14
14
  EvidenceManifest,
15
+ ManifestArtifactInputBinding,
16
+ ManifestArtifactLineageEdge,
15
17
  ManifestPayloadDigests,
18
+ ManifestProducedArtifact,
16
19
  ManifestRetentionInfo,
17
20
  StepValidatorRecord,
18
21
  WorkflowContractSnapshot,
@@ -23,7 +26,10 @@ __all__ = [
23
26
  "ContractConstant",
24
27
  "ContractSignalMapping",
25
28
  "EvidenceManifest",
29
+ "ManifestArtifactInputBinding",
30
+ "ManifestArtifactLineageEdge",
26
31
  "ManifestPayloadDigests",
32
+ "ManifestProducedArtifact",
27
33
  "ManifestRetentionInfo",
28
34
  "StepValidatorRecord",
29
35
  "WorkflowContractSnapshot",
@@ -267,6 +267,160 @@ class ManifestPayloadDigests(BaseModel):
267
267
  )
268
268
 
269
269
 
270
+ class ManifestProducedArtifact(BaseModel):
271
+ """Public, durable projection of an artifact produced by a run step.
272
+
273
+ This deliberately omits storage locations (``storage_uri`` and
274
+ ``manifest_uri`` in the Django model / ArtifactRef). Evidence consumers need
275
+ stable identity, hashes, producer coordinates, and file descriptors; private
276
+ bucket paths stay inside the producer.
277
+ """
278
+
279
+ model_config = ConfigDict(frozen=True)
280
+
281
+ artifact_id: str = Field(description="Producer-side Artifact primary key.")
282
+ run_id: str = Field(description="Run UUID that owns the artifact.")
283
+ step_run_id: str | None = Field(
284
+ default=None,
285
+ description="Step-run UUID/primary key that produced the artifact.",
286
+ )
287
+ producer_step_id: int | None = Field(
288
+ default=None,
289
+ description="Producer-side WorkflowStep primary key, if step-scoped.",
290
+ )
291
+ producer_step_key: str = Field(
292
+ default="",
293
+ description="Stable workflow-local key of the producer step.",
294
+ )
295
+ producer_step_name: str = Field(
296
+ default="",
297
+ description="Human-facing producer step name at run time.",
298
+ )
299
+ contract_key: str = Field(description="Stable artifact output contract key.")
300
+ item_key: str = Field(
301
+ default="",
302
+ description="Stable collection item key, empty for scalar artifact ports.",
303
+ )
304
+ filename: str = Field(default="", description="Original/public filename.")
305
+ label: str = Field(default="", description="Human-facing artifact label.")
306
+ role: str = Field(default="", description="Validator-facing artifact role.")
307
+ kind: str = Field(default="", description="Artifact kind.")
308
+ media_type: str = Field(default="", description="Media/MIME type.")
309
+ data_format: str = Field(default="", description="Domain data format.")
310
+ size_bytes: int | None = Field(default=None, description="Artifact byte size.")
311
+ sha256: str = Field(default="", description="SHA-256 of artifact bytes.")
312
+ manifest_sha256: str = Field(
313
+ default="",
314
+ description="SHA-256 of the validator output manifest, if present.",
315
+ )
316
+ producer_validator_type: str = Field(
317
+ default="",
318
+ description="Validator type that produced the artifact.",
319
+ )
320
+ producer_validator_version: str = Field(
321
+ default="",
322
+ description="Validator version that produced the artifact.",
323
+ )
324
+ producer_backend_image_digest: str = Field(
325
+ default="",
326
+ description="Backend image digest that produced the artifact.",
327
+ )
328
+ retention_class: str = Field(
329
+ default="",
330
+ description="Output-retention class attached to the artifact.",
331
+ )
332
+
333
+
334
+ class ManifestArtifactInputBinding(BaseModel):
335
+ """Runtime file-port binding consumed by a step.
336
+
337
+ Each row records what artifact-like source satisfied a declared input port:
338
+ submitted file, workflow resource, or upstream artifact. URI-bearing runtime
339
+ traces are projected into filename/hash/ID fields only.
340
+ """
341
+
342
+ model_config = ConfigDict(frozen=True)
343
+
344
+ target_step_id: int = Field(description="WorkflowStep primary key consuming it.")
345
+ target_step_key: str = Field(description="Stable key of the consumer step.")
346
+ target_step_name: str = Field(default="", description="Consumer step name.")
347
+ target_step_run_id: str = Field(default="", description="Consumer step-run id.")
348
+ target_port_key: str = Field(description="Declared artifact input port key.")
349
+ target_port_role: str = Field(default="", description="Backend-facing port role.")
350
+ target_envelope_channel: str = Field(
351
+ default="",
352
+ description="Envelope channel used by the port.",
353
+ )
354
+ source_scope: str = Field(
355
+ description="Runtime source scope used to resolve the binding.",
356
+ )
357
+ source_data_path: str = Field(
358
+ default="",
359
+ description="Runtime source path used to resolve the binding.",
360
+ )
361
+ resolved: bool = Field(description="Whether runtime resolution succeeded.")
362
+ used_default: bool = Field(
363
+ default=False,
364
+ description="Whether a default satisfied the input.",
365
+ )
366
+ source_artifact_id: str = Field(
367
+ default="",
368
+ description="Upstream Artifact id, for upstream_artifact bindings.",
369
+ )
370
+ source_submission_file_id: str = Field(
371
+ default="",
372
+ description="SubmissionInputFile id, for submitted artifact-port files.",
373
+ )
374
+ source_submission_id: str = Field(
375
+ default="",
376
+ description="Submission id for the primary submitted file, when used.",
377
+ )
378
+ source_resource_id: str = Field(
379
+ default="",
380
+ description="Workflow/resource id, for workflow_resource bindings.",
381
+ )
382
+ source_filename: str = Field(default="", description="Resolved source filename.")
383
+ source_media_type: str = Field(
384
+ default="",
385
+ description="Resolved source media type.",
386
+ )
387
+ source_data_format: str = Field(
388
+ default="",
389
+ description="Resolved source data format or resource type.",
390
+ )
391
+ source_size_bytes: int | None = Field(default=None)
392
+ source_sha256: str = Field(default="", description="Resolved source SHA-256.")
393
+ producer_step_key: str = Field(
394
+ default="",
395
+ description="Producer step key for upstream_artifact bindings.",
396
+ )
397
+ producer_contract_key: str = Field(
398
+ default="",
399
+ description="Producer artifact contract key for upstream_artifact bindings.",
400
+ )
401
+ error_message: str = Field(
402
+ default="",
403
+ description="Resolution error when resolved=false.",
404
+ )
405
+
406
+
407
+ class ManifestArtifactLineageEdge(BaseModel):
408
+ """Explicit upstream-artifact edge from producer output to consumer input."""
409
+
410
+ model_config = ConfigDict(frozen=True)
411
+
412
+ source_artifact_id: str = Field(description="Produced Artifact id.")
413
+ source_step_key: str = Field(default="", description="Producer step key.")
414
+ source_contract_key: str = Field(
415
+ default="",
416
+ description="Producer artifact contract key.",
417
+ )
418
+ source_sha256: str = Field(default="", description="Produced artifact SHA-256.")
419
+ target_step_key: str = Field(description="Consumer step key.")
420
+ target_port_key: str = Field(description="Consumer artifact input port key.")
421
+ target_step_run_id: str = Field(default="", description="Consumer step-run id.")
422
+
423
+
270
424
  class EvidenceManifest(BaseModel):
271
425
  """Top-level evidence manifest for a single validation run.
272
426
 
@@ -321,6 +475,29 @@ class EvidenceManifest(BaseModel):
321
475
  default_factory=ManifestPayloadDigests,
322
476
  description="Session A: empty. Session B: input + output hashes.",
323
477
  )
478
+ produced_artifacts: list[ManifestProducedArtifact] = Field(
479
+ default_factory=list,
480
+ description=(
481
+ "Artifacts produced by this run, projected without private storage "
482
+ "URIs. Empty for runs with no artifacts or producers predating "
483
+ "artifact lineage evidence."
484
+ ),
485
+ )
486
+ artifact_input_bindings: list[ManifestArtifactInputBinding] = Field(
487
+ default_factory=list,
488
+ description=(
489
+ "Runtime artifact file-port inputs consumed by steps: submitted "
490
+ "files, workflow resources, and upstream artifacts."
491
+ ),
492
+ )
493
+ artifact_lineage_edges: list[ManifestArtifactLineageEdge] = Field(
494
+ default_factory=list,
495
+ description=(
496
+ "Producer artifact -> consumer port edges for upstream_artifact "
497
+ "bindings. Submitted files and workflow resources are recorded in "
498
+ "artifact_input_bindings but do not create producer-step edges."
499
+ ),
500
+ )
324
501
  # The auth channel that initiated the run. Pinning it in the
325
502
  # manifest lets verifiers answer "what surface produced this
326
503
  # run?" without consulting the producer database (the run row
@@ -346,7 +523,10 @@ class EvidenceManifest(BaseModel):
346
523
  __all__ = [
347
524
  "SCHEMA_VERSION",
348
525
  "EvidenceManifest",
526
+ "ManifestArtifactInputBinding",
527
+ "ManifestArtifactLineageEdge",
349
528
  "ManifestPayloadDigests",
529
+ "ManifestProducedArtifact",
350
530
  "ManifestRetentionInfo",
351
531
  "StepValidatorRecord",
352
532
  "WorkflowContractSnapshot",