actproof-events 1.4.0rc1__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 (51) hide show
  1. actproof_events-1.4.0rc1/.gitignore +218 -0
  2. actproof_events-1.4.0rc1/CATALOGUE_LOADER_CONTRACT.md +290 -0
  3. actproof_events-1.4.0rc1/CONTRIBUTING_ACTS.md +187 -0
  4. actproof_events-1.4.0rc1/LICENSE +201 -0
  5. actproof_events-1.4.0rc1/PKG-INFO +50 -0
  6. actproof_events-1.4.0rc1/README.md +18 -0
  7. actproof_events-1.4.0rc1/actproof_events/__init__.py +182 -0
  8. actproof_events-1.4.0rc1/catalogue/acts/actproof/STEP-2-NOTES.md +117 -0
  9. actproof_events-1.4.0rc1/catalogue/acts/actproof/software_release.v1.json +83 -0
  10. actproof_events-1.4.0rc1/catalogue/acts/actproof/software_release.v1.test_vectors.json +107 -0
  11. actproof_events-1.4.0rc1/catalogue/acts/actproof/standards_engagement_record.v1.json +108 -0
  12. actproof_events-1.4.0rc1/catalogue/acts/actproof/standards_engagement_record.v1.test_vectors.json +131 -0
  13. actproof_events-1.4.0rc1/catalogue/acts/corporate/_deprecated/README.md +23 -0
  14. actproof_events-1.4.0rc1/catalogue/acts/corporate/_deprecated/board_resolution_v1.json +46 -0
  15. actproof_events-1.4.0rc1/catalogue/acts/corporate/_deprecated/board_resolution_v1.test_vectors.json +249 -0
  16. actproof_events-1.4.0rc1/catalogue/acts/democracy/civil_society_mandate.settlement.v1.json +83 -0
  17. actproof_events-1.4.0rc1/catalogue/acts/democracy/civil_society_mandate.settlement.v1.test_vectors.json +107 -0
  18. actproof_events-1.4.0rc1/catalogue/acts/eu/ai_act/art26/_deprecated/README.md +23 -0
  19. actproof_events-1.4.0rc1/catalogue/acts/eu/ai_act/art26/_deprecated/risk_assessment.json +46 -0
  20. actproof_events-1.4.0rc1/catalogue/acts/eu/ai_act/art26/_deprecated/risk_assessment.test_vectors.json +296 -0
  21. actproof_events-1.4.0rc1/catalogue/acts/eu/dora/ict_incident_notification_initial.v1.json +142 -0
  22. actproof_events-1.4.0rc1/catalogue/acts/eu/dora/ict_incident_notification_initial.v1.test_vectors.json +136 -0
  23. actproof_events-1.4.0rc1/catalogue/acts/eu/eudr/dds_preparation.v1.json +98 -0
  24. actproof_events-1.4.0rc1/catalogue/acts/eu/eudr/dds_preparation.v1.test_vectors.json +114 -0
  25. actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/_deprecated/README.md +21 -0
  26. actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/_deprecated/approval.json +44 -0
  27. actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/_deprecated/approval.test_vectors.json +219 -0
  28. actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/management_body_approval.v1.json +98 -0
  29. actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/management_body_approval.v1.test_vectors.json +106 -0
  30. actproof_events-1.4.0rc1/docs/releases/v1.4-rc1.md +292 -0
  31. actproof_events-1.4.0rc1/docs/releases/v1.5-rc1.md +96 -0
  32. actproof_events-1.4.0rc1/pyproject.toml +103 -0
  33. actproof_events-1.4.0rc1/salt-erasure/README.md +181 -0
  34. actproof_events-1.4.0rc1/salt-erasure/salt_erasure.py +413 -0
  35. actproof_events-1.4.0rc1/salt-erasure/test_vectors.json +197 -0
  36. actproof_events-1.4.0rc1/scripts/compute_test_vectors.py +298 -0
  37. actproof_events-1.4.0rc1/scripts/deploy-v1.4-rc1.sh +453 -0
  38. actproof_events-1.4.0rc1/scripts/test_vector_inputs/README.md +46 -0
  39. actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_software_release_v1_001.json +53 -0
  40. actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_standards_engagement_record_v1_001.json +69 -0
  41. actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_standards_engagement_record_v1_001_evidence/implementation_repository_state.txt +34 -0
  42. actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_standards_engagement_record_v1_001_evidence/working_group_charter_reference.txt +39 -0
  43. actproof_events-1.4.0rc1/scripts/test_vector_inputs/democracy_civil_society_mandate_settlement_v1_001.json +53 -0
  44. actproof_events-1.4.0rc1/scripts/test_vector_inputs/eu_dora_ict_incident_notification_initial_v1_001.json +70 -0
  45. actproof_events-1.4.0rc1/scripts/test_vector_inputs/eu_dora_ict_incident_notification_initial_v1_001_evidence/classification_committee_record.txt +107 -0
  46. actproof_events-1.4.0rc1/scripts/test_vector_inputs/eu_dora_ict_incident_notification_initial_v1_001_evidence/detection_system_log_excerpt.txt +83 -0
  47. actproof_events-1.4.0rc1/scripts/test_vector_inputs/eudr_dds_v1_001.json +60 -0
  48. actproof_events-1.4.0rc1/scripts/test_vector_inputs/nis2_art20_v1_001.json +52 -0
  49. actproof_events-1.4.0rc1/spec/actproof-events.spec.md +472 -0
  50. actproof_events-1.4.0rc1/spec/schemas/act_catalogue_entry.v2.json +185 -0
  51. actproof_events-1.4.0rc1/spec/schemas/act_catalogue_entry.v3.json +357 -0
@@ -0,0 +1,218 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
@@ -0,0 +1,290 @@
1
+ # Catalogue loader contract
2
+
3
+ This document is the binding contract between the ActProof Events substrate and any implementation that loads catalogue entries from the substrate's filesystem layout. Implementations that satisfy this contract are conforming catalogue loaders for v1.4 of the specification.
4
+
5
+ The contract is intentionally implementation-agnostic. The reference implementation in Quoruna is written in Python and lives at `catalogue_loader.py` in the Quoruna repository. Future implementations in other languages (Go, Rust, TypeScript) MUST satisfy the same contract to be considered conforming.
6
+
7
+ ## Scope
8
+
9
+ This document covers:
10
+
11
+ - How a loader reads catalogue entries from the filesystem
12
+ - How a loader distinguishes v2 entries from deprecated v1 entries
13
+ - How a loader validates a candidate manifest against a catalogue entry
14
+ - How a loader caches and reloads catalogue entries
15
+ - How a loader reports errors
16
+
17
+ This document does NOT cover:
18
+
19
+ - The transport layer between the loader and any UI or API surface
20
+ - The persistence layer for issued attestations (that lives in the consuming implementation)
21
+ - The signing or anchoring pipeline (that is downstream of the loader)
22
+ - Any internal database schema choices
23
+
24
+ ## Terminology
25
+
26
+ The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL are interpreted per RFC 2119.
27
+
28
+ **Catalogue entry** refers to a JSON document conforming to `actproof.act_catalogue_entry.v2` (defined in spec Section 2.1).
29
+
30
+ **v1 entry** refers to a JSON document under any `_deprecated/` directory that uses the predecessor voting-shaped schema.
31
+
32
+ **Manifest** refers to a candidate `actproof.attestation_manifest.v1` document (defined in spec Section 3).
33
+
34
+ **New issuance** means the loader is being asked to surface or accept an act_type_id for the purpose of creating a new attestation.
35
+
36
+ **Historical rendering** means the loader is being asked to resolve an act_type_id that was issued previously, in order to render a receipt or verification page for an existing attestation.
37
+
38
+ ## 1. Loading requirements
39
+
40
+ ### 1.1 Surfacing v2 entries
41
+
42
+ The loader MUST surface only v2 entries for new issuance. A v2 entry is one whose `schema` field equals the literal string `"actproof.act_catalogue_entry.v2"`.
43
+
44
+ The loader MUST validate each loaded entry against the JSON Schema at `spec/schemas/act_catalogue_entry.v2.json`. Entries that fail schema validation MUST NOT be surfaced for new issuance and SHOULD be logged at warning level for operator attention.
45
+
46
+ ### 1.2 Refusing v1 entries for new issuance
47
+
48
+ The loader MUST refuse to surface entries from any `_deprecated/` directory for new issuance. Specifically:
49
+
50
+ - Any catalogue file whose path contains a `_deprecated/` segment MUST NOT appear in the list of available act types for new attestation creation.
51
+ - Any attempt to begin a new attestation against a v1 act_type_id (e.g., `op:eu.nis2.art20.approval`) MUST be rejected with a clear error message indicating the v1 entry has been superseded.
52
+
53
+ ### 1.3 Allowing v1 read-only access for historical rendering
54
+
55
+ The loader MUST allow read-only access to v1 entries when resolving an act_type_id that was used in a previously committed attestation. This requirement preserves the namespace and ensures that historical receipts continue to render correctly.
56
+
57
+ The loader SHOULD distinguish at the API boundary between "list act types for new issuance" (returns only v2 entries) and "resolve act type for historical rendering" (returns v2 and v1 entries). Reference implementations expose two distinct functions for this purpose; see Section 6.
58
+
59
+ ### 1.4 Path discovery
60
+
61
+ The loader MUST discover catalogue entries by walking the `catalogue/` directory tree. The discovery rules are:
62
+
63
+ - Files ending in `.json` are candidate catalogue entries.
64
+ - Files under a `_deprecated/` subdirectory are v1 entries (read-only, historical access only).
65
+ - Files under any other path are v2 entries (subject to v2 schema validation).
66
+ - Files ending in `.test_vectors.json` are NOT catalogue entries and MUST be ignored by the loader.
67
+ - `README.md` files and other non-JSON files MUST be ignored.
68
+
69
+ The loader MUST treat the `catalogue/` directory as the root. Catalogue entries deeper in the tree (under `catalogue/acts/eu/nis2/art20/` etc.) are discovered recursively.
70
+
71
+ ## 2. Validation requirements
72
+
73
+ When a candidate manifest is submitted for transition to `awaiting_commit` state, the loader MUST perform the following validations against the corresponding catalogue entry. Failure of any validation MUST block the transition and return a structured error.
74
+
75
+ ### 2.1 act_type_id match
76
+
77
+ The manifest's `act_type_id` MUST exactly match the catalogue entry's `act_type_id`. Case-sensitive comparison.
78
+
79
+ ### 2.2 catalogue_entry_version match
80
+
81
+ The manifest's `catalogue_entry_version` MUST exactly match the catalogue entry's `version`. Mismatches indicate the manifest was created against a different version of the catalogue entry; the consuming implementation MUST either re-validate against the current version or surface the version mismatch as a structured error.
82
+
83
+ ### 2.3 required_claim_fields presence
84
+
85
+ For every identifier in the catalogue entry's `required_claim_fields`, the manifest's `claim_fields` object MUST contain a key with the same identifier. The value of the key MUST NOT be null, empty string, or empty array.
86
+
87
+ The loader MUST report each missing required field individually, not as a single aggregate error, so the issuer can correct all gaps in a single revision cycle.
88
+
89
+ ### 2.4 required_evidence_labels coverage
90
+
91
+ For every identifier in the catalogue entry's `required_evidence_labels`, the manifest's `evidence` array MUST contain at least one item whose `label` equals that identifier. The corresponding evidence item MUST have a non-null `sha256_hex` value.
92
+
93
+ The loader MUST report each missing required evidence label individually.
94
+
95
+ ### 2.5 Recipient role validation
96
+
97
+ For every recipient in the manifest's `recipients` array, the recipient's `role` SHOULD match an identifier in the catalogue entry's `recommended_witness_roles`. Recipients with non-recommended roles MAY be permitted but the loader MUST flag them as non-standard in a warning that the consuming implementation surfaces to the issuer.
98
+
99
+ The loader MUST validate that every recipient has a syntactically valid email address in the `email` field. Invalid email addresses MUST be reported as structured errors.
100
+
101
+ The loader MUST validate that no two recipients share the same `(role, email)` pair. Duplicate designations MUST be reported as structured errors.
102
+
103
+ ### 2.6 signature_policy compatibility
104
+
105
+ If the catalogue entry's `signature_policy.minimum` is `external_signature`, the manifest MUST include at least one evidence item whose label appears in the catalogue entry's `signature_policy.supports` list. If the manifest contains only `issuer_record`-equivalent evidence, the loader MUST block the transition.
106
+
107
+ If the catalogue entry's `signature_policy.minimum` is `issuer_record` or `either`, the loader MUST permit transition without external signature evidence (the platform-recorded commit action satisfies the floor).
108
+
109
+ ## 3. Caching requirements
110
+
111
+ ### 3.1 Startup load
112
+
113
+ The loader MUST load and cache all catalogue entries in memory at process startup. After successful startup, no catalogue entry SHALL be read from the filesystem on the path of a request.
114
+
115
+ ### 3.2 Explicit reload
116
+
117
+ The loader MUST support an explicit reload operation triggered by an operator signal. Acceptable signal mechanisms include:
118
+
119
+ - A POSIX signal (e.g., `SIGHUP`) handled by the process
120
+ - An admin HTTP endpoint that triggers an in-process reload
121
+ - A scheduled reload at known process restart points (e.g., daily deploy)
122
+
123
+ The loader MUST NOT reload catalogue entries based on file modification time, inotify events, or any other implicit trigger. All reloads MUST be operator-initiated.
124
+
125
+ ### 3.3 Atomicity
126
+
127
+ The reload operation MUST be atomic with respect to concurrent reads. A consuming request that is mid-flight when reload begins MUST observe either the pre-reload catalogue or the post-reload catalogue in its entirety, never a mixed state.
128
+
129
+ The reference implementation accomplishes this by computing the new catalogue cache fully in a temporary structure, then atomically swapping the active cache pointer.
130
+
131
+ ### 3.4 Reload failure handling
132
+
133
+ If reload fails (e.g., a newly added catalogue entry fails schema validation), the loader MUST retain the previous valid cache and surface the failure as a structured error. A failed reload MUST NOT leave the loader in an unloaded or partially-loaded state.
134
+
135
+ ## 4. Error reporting
136
+
137
+ The loader MUST report validation errors as structured objects, not as opaque strings. The recommended structure is:
138
+
139
+ ```
140
+ {
141
+ "error_type": "REQUIRED_CLAIM_FIELD_MISSING",
142
+ "field_path": "claim_fields.approving_body_name",
143
+ "act_type_id": "op:eu.nis2.art20.management_body_approval.v1",
144
+ "catalogue_entry_version": 1,
145
+ "human_message": "The required claim field 'approving_body_name' is missing from the manifest."
146
+ }
147
+ ```
148
+
149
+ Each structured error MUST include:
150
+
151
+ - `error_type`: an enumerated identifier from the error taxonomy in Section 4.1
152
+ - `human_message`: a human-readable explanation suitable for display to the issuer
153
+
154
+ Each structured error SHOULD include where applicable:
155
+
156
+ - `field_path`: a JSON pointer-like path identifying the offending field
157
+ - `act_type_id` and `catalogue_entry_version` for context
158
+ - `expected` and `actual` values for mismatches
159
+ - `valid_options` listing the permissible values when applicable
160
+
161
+ ### 4.1 Error taxonomy
162
+
163
+ The loader MUST recognise and report the following error types:
164
+
165
+ - `CATALOGUE_ENTRY_NOT_FOUND`: requested act_type_id does not match any loaded entry
166
+ - `CATALOGUE_ENTRY_DEPRECATED`: requested act_type_id is a v1 entry; new issuance refused
167
+ - `CATALOGUE_ENTRY_VERSION_MISMATCH`: manifest's catalogue_entry_version does not match catalogue
168
+ - `REQUIRED_CLAIM_FIELD_MISSING`: a required claim field is absent or empty
169
+ - `REQUIRED_EVIDENCE_LABEL_MISSING`: no evidence item carries a required label
170
+ - `RECIPIENT_ROLE_NOT_RECOMMENDED`: a recipient's role is not in recommended_witness_roles (warning, not error, unless implementation enforces strict mode)
171
+ - `RECIPIENT_EMAIL_INVALID`: a recipient's email is malformed
172
+ - `RECIPIENT_DUPLICATE_DESIGNATION`: two recipients share the same (role, email) pair
173
+ - `SIGNATURE_POLICY_VIOLATED`: catalogue requires external signature but manifest provides none
174
+ - `CATALOGUE_ENTRY_SCHEMA_INVALID`: a catalogue entry on disk fails v2 schema validation (operator-facing error at startup or reload)
175
+
176
+ ## 5. Concurrency
177
+
178
+ The loader's read operations (resolve catalogue entry, validate manifest) MUST be safe for concurrent invocation. Implementations MAY achieve concurrency safety by treating the cached catalogue as immutable after each reload and using atomic pointer swap for updates.
179
+
180
+ The loader's reload operation SHOULD be safe to invoke concurrently with read operations as described in Section 3.3. The loader MAY serialize concurrent reload calls to prevent redundant work.
181
+
182
+ ## 6. Recommended interface
183
+
184
+ Reference implementations are RECOMMENDED to expose at least the following operations. Type signatures are written in Python-flavoured pseudocode for clarity; equivalent signatures in other languages are conforming.
185
+
186
+ ```python
187
+ class CatalogueLoader:
188
+ def load_all(self, catalogue_root: Path) -> None:
189
+ """
190
+ Discover and cache all catalogue entries. Called at process startup
191
+ and on explicit reload signal.
192
+ """
193
+
194
+ def list_act_types_for_issuance(self) -> list[ActTypeDescriptor]:
195
+ """
196
+ Return the list of v2 act types currently available for new
197
+ attestation creation. v1 entries are excluded.
198
+ """
199
+
200
+ def resolve_for_issuance(self, act_type_id: str) -> CatalogueEntry:
201
+ """
202
+ Return the v2 catalogue entry matching act_type_id. Raises
203
+ CatalogueEntryNotFound if no match exists, or CatalogueEntryDeprecated
204
+ if the match is a v1 entry.
205
+ """
206
+
207
+ def resolve_for_history(self, act_type_id: str) -> CatalogueEntry:
208
+ """
209
+ Return the catalogue entry matching act_type_id, including v1 entries.
210
+ Used for rendering receipts of attestations issued before the v2
211
+ migration.
212
+ """
213
+
214
+ def validate_manifest(
215
+ self,
216
+ manifest: dict,
217
+ catalogue_entry: CatalogueEntry,
218
+ ) -> list[ValidationError]:
219
+ """
220
+ Validate a candidate manifest against a catalogue entry. Returns a
221
+ list of structured errors; empty list means the manifest is valid
222
+ for transition to awaiting_commit.
223
+ """
224
+
225
+ def reload(self) -> ReloadResult:
226
+ """
227
+ Re-discover and re-cache all catalogue entries atomically. Returns
228
+ a summary of what changed.
229
+ """
230
+ ```
231
+
232
+ Implementations MAY add additional operations beyond this minimum (e.g., bulk validation, partial reload, schema diff). Such extensions MUST NOT change the semantics of the operations above.
233
+
234
+ ## 7. Test scenarios
235
+
236
+ A conforming loader implementation MUST pass at least the following test scenarios. The reference implementation in Quoruna includes these as integration tests under `tests/test_catalogue_loader.py` (added in Batch B).
237
+
238
+ ### Scenario A: Successful v2 load
239
+
240
+ Given a clean catalogue directory containing `management_body_approval.v1.json` and `dds_preparation.v1.json`, the loader's `load_all` succeeds, `list_act_types_for_issuance` returns both entries, and `resolve_for_issuance` returns the correct entry for each act_type_id.
241
+
242
+ ### Scenario B: v1 namespace preservation
243
+
244
+ Given a catalogue directory containing the v1 `_deprecated/approval.json` alongside the v2 `management_body_approval.v1.json`:
245
+
246
+ - `list_act_types_for_issuance` returns only the v2 entry.
247
+ - `resolve_for_issuance("op:eu.nis2.art20.approval")` raises CatalogueEntryDeprecated.
248
+ - `resolve_for_history("op:eu.nis2.art20.approval")` succeeds and returns the v1 entry.
249
+
250
+ ### Scenario C: Missing required claim field
251
+
252
+ Given a manifest against `management_body_approval.v1` that omits `responsible_officers`, `validate_manifest` returns a single structured error of type `REQUIRED_CLAIM_FIELD_MISSING` with `field_path: "claim_fields.responsible_officers"`.
253
+
254
+ ### Scenario D: Missing required evidence label
255
+
256
+ Given a manifest against `dds_preparation.v1` that includes only the `geojson_plot_geometries` evidence and omits `due_diligence_screening_report`, `validate_manifest` returns a `REQUIRED_EVIDENCE_LABEL_MISSING` error for the missing label.
257
+
258
+ ### Scenario E: Duplicate recipient
259
+
260
+ Given a manifest with two recipients sharing the same `(role, email)`, `validate_manifest` returns a `RECIPIENT_DUPLICATE_DESIGNATION` error identifying both indices.
261
+
262
+ ### Scenario F: Non-recommended role warning
263
+
264
+ Given a manifest with a recipient whose role is not in `recommended_witness_roles`, `validate_manifest` returns a warning (not an error) of type `RECIPIENT_ROLE_NOT_RECOMMENDED`. The transition to `awaiting_commit` is permitted but the consuming implementation MUST surface the warning to the issuer.
265
+
266
+ ### Scenario G: Invalid catalogue entry on disk
267
+
268
+ Given a catalogue directory where one of the JSON files fails v2 schema validation, `load_all` succeeds for the valid entries, logs the failure at warning level, and `list_act_types_for_issuance` returns only the valid v2 entries. The invalid entry is NOT surfaced.
269
+
270
+ ### Scenario H: Reload preserves prior cache on failure
271
+
272
+ After successful initial load, given a `reload` call where one of the catalogue files has been corrupted, the reload fails atomically. The loader's existing cache remains active and serving requests. The failure is reported as a structured error.
273
+
274
+ ### Scenario I: No filesystem read on issuance path
275
+
276
+ A profiling test confirms that calling `resolve_for_issuance` for any cached act_type_id triggers zero filesystem reads after the initial `load_all` completes.
277
+
278
+ ## 8. Reference implementation
279
+
280
+ The reference implementation lives in the Quoruna repository at `catalogue_loader.py`. It is implemented in Python with `asyncpg` for any database-side persistence concerns and `jsonschema` for v2 schema validation. The full implementation lands in Quoruna Batch B alongside the attestation schema and the issuer flow.
281
+
282
+ Future implementations in other languages may diverge in idioms, frameworks, and persistence choices, but MUST satisfy the contract documented here to be considered conforming.
283
+
284
+ ## 9. Versioning
285
+
286
+ This contract is bound to v1.4 of the ActProof Events specification. Substrate changes that affect the loader contract (new schema versions, new validation requirements, new error types) will be accompanied by a new version of this contract. Implementations that satisfy the v1.4 contract are NOT automatically conforming to future contract versions; each substrate release MUST be reviewed against the corresponding contract.
287
+
288
+ ---
289
+
290
+ *This contract is licensed CC0. Any implementation may incorporate or paraphrase this text without attribution.*