zeromodel-artifacts 1.1.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 (37) hide show
  1. zeromodel_artifacts-1.1.0/PKG-INFO +118 -0
  2. zeromodel_artifacts-1.1.0/README.md +92 -0
  3. zeromodel_artifacts-1.1.0/pyproject.toml +44 -0
  4. zeromodel_artifacts-1.1.0/setup.cfg +4 -0
  5. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/__init__.py +120 -0
  6. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/adapted_report_persistence.py +91 -0
  7. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/adapter.py +30 -0
  8. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/aggregate.py +795 -0
  9. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/canonicalization.py +13 -0
  10. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/compatibility_schema.py +93 -0
  11. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/compiled_artifact.py +518 -0
  12. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/core_artifact_persistence.py +95 -0
  13. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/ref.py +44 -0
  14. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/report_adapter_contract_persistence.py +78 -0
  15. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/report_compiler.py +254 -0
  16. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/report_decode.py +82 -0
  17. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/report_dto.py +493 -0
  18. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/report_errors.py +13 -0
  19. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/report_loading.py +94 -0
  20. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/score_semantics.py +16 -0
  21. zeromodel_artifacts-1.1.0/src/zeromodel/artifacts/store.py +156 -0
  22. zeromodel_artifacts-1.1.0/src/zeromodel_artifacts.egg-info/PKG-INFO +118 -0
  23. zeromodel_artifacts-1.1.0/src/zeromodel_artifacts.egg-info/SOURCES.txt +35 -0
  24. zeromodel_artifacts-1.1.0/src/zeromodel_artifacts.egg-info/dependency_links.txt +1 -0
  25. zeromodel_artifacts-1.1.0/src/zeromodel_artifacts.egg-info/requires.txt +2 -0
  26. zeromodel_artifacts-1.1.0/src/zeromodel_artifacts.egg-info/top_level.txt +1 -0
  27. zeromodel_artifacts-1.1.0/tests/test_adapted_report_identity.py +430 -0
  28. zeromodel_artifacts-1.1.0/tests/test_adapted_report_persistence.py +209 -0
  29. zeromodel_artifacts-1.1.0/tests/test_artifact_ref_and_store.py +146 -0
  30. zeromodel_artifacts-1.1.0/tests/test_artifacts_api_isolation.py +98 -0
  31. zeromodel_artifacts-1.1.0/tests/test_compiled_artifact_validation.py +364 -0
  32. zeromodel_artifacts-1.1.0/tests/test_compiled_report_aggregate.py +321 -0
  33. zeromodel_artifacts-1.1.0/tests/test_compiled_report_aggregate_adversarial.py +761 -0
  34. zeromodel_artifacts-1.1.0/tests/test_reference_kind_and_receipt_invariants.py +242 -0
  35. zeromodel_artifacts-1.1.0/tests/test_report_adapter_contract.py +63 -0
  36. zeromodel_artifacts-1.1.0/tests/test_report_compilation.py +409 -0
  37. zeromodel_artifacts-1.1.0/tests/test_report_families.py +184 -0
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: zeromodel-artifacts
3
+ Version: 1.1.0
4
+ Summary: ZeroModel canonical artifact reference, resolution, and content-addressed storage
5
+ Author-email: Ernan Hughes <ernanhughes@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ernanhughes/zeromodel
8
+ Project-URL: Repository, https://github.com/ernanhughes/zeromodel
9
+ Project-URL: Documentation, https://ernanhughes.github.io/zeromodel/
10
+ Keywords: visual-policy-map,vpm,artifact-identity,content-addressed-storage
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: numpy>=1.23
25
+ Requires-Dist: zeromodel==1.1.0
26
+
27
+ # zeromodel-artifacts
28
+
29
+ Canonical artifact reference, resolution, and content-addressed storage for
30
+ the ZeroModel workspace.
31
+
32
+ This package defines the stable, cross-package `ArtifactRef` identity and the
33
+ `ArtifactResolver` / `ArtifactStore` protocols other packages (such as
34
+ `zeromodel-trust` and `zeromodel-navigation`) use to persist and resolve their
35
+ own artifacts, without each package inventing its own storage layer.
36
+
37
+ It reuses `zeromodel.core`'s existing canonicalization and digest primitives
38
+ (`canonical_json_bytes`, `sha256_digest`) rather than redefining them.
39
+
40
+ ## Report compilation and the compiled-report aggregate
41
+
42
+ On top of the storage kernel, this package also compiles typed external
43
+ reports into deterministic, source-bound VPM artifacts (`ReportAdapter`,
44
+ `AdaptedReportDTO`, `compile_report()`) and resolves the complete result as
45
+ one coherent aggregate:
46
+
47
+ ```text
48
+ AdaptedReportDTO (persisted, resolvable)
49
+ ReportAdapterContractDTO (persisted, resolvable)
50
+
51
+ CompiledReportArtifactDTO (aggregate root)
52
+ ├── adapted_report_ref -> AdaptedReportDTO
53
+ ├── adapter_contract_ref -> ReportAdapterContractDTO
54
+ ├── score_table_ref -> ScoreTable
55
+ ├── layout_recipe_ref -> LayoutRecipe
56
+ └── vpm_artifact_ref -> VPMArtifact
57
+ ```
58
+
59
+ `load_compiled_report_aggregate()` resolves all five referenced artifacts
60
+ and runs `validate_compiled_report_aggregate()`, which proves the
61
+ collection is semantically closed - not merely that each object's own
62
+ digest is valid, and not merely that the resolved objects agree with each
63
+ other. A compiled report can reference a `ScoreTable` from one report, a
64
+ `LayoutRecipe` from another, and a `VPMArtifact` from a third, each
65
+ individually digest-valid; a VPM can even embed the *correct* `ScoreTable`
66
+ and `LayoutRecipe` while its normalized pixels or row/column ordering are
67
+ fabricated. Aggregate validation proves: every resolved object's
68
+ store-level digest actually matches the ref the compiled report declares
69
+ for it (not merely assumed from how the caller obtained the object); the
70
+ resolved adapter contract's declared fields agree with what the compiled
71
+ and adapted reports copied from it; the VPM's `normalized_values`/
72
+ `row_order`/`column_order`/`provenance` are exactly what `build_vpm()`
73
+ would deterministically reproduce from the resolved `ScoreTable`/
74
+ `LayoutRecipe`; and every VPM view coordinate, raw value, and source
75
+ binding is exactly what the compiled report's cell bindings claim.
76
+ `build_compiled_report_closure_receipt()` produces an auditable,
77
+ content-addressed receipt only after every check passes - never a partial
78
+ result.
79
+
80
+ Compatibility between two compiled reports requires three independent
81
+ identities to agree: `compatibility_id` (a human label), and two content
82
+ digests: `compatibility_schema_id` (dimension ids, order, score semantics,
83
+ and value/target ranges) and `report_semantics_id` (report kind, subject
84
+ kind, dimension namespace, and duplicate-value policy - the layer that
85
+ distinguishes, for example, a report over sentences from a structurally
86
+ identical report over claims).
87
+
88
+ `load_compiled_report_vpm(*, ref, resolver)` is the one safe public path
89
+ to a compiled report's rendered `VPMArtifact` - it is a thin wrapper
90
+ around `load_compiled_report_aggregate(...).vpm_artifact`, so it always
91
+ runs the full closure (including the deterministic-reconstruction check
92
+ above) before returning anything. `core_artifact_persistence.load_vpm_artifact`
93
+ remains available as an explicitly low-level Core loader (digest-only, no
94
+ compiled-report context to close an aggregate against) for callers who
95
+ genuinely have no compiled report to check against - it is not itself a
96
+ safe rendering path for a compiled report's VPM.
97
+
98
+ A reference is the pair `(artifact_kind, artifact_id)`, not the digest
99
+ alone: `CompiledReportArtifactDTO` rejects a nested ref whose declared
100
+ kind doesn't match its expected artifact kind even when the `artifact_id`
101
+ genuinely matches stored content, and `validate_compiled_report_aggregate`
102
+ re-asserts the same check as defense in depth.
103
+ `CompiledReportClosureReceiptDTO` similarly cannot be constructed with an
104
+ incomplete `checks` tuple or non-empty `failure_codes`, even with a
105
+ `receipt_id` that correctly hashes that content - the type's promise that
106
+ "every check passed" is enforced structurally, not left to its one
107
+ legitimate builder.
108
+
109
+ **Claims boundary:** ZeroModel can persist and reload a complete adapted
110
+ report, its governing adapter contract, and its compiled `ScoreTable`,
111
+ `LayoutRecipe`, and `VPMArtifact` as one content-addressed aggregate, and
112
+ verify that every subject, dimension, value, coordinate, source binding,
113
+ declared contract semantic, and rendered pixel agrees across all five
114
+ representations - including that the VPM's visible pixels were actually
115
+ generated by its own embedded recipe, not merely digest-consistent with
116
+ themselves. This does not include Trust-signed compiled reports (Trust
117
+ integration is a call-site composition, not implemented here),
118
+ cross-schema conversion, or automatic report repair.
@@ -0,0 +1,92 @@
1
+ # zeromodel-artifacts
2
+
3
+ Canonical artifact reference, resolution, and content-addressed storage for
4
+ the ZeroModel workspace.
5
+
6
+ This package defines the stable, cross-package `ArtifactRef` identity and the
7
+ `ArtifactResolver` / `ArtifactStore` protocols other packages (such as
8
+ `zeromodel-trust` and `zeromodel-navigation`) use to persist and resolve their
9
+ own artifacts, without each package inventing its own storage layer.
10
+
11
+ It reuses `zeromodel.core`'s existing canonicalization and digest primitives
12
+ (`canonical_json_bytes`, `sha256_digest`) rather than redefining them.
13
+
14
+ ## Report compilation and the compiled-report aggregate
15
+
16
+ On top of the storage kernel, this package also compiles typed external
17
+ reports into deterministic, source-bound VPM artifacts (`ReportAdapter`,
18
+ `AdaptedReportDTO`, `compile_report()`) and resolves the complete result as
19
+ one coherent aggregate:
20
+
21
+ ```text
22
+ AdaptedReportDTO (persisted, resolvable)
23
+ ReportAdapterContractDTO (persisted, resolvable)
24
+
25
+ CompiledReportArtifactDTO (aggregate root)
26
+ ├── adapted_report_ref -> AdaptedReportDTO
27
+ ├── adapter_contract_ref -> ReportAdapterContractDTO
28
+ ├── score_table_ref -> ScoreTable
29
+ ├── layout_recipe_ref -> LayoutRecipe
30
+ └── vpm_artifact_ref -> VPMArtifact
31
+ ```
32
+
33
+ `load_compiled_report_aggregate()` resolves all five referenced artifacts
34
+ and runs `validate_compiled_report_aggregate()`, which proves the
35
+ collection is semantically closed - not merely that each object's own
36
+ digest is valid, and not merely that the resolved objects agree with each
37
+ other. A compiled report can reference a `ScoreTable` from one report, a
38
+ `LayoutRecipe` from another, and a `VPMArtifact` from a third, each
39
+ individually digest-valid; a VPM can even embed the *correct* `ScoreTable`
40
+ and `LayoutRecipe` while its normalized pixels or row/column ordering are
41
+ fabricated. Aggregate validation proves: every resolved object's
42
+ store-level digest actually matches the ref the compiled report declares
43
+ for it (not merely assumed from how the caller obtained the object); the
44
+ resolved adapter contract's declared fields agree with what the compiled
45
+ and adapted reports copied from it; the VPM's `normalized_values`/
46
+ `row_order`/`column_order`/`provenance` are exactly what `build_vpm()`
47
+ would deterministically reproduce from the resolved `ScoreTable`/
48
+ `LayoutRecipe`; and every VPM view coordinate, raw value, and source
49
+ binding is exactly what the compiled report's cell bindings claim.
50
+ `build_compiled_report_closure_receipt()` produces an auditable,
51
+ content-addressed receipt only after every check passes - never a partial
52
+ result.
53
+
54
+ Compatibility between two compiled reports requires three independent
55
+ identities to agree: `compatibility_id` (a human label), and two content
56
+ digests: `compatibility_schema_id` (dimension ids, order, score semantics,
57
+ and value/target ranges) and `report_semantics_id` (report kind, subject
58
+ kind, dimension namespace, and duplicate-value policy - the layer that
59
+ distinguishes, for example, a report over sentences from a structurally
60
+ identical report over claims).
61
+
62
+ `load_compiled_report_vpm(*, ref, resolver)` is the one safe public path
63
+ to a compiled report's rendered `VPMArtifact` - it is a thin wrapper
64
+ around `load_compiled_report_aggregate(...).vpm_artifact`, so it always
65
+ runs the full closure (including the deterministic-reconstruction check
66
+ above) before returning anything. `core_artifact_persistence.load_vpm_artifact`
67
+ remains available as an explicitly low-level Core loader (digest-only, no
68
+ compiled-report context to close an aggregate against) for callers who
69
+ genuinely have no compiled report to check against - it is not itself a
70
+ safe rendering path for a compiled report's VPM.
71
+
72
+ A reference is the pair `(artifact_kind, artifact_id)`, not the digest
73
+ alone: `CompiledReportArtifactDTO` rejects a nested ref whose declared
74
+ kind doesn't match its expected artifact kind even when the `artifact_id`
75
+ genuinely matches stored content, and `validate_compiled_report_aggregate`
76
+ re-asserts the same check as defense in depth.
77
+ `CompiledReportClosureReceiptDTO` similarly cannot be constructed with an
78
+ incomplete `checks` tuple or non-empty `failure_codes`, even with a
79
+ `receipt_id` that correctly hashes that content - the type's promise that
80
+ "every check passed" is enforced structurally, not left to its one
81
+ legitimate builder.
82
+
83
+ **Claims boundary:** ZeroModel can persist and reload a complete adapted
84
+ report, its governing adapter contract, and its compiled `ScoreTable`,
85
+ `LayoutRecipe`, and `VPMArtifact` as one content-addressed aggregate, and
86
+ verify that every subject, dimension, value, coordinate, source binding,
87
+ declared contract semantic, and rendered pixel agrees across all five
88
+ representations - including that the VPM's visible pixels were actually
89
+ generated by its own embedded recipe, not merely digest-consistent with
90
+ themselves. This does not include Trust-signed compiled reports (Trust
91
+ integration is a call-site composition, not implemented here),
92
+ cross-schema conversion, or automatic report repair.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zeromodel-artifacts"
7
+ version = "1.1.0"
8
+ description = "ZeroModel canonical artifact reference, resolution, and content-addressed storage"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "Ernan Hughes", email = "ernanhughes@gmail.com"}]
13
+ keywords = [
14
+ "visual-policy-map",
15
+ "vpm",
16
+ "artifact-identity",
17
+ "content-addressed-storage",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "Intended Audience :: Science/Research",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ ]
32
+ dependencies = [
33
+ "numpy>=1.23",
34
+ "zeromodel==1.1.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/ernanhughes/zeromodel"
39
+ Repository = "https://github.com/ernanhughes/zeromodel"
40
+ Documentation = "https://ernanhughes.github.io/zeromodel/"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+ include = ["zeromodel.artifacts*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ from zeromodel.artifacts.adapted_report_persistence import (
4
+ ADAPTED_REPORT_ARTIFACT_KIND,
5
+ load_adapted_report,
6
+ store_adapted_report,
7
+ )
8
+ from zeromodel.artifacts.adapter import ReportAdapter
9
+ from zeromodel.artifacts.aggregate import (
10
+ CompiledReportClosureReceiptDTO,
11
+ ResolvedCompiledReportAggregateDTO,
12
+ build_compiled_report_closure_receipt,
13
+ load_compiled_report_aggregate,
14
+ load_compiled_report_vpm,
15
+ validate_compiled_report_aggregate,
16
+ )
17
+ from zeromodel.artifacts.canonicalization import canonical_json_bytes, sha256_digest
18
+ from zeromodel.artifacts.compatibility_schema import (
19
+ compute_compatibility_schema_id,
20
+ compute_report_semantics_id,
21
+ )
22
+ from zeromodel.artifacts.compiled_artifact import (
23
+ CellBindingDTO,
24
+ CompiledReportArtifactDTO,
25
+ CoreArtifactRefs,
26
+ )
27
+ from zeromodel.artifacts.core_artifact_persistence import (
28
+ LAYOUT_RECIPE_ARTIFACT_KIND,
29
+ SCORE_TABLE_ARTIFACT_KIND,
30
+ VPM_ARTIFACT_ARTIFACT_KIND,
31
+ load_layout_recipe,
32
+ load_score_table,
33
+ load_vpm_artifact,
34
+ store_layout_recipe,
35
+ store_score_table,
36
+ store_vpm_artifact,
37
+ )
38
+ from zeromodel.artifacts.ref import ARTIFACT_REF_VERSION, ArtifactRef, is_sha256_digest
39
+ from zeromodel.artifacts.report_adapter_contract_persistence import (
40
+ REPORT_ADAPTER_CONTRACT_ARTIFACT_KIND,
41
+ load_report_adapter_contract,
42
+ store_report_adapter_contract,
43
+ )
44
+ from zeromodel.artifacts.report_compiler import compile_report
45
+ from zeromodel.artifacts.report_dto import (
46
+ AdaptedDimensionDTO,
47
+ AdaptedReportDTO,
48
+ AdaptedSubjectDTO,
49
+ AdaptedValueDTO,
50
+ ReportAdapterContractDTO,
51
+ ReportFindingRefDTO,
52
+ SourceBindingDTO,
53
+ )
54
+ from zeromodel.artifacts.report_errors import (
55
+ ReportAdaptationError,
56
+ ReportCompilationError,
57
+ )
58
+ from zeromodel.artifacts.report_loading import load_compiled_report_artifact
59
+ from zeromodel.artifacts.score_semantics import ScoreSemantics
60
+ from zeromodel.artifacts.store import (
61
+ ArtifactIntegrityError,
62
+ ArtifactManifestConflictError,
63
+ ArtifactNotFoundError,
64
+ ArtifactResolver,
65
+ ArtifactStore,
66
+ InMemoryArtifactStore,
67
+ )
68
+
69
+ __all__ = [
70
+ "ADAPTED_REPORT_ARTIFACT_KIND",
71
+ "ARTIFACT_REF_VERSION",
72
+ "LAYOUT_RECIPE_ARTIFACT_KIND",
73
+ "REPORT_ADAPTER_CONTRACT_ARTIFACT_KIND",
74
+ "SCORE_TABLE_ARTIFACT_KIND",
75
+ "VPM_ARTIFACT_ARTIFACT_KIND",
76
+ "AdaptedDimensionDTO",
77
+ "AdaptedReportDTO",
78
+ "AdaptedSubjectDTO",
79
+ "AdaptedValueDTO",
80
+ "ArtifactIntegrityError",
81
+ "ArtifactManifestConflictError",
82
+ "ArtifactNotFoundError",
83
+ "ArtifactRef",
84
+ "ArtifactResolver",
85
+ "ArtifactStore",
86
+ "CellBindingDTO",
87
+ "CompiledReportArtifactDTO",
88
+ "CompiledReportClosureReceiptDTO",
89
+ "CoreArtifactRefs",
90
+ "InMemoryArtifactStore",
91
+ "ReportAdaptationError",
92
+ "ReportAdapter",
93
+ "ReportAdapterContractDTO",
94
+ "ReportCompilationError",
95
+ "ReportFindingRefDTO",
96
+ "ResolvedCompiledReportAggregateDTO",
97
+ "ScoreSemantics",
98
+ "SourceBindingDTO",
99
+ "build_compiled_report_closure_receipt",
100
+ "canonical_json_bytes",
101
+ "compile_report",
102
+ "compute_compatibility_schema_id",
103
+ "compute_report_semantics_id",
104
+ "is_sha256_digest",
105
+ "load_adapted_report",
106
+ "load_compiled_report_aggregate",
107
+ "load_compiled_report_artifact",
108
+ "load_compiled_report_vpm",
109
+ "load_layout_recipe",
110
+ "load_report_adapter_contract",
111
+ "load_score_table",
112
+ "load_vpm_artifact",
113
+ "sha256_digest",
114
+ "store_adapted_report",
115
+ "store_layout_recipe",
116
+ "store_report_adapter_contract",
117
+ "store_score_table",
118
+ "store_vpm_artifact",
119
+ "validate_compiled_report_aggregate",
120
+ ]
@@ -0,0 +1,91 @@
1
+ """Persist and reload `AdaptedReportDTO` as a first-class artifact.
2
+
3
+ Before this module, `AdaptedReportDTO` only ever existed as a discarded
4
+ local Python object inside `compile_report()`: the compiled report named it
5
+ by digest (`adapted_report_id`) but nothing made that digest resolvable
6
+ after the process exited. Raw values, per-value confidence, per-value
7
+ importance, source bindings, and parent-report lineage - none of which the
8
+ VPM matrix preserves completely - were unrecoverable once compilation
9
+ finished. This module closes that gap using the same decode-and-verify
10
+ pattern as `core_artifact_persistence.py`: resolve canonical bytes,
11
+ recompute the digest, require it to equal the requested ref's
12
+ `artifact_id`, decode, and reconstruct via `AdaptedReportDTO.__post_init__`
13
+ - never trusting a store's manifest as authoritative.
14
+
15
+ Canonical format: the stored payload is exactly
16
+ `adapted_report_signing_payload(adapted_report)` - the same payload
17
+ `AdaptedReportDTO.adapted_report_id` is computed over, with the
18
+ self-referential id field excluded. Because `ArtifactStore.put()` computes
19
+ `ref.artifact_id` as `sha256_digest(canonical_bytes)` over that same
20
+ payload, `ref.artifact_id == adapted_report.adapted_report_id` always
21
+ holds - the artifact-store identity and the DTO's own content identity are
22
+ the same digest, not two independent layers that happen to agree.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+
29
+ from zeromodel.artifacts.canonicalization import canonical_json_bytes, sha256_digest
30
+ from zeromodel.artifacts.ref import ArtifactRef
31
+ from zeromodel.artifacts.report_decode import (
32
+ decode_attributes,
33
+ decode_dimension,
34
+ decode_subject,
35
+ decode_value,
36
+ )
37
+ from zeromodel.artifacts.report_dto import (
38
+ AdaptedReportDTO,
39
+ adapted_report_signing_payload,
40
+ )
41
+ from zeromodel.artifacts.report_errors import ReportCompilationError
42
+ from zeromodel.artifacts.store import ArtifactResolver, ArtifactStore
43
+
44
+ ADAPTED_REPORT_ARTIFACT_KIND = "zeromodel.artifacts.adapted-report/v1"
45
+
46
+
47
+ def store_adapted_report(
48
+ adapted_report: AdaptedReportDTO, *, store: ArtifactStore
49
+ ) -> ArtifactRef:
50
+ canonical_bytes = canonical_json_bytes(
51
+ adapted_report_signing_payload(adapted_report)
52
+ )
53
+ ref = store.put(ADAPTED_REPORT_ARTIFACT_KIND, canonical_bytes, manifest=None)
54
+ if ref.artifact_id != adapted_report.adapted_report_id:
55
+ raise ReportCompilationError(
56
+ "stored adapted-report digest does not match adapted_report_id "
57
+ f"(stored={ref.artifact_id}, adapted_report_id={adapted_report.adapted_report_id})"
58
+ )
59
+ return ref
60
+
61
+
62
+ def load_adapted_report(
63
+ ref: ArtifactRef, *, resolver: ArtifactResolver
64
+ ) -> AdaptedReportDTO:
65
+ if ref.artifact_kind != ADAPTED_REPORT_ARTIFACT_KIND:
66
+ raise ReportCompilationError(
67
+ f"load_adapted_report requires artifact_kind={ADAPTED_REPORT_ARTIFACT_KIND!r}, "
68
+ f"got {ref.artifact_kind!r}"
69
+ )
70
+ canonical_bytes = resolver.resolve_canonical_bytes(ref)
71
+ actual_digest = sha256_digest(canonical_bytes)
72
+ if actual_digest != ref.artifact_id:
73
+ raise ReportCompilationError(
74
+ f"resolved canonical bytes for {ref.artifact_id} do not hash to the requested id "
75
+ f"(got {actual_digest})"
76
+ )
77
+ payload = json.loads(canonical_bytes)
78
+
79
+ return AdaptedReportDTO(
80
+ adapted_report_id=ref.artifact_id,
81
+ report_id=payload["report_id"],
82
+ report_kind=payload["report_kind"],
83
+ adapter_contract_id=payload["adapter_contract_id"],
84
+ compatibility_id=payload["compatibility_id"],
85
+ subjects=tuple(decode_subject(item) for item in payload["subjects"]),
86
+ dimensions=tuple(decode_dimension(item) for item in payload["dimensions"]),
87
+ values=tuple(decode_value(item) for item in payload["values"]),
88
+ parent_report_ids=tuple(payload["parent_report_ids"]),
89
+ attributes=decode_attributes(payload["attributes"]),
90
+ spec_version=payload["spec_version"],
91
+ )
@@ -0,0 +1,30 @@
1
+ """The adapter protocol - the ingress boundary for domain reports.
2
+
3
+ Concrete adapters live in the external application (e.g.
4
+ `writer.integrations.zeromodel.AIArtifactReportAdapter`), never in this
5
+ package. An adapter must not write directly to an `ArtifactStore`,
6
+ construct rendered images, or calculate artifact identity itself - it only
7
+ translates one typed domain report into a neutral `AdaptedReportDTO`;
8
+ `compile_report` (see `report_compiler.py`) owns validation and identity.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Generic, Protocol, TypeVar, runtime_checkable
14
+
15
+ from zeromodel.artifacts.report_dto import AdaptedReportDTO, ReportAdapterContractDTO
16
+
17
+ ReportT = TypeVar("ReportT", contravariant=True)
18
+
19
+
20
+ @runtime_checkable
21
+ class ReportAdapter(Protocol, Generic[ReportT]):
22
+ """Domain-owned translation from one typed report into neutral ZeroModel form."""
23
+
24
+ def contract(self) -> ReportAdapterContractDTO:
25
+ """Return the stable contract governing this adapter's translation."""
26
+ ...
27
+
28
+ def adapt(self, report: ReportT) -> AdaptedReportDTO:
29
+ """Convert one domain report into the neutral ZeroModel report form."""
30
+ ...