conclear 1.0.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 (269) hide show
  1. conclear-1.0.0/ARCHITECTURE.md +1813 -0
  2. conclear-1.0.0/CONTRIBUTING.md +92 -0
  3. conclear-1.0.0/DEVELOPMENT.md +1263 -0
  4. conclear-1.0.0/LICENSES/GPL-3.0-or-later.txt +232 -0
  5. conclear-1.0.0/PKG-INFO +735 -0
  6. conclear-1.0.0/README.md +696 -0
  7. conclear-1.0.0/REUSE.toml +20 -0
  8. conclear-1.0.0/docs/backup.md +132 -0
  9. conclear-1.0.0/docs/compatibility-inventory.json +2652 -0
  10. conclear-1.0.0/docs/conformance.md +712 -0
  11. conclear-1.0.0/docs/distributed-qualification.md +58 -0
  12. conclear-1.0.0/docs/implementation.md +51 -0
  13. conclear-1.0.0/pyproject.toml +171 -0
  14. conclear-1.0.0/pyproject.toml.orig +170 -0
  15. conclear-1.0.0/src/conclear/__init__.py +5 -0
  16. conclear-1.0.0/src/conclear/__main__.py +6 -0
  17. conclear-1.0.0/src/conclear/_development_identity.py +3 -0
  18. conclear-1.0.0/src/conclear/_embedded_identity.py +3 -0
  19. conclear-1.0.0/src/conclear/adapters/__init__.py +1 -0
  20. conclear-1.0.0/src/conclear/adapters/base.py +107 -0
  21. conclear-1.0.0/src/conclear/adapters/buildah.py +128 -0
  22. conclear-1.0.0/src/conclear/adapters/ci.py +198 -0
  23. conclear-1.0.0/src/conclear/adapters/cosign.py +475 -0
  24. conclear-1.0.0/src/conclear/adapters/git.py +127 -0
  25. conclear-1.0.0/src/conclear/adapters/hadolint.py +84 -0
  26. conclear-1.0.0/src/conclear/adapters/podman.py +661 -0
  27. conclear-1.0.0/src/conclear/adapters/quay.py +492 -0
  28. conclear-1.0.0/src/conclear/adapters/registry_backends.py +100 -0
  29. conclear-1.0.0/src/conclear/adapters/skopeo.py +193 -0
  30. conclear-1.0.0/src/conclear/adapters/trivy.py +402 -0
  31. conclear-1.0.0/src/conclear/archive.py +340 -0
  32. conclear-1.0.0/src/conclear/archive_source.py +113 -0
  33. conclear-1.0.0/src/conclear/artifacts.py +710 -0
  34. conclear-1.0.0/src/conclear/attestations.py +111 -0
  35. conclear-1.0.0/src/conclear/build_identity.py +18 -0
  36. conclear-1.0.0/src/conclear/catalog.py +415 -0
  37. conclear-1.0.0/src/conclear/checks.py +687 -0
  38. conclear-1.0.0/src/conclear/cli.py +229 -0
  39. conclear-1.0.0/src/conclear/commands/__init__.py +1 -0
  40. conclear-1.0.0/src/conclear/commands/adopt.py +64 -0
  41. conclear-1.0.0/src/conclear/commands/archive.py +143 -0
  42. conclear-1.0.0/src/conclear/commands/common.py +209 -0
  43. conclear-1.0.0/src/conclear/commands/configuration.py +39 -0
  44. conclear-1.0.0/src/conclear/commands/local.py +540 -0
  45. conclear-1.0.0/src/conclear/commands/maintenance.py +836 -0
  46. conclear-1.0.0/src/conclear/commands/remote.py +462 -0
  47. conclear-1.0.0/src/conclear/commands/transport.py +88 -0
  48. conclear-1.0.0/src/conclear/commands/version.py +32 -0
  49. conclear-1.0.0/src/conclear/compatibility_inventory.py +260 -0
  50. conclear-1.0.0/src/conclear/config.py +1611 -0
  51. conclear-1.0.0/src/conclear/config_decisions.py +53 -0
  52. conclear-1.0.0/src/conclear/conformance.py +37 -0
  53. conclear-1.0.0/src/conclear/containerfile.py +325 -0
  54. conclear-1.0.0/src/conclear/context.py +381 -0
  55. conclear-1.0.0/src/conclear/data/__init__.py +1 -0
  56. conclear-1.0.0/src/conclear/data/checks.json +76 -0
  57. conclear-1.0.0/src/conclear/data/guide-options.json +133 -0
  58. conclear-1.0.0/src/conclear/data/guide-requirements.json +3045 -0
  59. conclear-1.0.0/src/conclear/data/implementation.json +408 -0
  60. conclear-1.0.0/src/conclear/data/requirement-coverage.json +244 -0
  61. conclear-1.0.0/src/conclear/database.py +129 -0
  62. conclear-1.0.0/src/conclear/dependencies.py +267 -0
  63. conclear-1.0.0/src/conclear/emulation.py +138 -0
  64. conclear-1.0.0/src/conclear/errors.py +119 -0
  65. conclear-1.0.0/src/conclear/fileio.py +119 -0
  66. conclear-1.0.0/src/conclear/freshness.py +146 -0
  67. conclear-1.0.0/src/conclear/guide_options.py +200 -0
  68. conclear-1.0.0/src/conclear/guide_requirements.py +567 -0
  69. conclear-1.0.0/src/conclear/hook_scratch.py +132 -0
  70. conclear-1.0.0/src/conclear/hooks.py +139 -0
  71. conclear-1.0.0/src/conclear/identity.py +77 -0
  72. conclear-1.0.0/src/conclear/implementation.py +367 -0
  73. conclear-1.0.0/src/conclear/jsonutil.py +158 -0
  74. conclear-1.0.0/src/conclear/layout_assembly.py +269 -0
  75. conclear-1.0.0/src/conclear/oci.py +480 -0
  76. conclear-1.0.0/src/conclear/parsing.py +97 -0
  77. conclear-1.0.0/src/conclear/path_safety.py +135 -0
  78. conclear-1.0.0/src/conclear/pin_application.py +393 -0
  79. conclear-1.0.0/src/conclear/pin_occurrences.py +292 -0
  80. conclear-1.0.0/src/conclear/pin_updates.py +572 -0
  81. conclear-1.0.0/src/conclear/pins.py +327 -0
  82. conclear-1.0.0/src/conclear/presentation.py +101 -0
  83. conclear-1.0.0/src/conclear/process.py +532 -0
  84. conclear-1.0.0/src/conclear/provenance.py +129 -0
  85. conclear-1.0.0/src/conclear/records.py +223 -0
  86. conclear-1.0.0/src/conclear/registry_control.py +101 -0
  87. conclear-1.0.0/src/conclear/registry_policy.py +165 -0
  88. conclear-1.0.0/src/conclear/release_check.py +737 -0
  89. conclear-1.0.0/src/conclear/release_profile.py +362 -0
  90. conclear-1.0.0/src/conclear/rescan_history.py +337 -0
  91. conclear-1.0.0/src/conclear/runtime.py +204 -0
  92. conclear-1.0.0/src/conclear/runtime_directory.py +197 -0
  93. conclear-1.0.0/src/conclear/scan_identity.py +86 -0
  94. conclear-1.0.0/src/conclear/scan_policy.py +508 -0
  95. conclear-1.0.0/src/conclear/schema.py +125 -0
  96. conclear-1.0.0/src/conclear/schemas/__init__.py +1 -0
  97. conclear-1.0.0/src/conclear/schemas/archive.schema.json +40 -0
  98. conclear-1.0.0/src/conclear/schemas/config.schema.json +398 -0
  99. conclear-1.0.0/src/conclear/schemas/profile.schema.json +128 -0
  100. conclear-1.0.0/src/conclear/schemas/proposal.schema.json +113 -0
  101. conclear-1.0.0/src/conclear/schemas/provenance.schema.json +99 -0
  102. conclear-1.0.0/src/conclear/schemas/record.schema.json +869 -0
  103. conclear-1.0.0/src/conclear/schemas/result.schema.json +2482 -0
  104. conclear-1.0.0/src/conclear/schemas/triage.schema.json +43 -0
  105. conclear-1.0.0/src/conclear/secrets.py +127 -0
  106. conclear-1.0.0/src/conclear/services/__init__.py +1 -0
  107. conclear-1.0.0/src/conclear/services/adoption.py +126 -0
  108. conclear-1.0.0/src/conclear/services/adoption_draft.py +443 -0
  109. conclear-1.0.0/src/conclear/services/adoption_observation.py +459 -0
  110. conclear-1.0.0/src/conclear/services/archive_rescan.py +130 -0
  111. conclear-1.0.0/src/conclear/services/archives.py +519 -0
  112. conclear-1.0.0/src/conclear/services/assembly.py +788 -0
  113. conclear-1.0.0/src/conclear/services/attestation.py +567 -0
  114. conclear-1.0.0/src/conclear/services/attestation_reads.py +41 -0
  115. conclear-1.0.0/src/conclear/services/checking.py +89 -0
  116. conclear-1.0.0/src/conclear/services/ci_context.py +135 -0
  117. conclear-1.0.0/src/conclear/services/cleanup.py +462 -0
  118. conclear-1.0.0/src/conclear/services/configuration_view.py +309 -0
  119. conclear-1.0.0/src/conclear/services/doctor.py +159 -0
  120. conclear-1.0.0/src/conclear/services/local_phases.py +105 -0
  121. conclear-1.0.0/src/conclear/services/preflight.py +178 -0
  122. conclear-1.0.0/src/conclear/services/privilege_tests.py +392 -0
  123. conclear-1.0.0/src/conclear/services/promotion.py +335 -0
  124. conclear-1.0.0/src/conclear/services/publication.py +428 -0
  125. conclear-1.0.0/src/conclear/services/qualification.py +724 -0
  126. conclear-1.0.0/src/conclear/services/qualification_inputs.py +128 -0
  127. conclear-1.0.0/src/conclear/services/registry_diagnostics.py +210 -0
  128. conclear-1.0.0/src/conclear/services/release.py +672 -0
  129. conclear-1.0.0/src/conclear/services/rescan.py +675 -0
  130. conclear-1.0.0/src/conclear/services/rescan_evidence.py +145 -0
  131. conclear-1.0.0/src/conclear/services/run_context.py +340 -0
  132. conclear-1.0.0/src/conclear/services/runtime_controls.py +133 -0
  133. conclear-1.0.0/src/conclear/services/runtime_lifecycle.py +804 -0
  134. conclear-1.0.0/src/conclear/services/runtime_tests.py +795 -0
  135. conclear-1.0.0/src/conclear/services/verification.py +363 -0
  136. conclear-1.0.0/src/conclear/setid_inventory.py +303 -0
  137. conclear-1.0.0/src/conclear/source_integrity.py +161 -0
  138. conclear-1.0.0/src/conclear/spdx.py +174 -0
  139. conclear-1.0.0/src/conclear/test_inputs.py +309 -0
  140. conclear-1.0.0/src/conclear/test_output_archive.py +92 -0
  141. conclear-1.0.0/src/conclear/tool_matrix.py +93 -0
  142. conclear-1.0.0/src/conclear/tools.py +324 -0
  143. conclear-1.0.0/src/conclear/transport.py +981 -0
  144. conclear-1.0.0/src/conclear/triage.py +110 -0
  145. conclear-1.0.0/src/conclear/values.py +264 -0
  146. conclear-1.0.0/src/conclear/version_sources.py +148 -0
  147. conclear-1.0.0/src/conclear/workspace.py +628 -0
  148. conclear-1.0.0/tests/__init__.py +1 -0
  149. conclear-1.0.0/tests/conftest.py +70 -0
  150. conclear-1.0.0/tests/local_integration/__init__.py +0 -0
  151. conclear-1.0.0/tests/local_integration/conftest.py +50 -0
  152. conclear-1.0.0/tests/local_integration/fixtures.py +255 -0
  153. conclear-1.0.0/tests/local_integration/sudo_fixture/Containerfile +7 -0
  154. conclear-1.0.0/tests/local_integration/sudo_fixture/sudoers +1 -0
  155. conclear-1.0.0/tests/local_integration/test_adapter_failures.py +595 -0
  156. conclear-1.0.0/tests/local_integration/test_archive_signatures.py +107 -0
  157. conclear-1.0.0/tests/local_integration/test_backup_recipe.py +283 -0
  158. conclear-1.0.0/tests/local_integration/test_containerfile.py +119 -0
  159. conclear-1.0.0/tests/local_integration/test_distribution_recipe.py +58 -0
  160. conclear-1.0.0/tests/local_integration/test_emulation.py +134 -0
  161. conclear-1.0.0/tests/local_integration/test_source_integrity.py +189 -0
  162. conclear-1.0.0/tests/local_integration/test_sudo.py +88 -0
  163. conclear-1.0.0/tests/local_integration/test_tools.py +816 -0
  164. conclear-1.0.0/tests/local_integration/test_transport_cli.py +487 -0
  165. conclear-1.0.0/tests/local_integration/test_trivy_database.py +317 -0
  166. conclear-1.0.0/tests/local_integration/test_trivy_policy.py +171 -0
  167. conclear-1.0.0/tests/network/test_cosign_attestation.py +100 -0
  168. conclear-1.0.0/tests/network/test_quay_candidate.py +102 -0
  169. conclear-1.0.0/tests/network/test_release_lifecycle.py +188 -0
  170. conclear-1.0.0/tests/network_support.py +103 -0
  171. conclear-1.0.0/tests/registry_policy_fixtures.py +22 -0
  172. conclear-1.0.0/tests/release_fakes.py +731 -0
  173. conclear-1.0.0/tests/release_scenarios.py +181 -0
  174. conclear-1.0.0/tests/unit/test_adapters.py +2130 -0
  175. conclear-1.0.0/tests/unit/test_adoption.py +704 -0
  176. conclear-1.0.0/tests/unit/test_archives.py +890 -0
  177. conclear-1.0.0/tests/unit/test_assembly.py +373 -0
  178. conclear-1.0.0/tests/unit/test_assembly_verification.py +830 -0
  179. conclear-1.0.0/tests/unit/test_attestation_binding.py +155 -0
  180. conclear-1.0.0/tests/unit/test_attestations.py +67 -0
  181. conclear-1.0.0/tests/unit/test_candidate_retention.py +231 -0
  182. conclear-1.0.0/tests/unit/test_catalog.py +105 -0
  183. conclear-1.0.0/tests/unit/test_catalog_loading.py +181 -0
  184. conclear-1.0.0/tests/unit/test_checks.py +798 -0
  185. conclear-1.0.0/tests/unit/test_ci_context.py +346 -0
  186. conclear-1.0.0/tests/unit/test_cleanup.py +612 -0
  187. conclear-1.0.0/tests/unit/test_cleanup_ownership.py +297 -0
  188. conclear-1.0.0/tests/unit/test_cli.py +442 -0
  189. conclear-1.0.0/tests/unit/test_commands.py +2138 -0
  190. conclear-1.0.0/tests/unit/test_compatibility_inventory.py +167 -0
  191. conclear-1.0.0/tests/unit/test_config.py +1485 -0
  192. conclear-1.0.0/tests/unit/test_config_rejections.py +389 -0
  193. conclear-1.0.0/tests/unit/test_configuration_view.py +337 -0
  194. conclear-1.0.0/tests/unit/test_containerfile.py +267 -0
  195. conclear-1.0.0/tests/unit/test_context.py +163 -0
  196. conclear-1.0.0/tests/unit/test_dependencies.py +192 -0
  197. conclear-1.0.0/tests/unit/test_doctor.py +307 -0
  198. conclear-1.0.0/tests/unit/test_emulation.py +133 -0
  199. conclear-1.0.0/tests/unit/test_evidence_retention.py +145 -0
  200. conclear-1.0.0/tests/unit/test_fileio.py +65 -0
  201. conclear-1.0.0/tests/unit/test_freshness.py +384 -0
  202. conclear-1.0.0/tests/unit/test_guide_options.py +162 -0
  203. conclear-1.0.0/tests/unit/test_guide_requirements.py +498 -0
  204. conclear-1.0.0/tests/unit/test_hook_scratch.py +171 -0
  205. conclear-1.0.0/tests/unit/test_hooks.py +50 -0
  206. conclear-1.0.0/tests/unit/test_identity.py +52 -0
  207. conclear-1.0.0/tests/unit/test_implementation.py +231 -0
  208. conclear-1.0.0/tests/unit/test_jsonutil.py +22 -0
  209. conclear-1.0.0/tests/unit/test_layout_assembly_errors.py +134 -0
  210. conclear-1.0.0/tests/unit/test_lifecycle_cli.py +54 -0
  211. conclear-1.0.0/tests/unit/test_local_phases.py +108 -0
  212. conclear-1.0.0/tests/unit/test_network_support.py +171 -0
  213. conclear-1.0.0/tests/unit/test_oci.py +119 -0
  214. conclear-1.0.0/tests/unit/test_parsing.py +59 -0
  215. conclear-1.0.0/tests/unit/test_path_confinement.py +155 -0
  216. conclear-1.0.0/tests/unit/test_path_safety.py +111 -0
  217. conclear-1.0.0/tests/unit/test_pin_updates.py +855 -0
  218. conclear-1.0.0/tests/unit/test_pins.py +124 -0
  219. conclear-1.0.0/tests/unit/test_pins_cli.py +403 -0
  220. conclear-1.0.0/tests/unit/test_pins_state.py +201 -0
  221. conclear-1.0.0/tests/unit/test_preflight.py +289 -0
  222. conclear-1.0.0/tests/unit/test_privilege_contracts.py +629 -0
  223. conclear-1.0.0/tests/unit/test_process.py +402 -0
  224. conclear-1.0.0/tests/unit/test_provenance.py +61 -0
  225. conclear-1.0.0/tests/unit/test_publication.py +1047 -0
  226. conclear-1.0.0/tests/unit/test_publication_resume.py +259 -0
  227. conclear-1.0.0/tests/unit/test_publication_retry.py +174 -0
  228. conclear-1.0.0/tests/unit/test_qualification.py +2544 -0
  229. conclear-1.0.0/tests/unit/test_readme.py +98 -0
  230. conclear-1.0.0/tests/unit/test_records.py +149 -0
  231. conclear-1.0.0/tests/unit/test_registry_backends.py +67 -0
  232. conclear-1.0.0/tests/unit/test_registry_diagnostics.py +239 -0
  233. conclear-1.0.0/tests/unit/test_registry_policy.py +381 -0
  234. conclear-1.0.0/tests/unit/test_release.py +433 -0
  235. conclear-1.0.0/tests/unit/test_release_check.py +238 -0
  236. conclear-1.0.0/tests/unit/test_release_check_gate.py +412 -0
  237. conclear-1.0.0/tests/unit/test_release_profile.py +454 -0
  238. conclear-1.0.0/tests/unit/test_release_scenarios.py +195 -0
  239. conclear-1.0.0/tests/unit/test_release_workflow.py +579 -0
  240. conclear-1.0.0/tests/unit/test_rescan.py +842 -0
  241. conclear-1.0.0/tests/unit/test_rescan_evidence.py +313 -0
  242. conclear-1.0.0/tests/unit/test_rescan_history.py +229 -0
  243. conclear-1.0.0/tests/unit/test_rescan_history_state.py +147 -0
  244. conclear-1.0.0/tests/unit/test_run_context.py +617 -0
  245. conclear-1.0.0/tests/unit/test_runtime.py +214 -0
  246. conclear-1.0.0/tests/unit/test_runtime_controls.py +121 -0
  247. conclear-1.0.0/tests/unit/test_runtime_directory.py +224 -0
  248. conclear-1.0.0/tests/unit/test_runtime_inputs.py +237 -0
  249. conclear-1.0.0/tests/unit/test_scan_identity.py +113 -0
  250. conclear-1.0.0/tests/unit/test_scan_policy.py +451 -0
  251. conclear-1.0.0/tests/unit/test_schema.py +204 -0
  252. conclear-1.0.0/tests/unit/test_schema_fixtures.py +355 -0
  253. conclear-1.0.0/tests/unit/test_secrets.py +135 -0
  254. conclear-1.0.0/tests/unit/test_setid_inventory.py +235 -0
  255. conclear-1.0.0/tests/unit/test_source_integrity.py +175 -0
  256. conclear-1.0.0/tests/unit/test_spdx.py +114 -0
  257. conclear-1.0.0/tests/unit/test_spdx_validation.py +204 -0
  258. conclear-1.0.0/tests/unit/test_tag_protection.py +217 -0
  259. conclear-1.0.0/tests/unit/test_test_inputs.py +184 -0
  260. conclear-1.0.0/tests/unit/test_test_output_archive.py +59 -0
  261. conclear-1.0.0/tests/unit/test_tool_matrix.py +65 -0
  262. conclear-1.0.0/tests/unit/test_tools.py +225 -0
  263. conclear-1.0.0/tests/unit/test_transport.py +1262 -0
  264. conclear-1.0.0/tests/unit/test_triage.py +109 -0
  265. conclear-1.0.0/tests/unit/test_values.py +140 -0
  266. conclear-1.0.0/tests/unit/test_version_sources.py +276 -0
  267. conclear-1.0.0/tests/unit/test_workspace.py +298 -0
  268. conclear-1.0.0/tests/unit/test_workspace_journal.py +292 -0
  269. conclear-1.0.0/uv.lock +950 -0
@@ -0,0 +1,1813 @@
1
+ # ConClear architecture
2
+
3
+ This document defines the architecture and required behavioral contract of
4
+ ConClear. The terms MUST, SHOULD and MAY are used as defined in
5
+ [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119) and
6
+ [RFC 8174](https://datatracker.ietf.org/doc/html/rfc8174).
7
+
8
+ Implementation and tests MUST conform to this contract. Discrepancies
9
+ MUST be investigated; an approved correction changes either the implementation
10
+ or the contract. This document contains no planned or speculative behavior.
11
+ Proposals and future changes are tracked separately, preferably as
12
+ [issues](https://github.com/foundata/conclear/issues), until their
13
+ implementation and tests land with the contract change.
14
+
15
+ Current implementation promises are marked with stable implementation promise
16
+ (IP) `IPnnnn` anchors. The generated
17
+ [implementation matrix](./docs/implementation.md) links every promise to
18
+ its production code and verification tests for this ConClear version.
19
+
20
+ The
21
+ [foundata OCI container image build and release guide](https://github.com/foundata/guidelines/blob/main/oci-container-image-guide.md)
22
+ is normative. This document explains how ConClear implements that guide's
23
+ automatable rules. Each ConClear release selects and embeds an exact guide
24
+ revision; when the documents conflict, that selected guide revision takes
25
+ precedence and this document must be corrected.
26
+
27
+
28
+ ## Table of contents<a id="table-of-contents"></a>
29
+
30
+ - [Product contract](#product-contract)
31
+ - [Goals](#goals)
32
+ - [Terminology](#terminology)
33
+ - [Core model](#core-model)
34
+ - [Invariants](#invariants)
35
+ - [Guide identity and conformance](#guide-identity-and-conformance)
36
+ - [Configuration and trust inputs](#configuration-and-trust-inputs)
37
+ - [Built-in limits](#built-in-limits)
38
+ - [Supported Containerfile syntax](#supported-containerfile-syntax)
39
+ - [Pin updates](#pin-updates)
40
+ - [Command model](#command-model)
41
+ - [Records and workspaces](#records-and-workspaces)
42
+ - [Tool execution](#tool-execution)
43
+ - [Build and qualification](#build-and-qualification)
44
+ - [Publication and promotion](#publication-and-promotion)
45
+ - [Provenance, signing and verification](#provenance-signing-and-verification)
46
+ - [Rescans](#rescans)
47
+ - [Implementation structure](#implementation-structure)
48
+ - [Testing](#testing)
49
+ - [Maintaining this document](#maintaining-this-document)
50
+
51
+
52
+ ## Product contract<a id="product-contract"></a>
53
+
54
+ <a id="promise-ip0001"></a>
55
+ ConClear is a command-line application that checks, builds, tests and qualifies
56
+ OCI container images; publishes accepted candidates; attaches release evidence;
57
+ verifies the published subject; and promotes only a verified digest. The
58
+ complete workflow runs on a maintainer-controlled Linux workstation and can run
59
+ unchanged in protected CI.
60
+
61
+ <a id="promise-ip0002"></a>
62
+ ConClear verifies declared container-image dependencies and base-image pins,
63
+ generates non-mutating pin-update proposals from its own registry resolution,
64
+ and applies a proposal to the local worktree only after verifying it against the
65
+ current repository state. Checking, proposing, applying and accepting a pin
66
+ update are distinct operations; acceptance stays with the repository owner's
67
+ review of the resulting diff.
68
+
69
+ ConClear does not deploy workloads, operate registries, schedule recurring jobs,
70
+ manage the supported-release inventory, perform vulnerability triage, rebuild
71
+ affected projects, orchestrate running services, build virtual machines, process
72
+ unrelated artifact types, or invoke Renovate or another external updater. An
73
+ external updater may deliver a ConClear proposal through a review branch or pull
74
+ request, but that delivery is optional and never required for an authorized
75
+ local maintainer workflow. Docker, Windows containers and GitHub container
76
+ actions are outside the supported and tested surface.
77
+
78
+
79
+ ## Goals<a id="goals"></a>
80
+
81
+ - Provide one fail-closed release workflow from a reviewed source commit through
82
+ promotion.
83
+ - Use the same commands, rules and record schemas on a workstation and in CI.
84
+ - Run rootless with Buildah, Podman and Skopeo and require no Docker daemon.
85
+ - Keep all rejecting content gates local until an accepted digest is ready for
86
+ public upload.
87
+ - Bind builds, tests, scans, SBOMs, provenance, signatures and verification
88
+ results to immutable digests.
89
+ - Make every release decision reconstructible from machine-readable records
90
+ without treating logs as evidence.
91
+ - Resolve and record the actual tools used by each release while allowing
92
+ supported tool upgrades between releases.
93
+ - Keep repository configuration narrow, reviewable and unable to relax
94
+ unconditional guide requirements.
95
+ - Fail without promotion when a required fact, test, signature, attestation or
96
+ remote digest cannot be established.
97
+
98
+
99
+ ## Terminology<a id="terminology"></a>
100
+
101
+ - An **authorized release environment** is a maintainer-controlled Linux
102
+ workstation or protected CI job with external registry credentials, signing
103
+ authority and trust configuration. A workstation release is not a lesser class
104
+ of release.
105
+ - **Repository configuration** is the reviewed `conclear.toml` at the selected
106
+ source revision. It contains project facts and exceptions permitted by the
107
+ guide, but no signing or registry credentials.
108
+ - A **release run** is one ConClear invocation and its resumable workspace. Its
109
+ identity is a lowercase ULID generated by ConClear.
110
+ - A **platform qualification** is the immutable
111
+ `platform-qualification-<platform>.json` record for one image and one target
112
+ platform. It binds the built layout, tests, SBOM, scans, execution facts and
113
+ verdict by digest.
114
+ - A **release candidate** is the immutable `release-candidate.json` aggregate
115
+ produced from exactly one accepted qualification for each required platform.
116
+ - A **candidate reference** is the single-use registry tag used to publish one
117
+ accepted candidate before verification and promotion.
118
+ - A **release image** is a platform manifest or image index that has passed the
119
+ required gates and has been published by the release process.
120
+ - **Release provenance** is machine-readable evidence of how and where an image
121
+ was built and which source and dependencies were used.
122
+ - **Evidence** is the digest-bound, machine-readable output of the release
123
+ process. Signed registry attestations are the authoritative retained evidence;
124
+ workspace files are convenience copies.
125
+ - A **trust root** is the approved public signing key or managed-key identity
126
+ used to verify signatures and attestations. It comes from
127
+ maintainer-controlled release or protected deployment configuration, never
128
+ from the repository being verified.
129
+ - A **rule rejection** means that observed content violates the guide or
130
+ effective repository configuration. An **operational failure** means that
131
+ ConClear could not establish a result, for example because a tool, network
132
+ operation or registry comparison failed.
133
+
134
+
135
+ ## Core model<a id="core-model"></a>
136
+
137
+ <a id="promise-ip0003"></a>
138
+ The canonical pre-publication artifact is an OCI image layout, not a mutable
139
+ local image name. A release follows this data flow:
140
+
141
+ ```text
142
+ reviewed source commit
143
+ -> isolated detached worktree and exported tracked tree
144
+ -> platform OCI layout build
145
+ -> digest-reverified Podman import and tests test
146
+ -> SBOM, scans and platform qualification evidence
147
+ -> verified manifest or image index assemble
148
+ -> release candidate and provenance predicate provenance
149
+ -> registry candidate digest publish
150
+ -> signatures and attestations attest
151
+ -> signed release-verification attestation verify
152
+ -> version and moving release tags promote
153
+ ```
154
+
155
+ <a id="promise-ip0004"></a>
156
+ `conclear release` owns this ordering and can execute every step in one local
157
+ process. ConClear deliberately completes and accepts `linux/amd64` qualification
158
+ before starting additional required platforms. This is stricter than the guide's
159
+ build-and-test ordering and fails the required platform early. Platform
160
+ qualification may instead run in separate worker runs, on one host or on
161
+ several: each worker invokes the same `qualify` command, exports its accepted
162
+ qualification as a digest-bound transport, and a coordinator run created by
163
+ `assemble` verifies and assembles those transports through the same assembly
164
+ path. External automation may move transports, prepare the host, unlock
165
+ credentials and schedule later rescans; it does not reimplement release
166
+ decisions.
167
+
168
+ <a id="promise-ip0005"></a>
169
+ The release state advances monotonically through `created`, `qualified`,
170
+ `assembled`, `published`, `attested`, `verified` and `promoted`. `rejected` and
171
+ `incomplete` results never satisfy a later state's prerequisite. Retrying a
172
+ network operation may resume the same state only when all immutable inputs and
173
+ expected digests still match. A run that is not a release, such as a rescan,
174
+ has no intermediate states: it ends as `completed` when its record is written,
175
+ or as `rejected` when its verdict rejects the subject. `promoted`, `completed`
176
+ and `rejected` are terminal and cannot be resumed, so a finished run is never
177
+ mistaken for one that stopped before doing anything.
178
+
179
+
180
+ ## Invariants<a id="invariants"></a>
181
+
182
+ <a id="promise-ip0006"></a>
183
+
184
+ 1. No rebuild occurs between qualification and publication.
185
+ 2. Every test, scan, SBOM, signature and attestation identifies an immutable
186
+ manifest or index digest.
187
+ 3. Runtime tests use a digest-reverified import of the exact OCI layout that
188
+ qualification records.
189
+ 4. Required skipped tests produce an incomplete run, not a successful
190
+ qualification.
191
+ 5. `conclear.toml` may narrow built-in rules but cannot relax an unconditional
192
+ `MUST` or `MUST NOT` or extend a built-in maximum.
193
+ 6. A workstation invocation and a CI invocation use the same state machine and
194
+ can produce equally authoritative evidence.
195
+ 7. Source identity comes from the isolated Git checkout, builder identity comes
196
+ from the protected release profile, ConClear implementation identity comes
197
+ from embedded version data, and signer identity comes from the configured
198
+ signing key. Caller-provided labels cannot replace these values.
199
+ 8. A candidate reference is generated once and is reused after an ambiguous
200
+ write only when the registry resolves it conclusively to the unchanged
201
+ expected digest within its recorded lifetime; otherwise the release requires
202
+ a new run and candidate reference.
203
+ 9. Promotion writes only the digest accepted by release verification and
204
+ verifies every written tag by resolving it again.
205
+ 10. ConClear deletes only local and remote resources recorded as owned by the
206
+ current release run.
207
+ 11. Secrets are never accepted as command-line literals, stored in repository
208
+ configuration, included in evidence or written to logs.
209
+ 12. Rule rejections and operational failures remain distinguishable in human
210
+ output, JSON output and process exit status.
211
+
212
+
213
+ ## Guide identity and conformance<a id="guide-identity-and-conformance"></a>
214
+
215
+ <a id="promise-ip0007"></a>
216
+ Every ConClear build embeds its version, full source revision, and the title,
217
+ repository, path and full revision of the guide it implements.
218
+ `conclear version` and `conclear version --format json` expose those values in
219
+ the forms required by the guide. They are build inputs and MUST NOT be read from
220
+ the application repository at runtime.
221
+
222
+ ConClear owns stable check identifiers in the form `CC` followed by four decimal
223
+ digits, for example `CC0101`. An identifier is never reused for a different
224
+ rule; removal leaves a retired entry so historical findings remain
225
+ understandable. Findings, narrow suppressions and documentation use these
226
+ identifiers.
227
+
228
+ A finding carries one of three severities. `error` rejects the command,
229
+ `warning` reports something the project polices but does not reject, and `info`
230
+ is advice this project does not police, such as a style hint from an external
231
+ linter. Only `error` changes a verdict; a finding that names an external tool
232
+ also states that tool's own level, so its taxonomy stays visible without
233
+ entering this contract.
234
+
235
+ One machine-readable check catalog is the implementation source for each
236
+ identifier, summary, severity, automatable behavior and the guide requirement
237
+ identifiers (`IGnnnn`) the check covers. ConClear ships the guide's requirement
238
+ inventory for the embedded revision and a coverage file that gives every
239
+ requirement no check covers one status: automated, manual, external or
240
+ unsupported. `docs/conformance.md` is generated from those three sources and
241
+ records the selected guide revision. CI verifies that identifiers are unique,
242
+ that every referenced requirement exists in the inventory, that every
243
+ requirement has exactly one status and that generated documentation is current.
244
+
245
+ Requirements that need human judgment are listed as manual in the conformance
246
+ documentation. ConClear MUST NOT claim that a mechanical check implements them.
247
+ Built-in defaults and maximums are listed in the same document so a tool release
248
+ completely identifies the rules it applies.
249
+
250
+
251
+ ## Configuration and trust inputs<a id="configuration-and-trust-inputs"></a>
252
+
253
+ <a id="promise-ip0008"></a>
254
+ ConClear has one repository-owned configuration file: `conclear.toml`. Its
255
+ schema is versioned and validated before any build or network operation. Unknown
256
+ keys are errors so misspelled security settings cannot be ignored.
257
+
258
+ The configuration declares image definitions, Containerfile and context paths, a
259
+ fully qualified release destination for every releasable image, required
260
+ platforms, native-testing requirements, runtime expectations, resource limits,
261
+ typed test inputs, test-image dependencies, test hooks, image-pin intent,
262
+ candidate lifetime reductions and permitted exceptions. Paths resolve below the
263
+ isolated source root and cannot escape through `..`, symlinks or archive
264
+ entries.
265
+
266
+ A project may declare where it states its release version in
267
+ `[[project.version_sources]]`: the first versioned heading of a Keep a
268
+ Changelog file, a Git tag pattern such as `v{version}` at the released
269
+ revision, or a regular expression with `{version}` searched in a file. Before
270
+ building, ConClear compares `--version` with every declared source by exact
271
+ string equality and rejects a source that states another version or none
272
+ (`CC0005`); the observations are retained in the platform record. ConClear
273
+ never derives the version from a source, and a project without declared
274
+ sources is unversioned and never rejected for it.
275
+
276
+ The configured source is the project's public source URL: a credential-free
277
+ absolute HTTPS URL, kept byte for byte including any fragment, through which
278
+ users find the source code. It need not name a Git repository and is never
279
+ compared with the checkout's Git origin. A project page section that lists every
280
+ repository mirror is valid, and it stays stable when repository names or hosts
281
+ change. Labels, records and attestations name this URL and the full source
282
+ revision, and nothing else about where the code came from.
283
+
284
+ ConClear still observes the Git origin of the selected checkout, but only to
285
+ enforce the release profile's allowed origins and to correlate optional CI
286
+ context. An observed remote may use the HTTPS form or an equivalent
287
+ `git@host:owner/repository.git` or `ssh://git@host/owner/repository.git`
288
+ transport form; ConClear converts it to its HTTPS identity for that comparison
289
+ and then discards it. The origin never enters labels, records, attestations,
290
+ command results, rejection messages or archives, so a primary forge that is not
291
+ publicly reachable stays undisclosed while public mirrors carry every commit.
292
+ ConClear rejects arbitrary SSH users, host aliases, local paths and other remote
293
+ forms whose identity cannot be established from their syntax alone; it never
294
+ requires a maintainer to change an equivalent local SSH remote.
295
+
296
+ An illustrative configuration is:
297
+
298
+ ```toml
299
+ schema_version = 1
300
+
301
+ [project]
302
+ name = "example"
303
+ source = "https://github.com/foundata/example"
304
+
305
+ [[images]]
306
+ id = "example"
307
+ containerfile = "Containerfile"
308
+ context = "."
309
+ repository = "quay.io/foundata/example"
310
+ platforms = ["linux/amd64", "linux/arm64"]
311
+ native_test_platforms = ["linux/amd64"]
312
+
313
+ [images.release]
314
+ version_tags = ["{version}"]
315
+ moving_tags = ["stable"]
316
+
317
+ [images.runtime]
318
+ profile = "service"
319
+ user = 65532
320
+ memory = "512MiB"
321
+ cpus = 1.0
322
+ pids = 256
323
+ nofile = 1024
324
+ health_command = ["/usr/local/libexec/example-healthcheck"]
325
+
326
+ [[images.pins]]
327
+ reference = "quay.io/fedora/fedora-minimal:<release>"
328
+ tag_intent = "moving-release-line"
329
+ ```
330
+
331
+ `containerfile`, `context` and `native_test_platforms` default to
332
+ `Containerfile`, `.` and `["linux/amd64"]`. Trivy is fixed policy rather than a
333
+ repository option. Resource values are required measurements; these numbers
334
+ illustrate syntax only. The root
335
+ filesystem defaults to read-only; writable paths are declared individually. A
336
+ `writable_root_requirement` with rationale, owner and review trigger permits a
337
+ writable container root without granting writable host paths. Release tag
338
+ templates may use only the documented
339
+ `{version}` value; unversioned projects omit version-dependent templates.
340
+ Candidate tags remain entirely ConClear-owned.
341
+
342
+ Each release declares at least one final tag. Unused tag classes, optional
343
+ tables and default limits may be omitted. Literal collisions fail during
344
+ configuration loading. Source-run creation resolves the sole release image when
345
+ `--image` is absent, records its ID, and validates version-dependent rendered
346
+ tags before resolving the build or signing tools. Several release images
347
+ require an explicit selection, even if only one supports the current host.
348
+ Platform declarations and platform-command arguments remain explicit.
349
+
350
+ Pin declarations contain a readable tag and its intent. Configuration loading
351
+ derives the effective digest-bearing reference from the Containerfile; a tag
352
+ with no matching digest, multiple digests or duplicate intent declarations is
353
+ rejected. Static and pin checks also reject undeclared external inputs.
354
+ Evidence records the full effective references; the source revision and source
355
+ tree bind their authoritative Containerfile bytes.
356
+
357
+ The configured runtime user is a numeric non-zero UID by default and must match
358
+ the final Containerfile `USER`. UID 0 is accepted only when the runtime also
359
+ contains a closed `root_requirement` table with non-empty `rationale`, `owner`
360
+ and `review_trigger` values. The exception is reviewed repository input and is
361
+ recorded in the platform qualification. A root requirement is rejected for a
362
+ non-zero UID.
363
+
364
+ A separate `sudo_requirement` records rationale, owner, review trigger,
365
+ authorization scope and either `presence-only` or `escalation` mode. Only
366
+ escalation disables `no-new-privileges` in the functional runtime. It requires
367
+ `test.sudo` to name distinct non-root permitted and denied callers, a distinct
368
+ target UID, an absolute command and exact expected stdout. Tests use
369
+ noninteractive sudo; test accounts and policy must exist in the image.
370
+ Root-startup images may instead use declared read-only launch fixtures whose
371
+ policy files appear root-owned in the container's user namespace.
372
+ Sudo executable paths default to
373
+ `/usr/bin/sudo`; other set-ID executables require individual
374
+ `setid_requirements`. These declarations do not add capabilities or change the
375
+ startup user or root filesystem mode.
376
+
377
+ The `service`, `one-shot` and `scratch` profiles use the ordinary process
378
+ lifecycle and explicitly disable Podman's automatic systemd mode. The separate
379
+ `systemd` profile requires UID 0, a root requirement and a closed `systemd`
380
+ table containing at least one `required_units` entry. The profile declares no
381
+ stop signal: systemd shuts down on `SIGRTMIN+3`, ConClear always sends that
382
+ signal, and the Containerfile must set the same `STOPSIGNAL`. The profile adds
383
+ `/run`, `/run/lock`, `/tmp` and `/var/log/journal` to the effective
384
+ private tmpfs set. Repository configuration may declare further writable paths,
385
+ but an immutable path cannot overlap any effective writable path.
386
+
387
+ The effective writable set is exact. It includes every read-write mount that
388
+ Podman observes, regardless of whether ConClear supplied it or the image created
389
+ an anonymous mount through `VOLUME`. Every image-declared volume destination
390
+ must therefore be present in `writable_mounts` unless the selected profile
391
+ already supplies it. A declared image volume keeps its anonymous backing in the
392
+ run-owned isolated Podman storage; ConClear supplies private tmpfs only for a
393
+ declared path that the image does not provide. It rejects any unexpected or
394
+ missing writable destination. Static checks reject an undeclared `VOLUME` in
395
+ the final local build stage; inherited volume metadata is authoritatively
396
+ detected by the runtime observation.
397
+
398
+ Repository test hooks are argument arrays, not shell strings. ConClear supplies
399
+ documented paths and immutable references as individual environment values.
400
+ Hooks cannot interpolate command text and cannot override release state,
401
+ evidence fields, registry subjects or signer identity.
402
+
403
+ A hook runs in the run's Git checkout and receives `CC_LAYOUT`, the qualified
404
+ OCI layout; `CC_IMAGE_DIGEST`; `CC_PLATFORM`; `CC_SOURCE_ROOT`, the checkout
405
+ path; `CC_TEST_INPUT_MANIFEST`, the non-secret test-input manifest; and
406
+ `CC_HOOK_SCRATCH`. The scratch directory is created empty and private below the
407
+ run workspace before the first hook of a platform runs and is shared by that
408
+ platform's hooks. It is the one place a hook may write working data such as a
409
+ container store. Hooks own that content and should remove it when they finish;
410
+ ConClear removes what remains: directly where it can, otherwise inside the
411
+ rootless container user namespace, because a container store leaves files owned
412
+ by subordinate user IDs that the invoking user cannot unlink. That step is
413
+ limited to the scratch directory, refuses to run while anything is mounted
414
+ below it and uses a throwaway Podman storage location that is removed
415
+ afterwards.
416
+
417
+ <a id="promise-ip0009"></a>
418
+ An image may declare a `test` table containing repository fixture handles,
419
+ run-owned output handles, ordered preparation steps, launch inputs and
420
+ dependencies on other image IDs from the same configuration. A fixture names an
421
+ immutable source-tree path and is always mounted read-only. An output names a
422
+ run-owned directory that ConClear creates empty before the first preparation
423
+ runs and that may be mounted writable only at a destination already declared
424
+ by the selected image's runtime profile. A read-only mount may name only an
425
+ output that an earlier preparation wrote, and every output must be mounted
426
+ writable by at least one preparation or by the launch. An output that only the
427
+ launched container writes needs no preparation; its final content is observed
428
+ and recorded, not required. Fixture and output names share one namespace, so a
429
+ mount identifies its source by name alone and is read-only unless it declares
430
+ otherwise. An output marked secret is never exposed to repository hooks or
431
+ included by value or content digest in public evidence.
432
+
433
+ Each preparation step selects the primary image or one of its declared
434
+ test-image dependencies, replaces that exact image's entrypoint with an argument
435
+ array, supplies only declared non-secret environment values and mounts, and
436
+ declares a bounded timeout and expected exit status. Preparation executes under
437
+ the selected image's configured user, root filesystem mode, capability,
438
+ `no-new-privileges`, platform and resource controls. It cannot alter those
439
+ controls or the main launch verdict. The main launch retains the tested image's
440
+ original entrypoint and may add an explicit argument array, non-secret
441
+ environment values and declared mounts; its expected one-shot exit status
442
+ defaults to zero.
443
+
444
+ Test-image dependencies form an acyclic graph of image IDs declared in the same
445
+ `conclear.toml`. ConClear rejects unknown IDs, self-dependencies, duplicates,
446
+ cycles and dependencies that do not cover every platform of the depending image.
447
+ Before a qualification builds anything, the static checks and the pin gate run
448
+ for the complete dependency closure in stable dependency-first order: every
449
+ transitive dependency, then the selected image, each under its own
450
+ Containerfile, context, declared pins and pin limits at one instant. Every
451
+ distinct readable tag is resolved once for the whole closure, so images that
452
+ share a tag observe one digest, and a static rejection anywhere in the closure
453
+ stops the run before any registry is contacted. Every finding of the closure
454
+ names the image it concerns, so a rejection is attributable. ConClear then
455
+ builds each dependency once from the same isolated revision, source timestamp,
456
+ target platform, version input and resolved Buildah toolchain, validates its OCI
457
+ layout and labels, imports it by its reverified manifest digest, and exposes no
458
+ mutable reference. A dependency participates only in the primary image's tests
459
+ and is not represented as independently qualified or releasable. The platform
460
+ qualification records, for each dependency, its Containerfile and context
461
+ digests, build arguments, external images, pin observations and effective pin
462
+ limits next to its layout and manifest digests. Transport import and assembly
463
+ verify that evidence against the configured dependency set, pins and limits, and
464
+ assembly requires it to agree across platforms. Build arguments are never
465
+ trusted from a record: the `assemble` command derives the one map every build of
466
+ the run received, from the selected source revision, the commit time Git
467
+ observed for it and the release version, when it imports each transport and
468
+ again when it assembles the candidate, and it rejects any qualified image or
469
+ dependency whose recorded map differs from that derived map in any key. Release
470
+ provenance names each dependency's Containerfile, context, tested manifest and
471
+ external images as resolved dependencies.
472
+
473
+ An image is releasable when it declares a `repository`; it then also declares
474
+ its release tags. An image without a repository is test-only: it declares only
475
+ what a dependency uses, namely its build inputs, platforms, pins, pin limits,
476
+ runtime contract and its own dependencies, and the keys that only a qualified
477
+ image uses are rejected there. ConClear's typed model mirrors that split: the
478
+ common build-image model holds exactly those facts, the release image type adds
479
+ the destination, tags, native-test requirements, rescan policy,
480
+ test inputs, hooks, vulnerability exceptions, a package assessment exception
481
+ and release limits, and scanning,
482
+ runtime qualification, assembly, publication and rescan accept only the
483
+ release image type, so release-only state cannot exist on a test-only image.
484
+ ConClear refuses to select a test-only image for a build, qualification, release
485
+ or rescan, ignores it when probing or cleaning registry destinations, and
486
+ rejects a test-only image that no image depends on. A releasable image may serve
487
+ as a test dependency as well; both roles use the same declaration.
488
+
489
+ Vulnerability exceptions use a dedicated typed table declared inside the image
490
+ they apply to, which identifies the image as the guide requires; each exception
491
+ states component, advisory, rationale, reachability, exposure, compensating
492
+ controls, owner, expiry and review trigger. ConClear verifies structure, expiry
493
+ and an exact finding match, and records the image identifier with every applied
494
+ exception. Security-owner review remains a repository merge-control
495
+ responsibility and is not inferred from a self-declared field.
496
+
497
+ Configuration exceptions use the same shape for the configuration scanner,
498
+ which walks the whole image filesystem and therefore also evaluates
499
+ infrastructure files that installed packages ship as data. Each exception names
500
+ the image path or path pattern (`*` and `?` within a segment, `**` across
501
+ segments), optionally the check identifiers it covers, and rationale, owner,
502
+ expiry and review trigger. ConClear applies it only to failed checks on matching
503
+ paths, rejects it when expired, records target, check and declaration with every
504
+ applied exception and never lets it suppress secret or vulnerability findings.
505
+
506
+ <a id="promise-ip0010"></a>
507
+ Maintainer-controlled release configuration is separate from the application
508
+ repository. A named profile under `$XDG_CONFIG_HOME/conclear/` supplies a public
509
+ HTTPS SLSA builder identity and explicitly selects one compiled registry control
510
+ backend with its host, API and credential locations. The profile may also
511
+ identify a containers-auth file, Cosign private-key and public-key paths, a
512
+ passphrase provider, or a KMS/HSM key handle. It contains trust identities and
513
+ credential locations, not alternative guide rules or secret values. ConClear
514
+ rejects release configuration and file-based credentials with unsafe ownership
515
+ or permissions. Backend selection is never inferred from a repository hostname.
516
+
517
+ The profile also lists the allowed Git origins of release checkouts as
518
+ credential-free HTTPS URL prefixes (`allowed_source_origins`). Every command
519
+ that runs with a profile normalizes the checkout's origin and requires it to
520
+ fall under one listed prefix, both when a run is created and when a later phase
521
+ reopens it; a run whose origin matches no prefix is rejected before any run
522
+ state exists, and the rejection names neither the origin nor the list. The list
523
+ belongs in the profile rather than in `conclear.toml` for two reasons: it may
524
+ name internal hosts, and the repository being released must not be able to
525
+ authorize itself. Without a profile no origin policy applies.
526
+
527
+ The release profile configures optional CI context handling as `omit`, `observe`
528
+ or `require`. `omit` does not inspect provider variables. `observe` records
529
+ complete context that agrees with the isolated checkout and otherwise writes a
530
+ local diagnostic without changing the release result. `require` treats missing,
531
+ malformed or inconsistent context as an operational failure. ConClear recognizes
532
+ GitHub Actions, GitLab CI, Gitea Actions, Forgejo Actions and Woodpecker CI
533
+ through provider-specific adapters. Gitea and Forgejo markers take precedence
534
+ over their GitHub-compatible variables.
535
+
536
+ Public CI context has one provider-neutral shape: provider,
537
+ `provider-environment` source, repository, full source revision and provider run
538
+ identifier. The values are correlation metadata from ordinary process
539
+ environment variables, not authenticated CI identity. They cannot override the
540
+ isolated checkout, builder, signer, ConClear run identifier, artifact digest or
541
+ release verdict. The provider's repository claim is compared with the checkout's
542
+ Git origin, not with the public source URL, which need not name a repository.
543
+ Full provider origins stay in local diagnostics so signed public evidence does
544
+ not disclose internal hostnames.
545
+
546
+ On a workstation, the signing passphrase may be read from the controlling
547
+ terminal. Automation may provide a read-once file descriptor or mounted secret.
548
+ If Cosign requires a child-process environment variable, ConClear creates it
549
+ only for that Cosign process from the protected source and removes it from all
550
+ logs and evidence. Secret values are never inherited from ordinary project
551
+ environment configuration.
552
+
553
+
554
+ ## Built-in limits<a id="built-in-limits"></a>
555
+
556
+ <a id="promise-ip0011"></a>
557
+ ConClear ships enforceable limits. Repository configuration may shorten these
558
+ intervals but cannot extend or disable them.
559
+
560
+ | Limit | Built-in maximum |
561
+ | -------------------------------------------------------------------------------------------- | ---------------: |
562
+ | Age of a successful pin resolution used for qualification | 24 hours |
563
+ | Qualification window from original fresh database selection | 24 hours |
564
+ | Divergence between a declared tag and its pinned digest | 7 days |
565
+ | Candidate lifetime before promotion | 7 days |
566
+ | Remediation after an authoritative rescan finds a fixable `HIGH` or `CRITICAL` vulnerability | 30 days |
567
+
568
+ Pin observations are stored in durable ConClear state outside the project
569
+ checkout. CI must persist that state through a protected cache. When a
570
+ divergence has no earlier observation, ConClear records the current registry
571
+ resolution as the first observation and marks the history as newly initialized
572
+ in evidence.
573
+
574
+ The seven-day divergence maximum applies to both pin intents. Divergence under
575
+ an `immutable-version` tag immediately emits a non-suppressible, review-required
576
+ supply-chain finding; the pin gate may continue to use the pinned digest within
577
+ the interval, but the review obligation does not pause or extend the maximum.
578
+ Divergence under a `moving-release-line` tag is a routine update proposal within
579
+ the interval. Either intent rejects qualification after the maximum expires.
580
+
581
+ Changing a built-in limit changes release behavior and therefore requires a
582
+ reviewed code change, conformance update and ordinary ConClear release. Evidence
583
+ identifies the exact ConClear and guide revisions that supplied the effective
584
+ limit.
585
+
586
+
587
+ ## Supported Containerfile syntax<a id="supported-containerfile-syntax"></a>
588
+
589
+ `containerfile.py` owns one immutable lexical representation for static checks,
590
+ adoption observation and pin discovery. It records logical instructions, builder
591
+ flags, stage names and image operands with offsets into the original bytes.
592
+ Each operation reads a bounded source snapshot once and passes that snapshot to
593
+ its consumers. Pin discovery does not reread the Containerfile or search its
594
+ instruction text to locate editable operands.
595
+
596
+ The supported subset includes:
597
+
598
+ - UTF-8 text with LF instruction boundaries, full-line comments, indentation and
599
+ default backslash continuations. Continuations remove the backslash and line
600
+ ending without inserting a space. Existing whitespace is preserved;
601
+ intervening blank and comment lines are ignored. Format checks still reject
602
+ BOMs, CR line endings and a missing final newline.
603
+ - Whitespace-delimited `FROM` operands, optional leading builder flags and
604
+ `AS name` stages. `scratch` and named stages are not external image inputs.
605
+ - Leading `COPY --from=...`, `ADD --from=...` and repeated
606
+ `RUN --mount=...,from=...,...` flags. Single or double quotes can surround
607
+ flag values; spaces inside quotes are preserved. Mount fields use comma
608
+ separators. The first non-flag operand or standalone `--` ends the builder
609
+ flag prefix. Text resembling a flag inside a shell command, JSON operand or
610
+ label is not an image input. Numeric stage references are observed but
611
+ rejected by policy.
612
+ - JSON and shell instruction bodies. JSON exec-form decoding is shared by checks
613
+ and adoption. Shell bodies remain text, not a shell execution model.
614
+
615
+ ConClear does not implement all syntax accepted by Buildah. Heredocs,
616
+ non-default escape or platform directives, and `ONBUILD` instructions are
617
+ explicitly unsupported. In non-JSON `RUN`, `COPY` and `ADD` bodies, any `<<`
618
+ spelling is conservatively rejected, including quoted literal text. Duplicate
619
+ `--from` flags, duplicate `from` fields within one mount, empty image operands,
620
+ malformed `FROM` declarations and unterminated quotes or continuations are
621
+ rejected before any pin proposal or build. BuildKit `syntax` directives remain a
622
+ policy rejection. These boundaries are not claims that the builder rejects those
623
+ forms.
624
+
625
+ Image-reference variables are retained as text so adoption can report them;
626
+ policy rejects them rather than evaluating build arguments. Observation covers
627
+ explicit final-stage instructions, not inherited base-image configuration or
628
+ expanded label values. Hadolint, Buildah and built-image checks remain
629
+ necessary; successful lexing alone does not establish builder validity or guide
630
+ compliance.
631
+
632
+ Pin editing requires each image reference to occupy one contiguous literal byte
633
+ span. Surrounding quotes and continuations between operands are preserved. A
634
+ reference split internally by a continuation, escape or quote may be observable,
635
+ but cannot be rewritten. The extra-occurrence guard described below remains a
636
+ separate safety check; it does not discover image inputs or edit spans.
637
+
638
+ Hermetic tests cover lexical boundaries and byte-span round trips, including
639
+ generated combinations of whitespace, quotes and multibyte prefixes. Local
640
+ integration tests compare complex fixtures with Buildah's emitted OCI metadata
641
+ and verify that valid heredoc and backtick-escape fixtures are explicitly
642
+ outside ConClear's subset. The lexer is intentionally narrower than the
643
+ [builder parser](https://github.com/containers/buildah/tree/v1.43.2/vendor/github.com/openshift/imagebuilder/dockerfile/parser);
644
+ adding syntax requires shared lexical tests and a real-builder comparison.
645
+
646
+
647
+ ## Pin updates<a id="pin-updates"></a>
648
+
649
+ `pins check` remains the freshness and divergence gate and the only owner of
650
+ durable pin observations. It never edits project files. `pins propose` and
651
+ `pins apply` are separate explicit operations that implement the guide's
652
+ pin-update contract without an external updater.
653
+
654
+ <a id="promise-ip0012"></a>
655
+ `pins propose` resolves every distinct readable tag exactly once through
656
+ ConClear's authenticated Skopeo resolution and binds that one observed digest to
657
+ every occurrence of the tag. It derives the required occurrence set from the
658
+ parsed repository configuration and the parsed Containerfiles, not from a
659
+ caller-supplied list or a repository-wide text search: intent comes from the
660
+ `[[images.pins]]` declarations, and editable bytes come from every external
661
+ `FROM`, `COPY --from` and `RUN --mount=from` instruction that names the same
662
+ tagged and digest-pinned reference. A declared pin without a Containerfile
663
+ occurrence, an undeclared Containerfile input, conflicting tag intents for one
664
+ readable tag, a reference that appears in a comment, an unrelated value or an
665
+ undeclared file, an ambiguous or unsupported spelling, and any duplicate or
666
+ overlapping span are rejected; nothing is rewritten opportunistically. Proposal
667
+ generation is repository-wide by default. An image selection is accepted only
668
+ when it omits no other image bound to the same readable tag. `pins propose` does
669
+ not modify project files, create commits or branches, or update durable pin
670
+ observations. Its explicitly requested output file is its only persistent write,
671
+ and it refuses to overwrite an existing file.
672
+
673
+ The proposal is a schema-validated version-1 record with `recordType`
674
+ `pinUpdateProposal`. It records the ConClear version, source revision and
675
+ embedded guide revision; the resolving tool identity; the creation time from an
676
+ injected UTC clock; the declared public source URL, current full Git
677
+ revision, configuration path and SHA-256 digest; the selected image IDs; one
678
+ lookup per original tagged-digest reference with its affected image IDs,
679
+ declared tag intent, resolved tagged-digest reference, old and new digest and
680
+ resolution time; and one entry per affected file with its repository-relative
681
+ path, original and expected resulting SHA-256 digests and exact non-overlapping
682
+ byte spans with their exact old and replacement bytes. Only the digest of a
683
+ reference changes; registry, repository and tag spelling are preserved byte for
684
+ byte, and a fully qualified reference is never normalized into another name. The
685
+ proposal contains no credentials, authentication-file paths or registry tokens.
686
+ Its serialization is canonical JSON, so fixed repository bytes, clock, resolver
687
+ observations and tool identity produce identical bytes; its identity is the
688
+ SHA-256 of those exact stored bytes. An already-current repository produces a
689
+ successful proposal with no file entries. A digest change under an
690
+ `immutable-version` tag is recorded as review-required and reported with
691
+ `CC0205`; there is no skip, override or automatic acceptance.
692
+
693
+ <a id="promise-ip0013"></a>
694
+ `pins apply` consumes one proposal and never resolves a tag again. Before its
695
+ first project-file write it completes a read-only preflight: it validates the
696
+ proposal schema and ConClear-supported record identity, confines every path
697
+ below the repository root without following symbolic links and rejects absolute
698
+ paths, traversal, symbolic links and non-regular targets, matches the canonical
699
+ repository identity and current full Git revision, matches the current
700
+ configuration digest and every target file's complete SHA-256 digest, reparses
701
+ the current configuration and Containerfiles and proves that their dependency
702
+ set, paths and occurrence cardinality equal the proposal, validates every span
703
+ boundary, old byte sequence, replacement reference, digest and non-overlap
704
+ invariant, and rejects a proposal whose resolution time exceeds the effective
705
+ pin-resolution freshness limit of the affected images without substituting a
706
+ newer digest. It constructs every resulting file in memory, proves that only the
707
+ proposed spans differ, writes each file through a same-directory temporary file
708
+ created with restrictive permissions, preserves the original mode, flushes and
709
+ durably replaces the target, then reparses and verifies the complete result
710
+ against the proposal. A proposal with no file entries touches nothing, and a
711
+ proposal whose files already carry the expected result is reported as already
712
+ applied without writes. On any detected preparation, write, flush, replace or
713
+ verification error, every target is restored to its exact original bytes and the
714
+ command returns an operational failure; a known partial application is never
715
+ left behind. `pins apply` never commits, creates a branch, pushes, merges,
716
+ builds, qualifies, publishes, signs or promotes, and it names the follow-up
717
+ `pins check` invocation that must confirm the result. A proposal and its
718
+ application are not release evidence.
719
+
720
+
721
+ ## Command model<a id="command-model"></a>
722
+
723
+ <a id="promise-ip0014"></a>
724
+ The public command surface is composable, but `release` is the normal release
725
+ interface. Individual commands support diagnosis, distributed platform work and
726
+ recovery without defining an alternative workflow.
727
+
728
+ A typical local release is selected explicitly:
729
+
730
+ ```sh
731
+ conclear release \
732
+ --image example \
733
+ --revision v1.8.2 \
734
+ --version 1.8.2 \
735
+ --profile foundata \
736
+ --archive-dir /srv/archives/conclear
737
+ ```
738
+
739
+ `--revision` is a Git selector that ConClear resolves and observes; `--version`
740
+ supplies release naming and may be omitted for an unversioned project;
741
+ `--profile` selects maintainer-controlled trust and credential locations outside
742
+ the repository.
743
+
744
+ | Command | Responsibility |
745
+ | ------------------ | -------------- |
746
+ | `version` | Report ConClear and implemented-guide identity in human-readable or JSON form. |
747
+ | `adopt` | Assess an existing repository read-only: observe its conventional Containerfiles, source identity, external inputs and runtime facts, suggest conservative values, list the decisions only a maintainer can make, and render a deliberately invalid draft `conclear.toml`. |
748
+ | `doctor` | Validate read-only prerequisites for one scope: `check` resolves the static toolchain, `qualify` adds rootless storage and platform execution, and `release` adds credential inputs, selected registry-policy APIs and Sigstore initialization. `--version` renders version tags for selective-policy checks. Registry results distinguish `checked`, `notChecked` and `failed`; absent auto-prune policies identify required creation without attempting it. Unselected policy APIs are not called. Writes, expiration enforcement and overwrite protection remain untested. Every missing or unsupported tool of the scope is reported at once. |
749
+ | `check` | Run static Containerfile, context, metadata, pin-declaration and repository-hygiene checks. |
750
+ | `pins check` | Resolve declared image references, update durable observations, report freshness and divergence, and never edit project files. |
751
+ | `pins propose` | Resolve each declared readable tag once, bind the observed digest to every Containerfile occurrence and the unchanged configuration digest, and write one schema-validated non-mutating proposal. |
752
+ | `config show` | Report effective defaults, runtime-profile mounts, limits and decision reasons without running tools or contacting external services; optionally show public release-profile identity without credential paths or values. |
753
+ | `pins apply` | Verify one proposal against the current worktree, Git revision and file digests, then replace only the proposed byte spans all-or-nothing without resolving, committing, building or publishing. |
754
+ | `build` | Build one platform into isolated Buildah storage and export an OCI layout plus build metadata. |
755
+ | `test` | Validate and import one layout, compare its imported digest, and run generic and repository-specific tests under the declared runtime constraints. |
756
+ | `qualify` | Run `check` and the pin gate for the image and its test dependencies, then `build`, `test` and evidence generation for one platform in its own worker run and emit `platform-qualification-<platform>.json`. |
757
+ | `transport export` | Write one accepted qualification, its OCI layout and the evidence payloads it names as a new archive or directory transport with a digest-binding manifest, and report the transport and record digests. |
758
+ | `assemble` | Create a coordinator run from the reviewed source revision, import each transport only against a caller-supplied digest, verify every record, layout, descriptor and payload, require exact platform coverage, create an index when needed and emit `release-candidate.json`. |
759
+ | `provenance` | Generate an in-toto Statement predicate using SLSA Provenance v1 from the accepted candidate and observed release data. |
760
+ | `publish` | Record candidate authorization, copy the accepted subject, apply selected cleanup controls and compare the remote digest graph. |
761
+ | `attest` | Attach platform SBOMs and provenance and sign the index and every platform manifest. |
762
+ | `verify` | Verify the remote graph, signatures, attestations, identities and guide evidence and attach a signed release-verification result. |
763
+ | `promote` | Apply configured version and moving tags to the verified digest, verify each tag, attempt candidate deletion and write a verified evidence archive. |
764
+ | `release` | Create an isolated checkout and execute the complete workflow through promotion and archival, locally or in CI. |
765
+ | `rescan` | Re-evaluate a released digest using current scanner data and emit a linked rescan result with an evidence archive. `--archive` restores the exact configuration and an authoritative history checkpoint. |
766
+ | `archive create` | Retry archival of a completed release or rescan without repeating registry writes. |
767
+ | `archive verify` | Check retained member bytes, evidence bindings and Sigstore bundles against an independently trusted profile key without retrieving registry attestations. |
768
+ | `cleanup` | Resume cleanup of resources recorded as owned by one release run. |
769
+
770
+ A command that writes to the registry or signs refuses a release profile that
771
+ lacks the auth file or the signing key before it creates or reopens a run. An
772
+ option that escalates what a command executes carries its own declaration:
773
+ `rescan --authoritative` attaches a signed result and is therefore held to the
774
+ write and signing rule, while a diagnostic rescan stays read-only.
775
+
776
+ `release` selects an image and a Git revision, resolves that selector to a
777
+ complete commit ID, creates a detached worktree and derives all source facts
778
+ from the checkout. A version supplied for naming is a validated invocation
779
+ parameter, not evidence of source identity.
780
+
781
+ `release --resume <run-id>` resumes only after verifying the recorded source,
782
+ configuration, tools, layouts, evidence and remote digests. It refuses to resume
783
+ across a changed immutable input. A resumed run reuses its recorded candidate
784
+ reference only when the registry resolves that reference conclusively to the
785
+ unchanged expected digest within its recorded lifetime. Otherwise the run cannot
786
+ continue: a candidate reference is never reused for a second publication
787
+ attempt, and the release restarts as a new run with a new run identifier and
788
+ candidate reference.
789
+
790
+ Commands support `--format json`. JSON mode writes one documented result object
791
+ to standard output and diagnostics to standard error. The result schema
792
+ documents the `data` object of every command: a successful result carries
793
+ exactly the documented keys, and a failed result carries only documented keys or
794
+ none. A command that created a run before failing names that run as `runId` in
795
+ its failure result and diagnostics and leaves the run in the `rejected` or
796
+ `incomplete` state, so the journaled resources of every failed run can be found
797
+ and removed with `cleanup`. Exit statuses are `0` for success, `1` for
798
+ operational failure, `2` for rule rejection and `64` for invalid invocation or
799
+ configuration.
800
+
801
+
802
+ ## Records and workspaces<a id="records-and-workspaces"></a>
803
+
804
+ <a id="promise-ip0015"></a>
805
+ Every record is UTF-8 JSON validated against a versioned schema. It includes
806
+ `schemaVersion`, `recordType`, `createdAt`, `runId`, ConClear and guide
807
+ identity, the declared public source URL and source revision, SHA-256 of the
808
+ exact `conclear.toml` bytes, relevant tool identities and a verdict. Timestamps
809
+ use
810
+ UTC RFC 3339 form with whole-second precision and a `Z` suffix. ConClear
811
+ truncates a sub-second observation when it reads its clock and never rounds, so
812
+ a recorded time never post-dates the observation and identical inputs serialize
813
+ to identical bytes. A record digest is the SHA-256 of its exact stored bytes.
814
+
815
+ `platform-qualification-<platform>.json` additionally binds the target platform;
816
+ OCI descriptor and manifest digest; Containerfile, context and effective build
817
+ arguments; external image digests; build and test host, target and execution
818
+ architectures; emulation or cross-build mechanism; runtime constraints;
819
+ non-secret test-input and preparation identities; exact test-image dependency
820
+ descriptors and manifest digests; test result digests; SBOM digest, SPDX
821
+ version and attestation predicate type; scan-result and vulnerability-database
822
+ identities; applied exceptions; and the platform verdict.
823
+
824
+ `transport.json` is the `qualificationTransport` record written by
825
+ `transport export`. It carries the worker run identity, source, configuration
826
+ digest and tool identities of the qualification it wraps and binds the
827
+ qualification-record digest, the layout descriptor, the platform-manifest digest
828
+ and every member path, size and digest. A transport contains only the
829
+ qualification record, the OCI layout and the evidence payloads the record names.
830
+
831
+ `release-candidate.json` binds exactly one accepted qualification per required
832
+ platform, every qualification and payload digest, the worker run identity of
833
+ each qualification and the transport digest of each imported one, every
834
+ platform-manifest digest, the index digest when present, the required and
835
+ accepted platform sets, candidate naming inputs and the aggregate verdict. Its
836
+ own run identity is the coordinator run, which also names the candidate
837
+ reference. A single-platform release uses the same aggregate schema and assembly
838
+ step.
839
+
840
+ A pin-update proposal uses its own version-1 schema rather than the public
841
+ record envelope: it is a reviewable input to a repository change, not release
842
+ evidence, and it carries no run identifier.
843
+
844
+ `release-verification.json` contains the subject and platform digests; ConClear
845
+ version and source revision; guide title, repository, path and revision; SHA-256
846
+ of `conclear.toml`; host architecture, run identity, protected builder identity
847
+ and optional observed CI context; signer mode and public-key fingerprint or
848
+ managed-key identity; and digests of the qualifications, SBOMs, scan results,
849
+ provenance and candidate record. It is an intermediate predicate, not a source
850
+ comment or committed project file. Its signed registry attestation is
851
+ authoritative.
852
+
853
+ <a id="promise-ip0016"></a>
854
+ A run workspace is stored under `$XDG_STATE_HOME/conclear/runs/<run-id>/`:
855
+
856
+ ```text
857
+ run.json
858
+ resources.json
859
+ source/
860
+ checkout/
861
+ logs/
862
+ layouts/<image>/<platform>/
863
+ reports/<image>/<platform>/
864
+ records/platform-qualification-<platform>.json
865
+ records/release-candidate.json
866
+ records/provenance.json
867
+ records/release-verification.json
868
+ exports/sbom/<platform>.spdx.json
869
+ summary.json
870
+ ```
871
+
872
+ Container tools use a private child of the validated user runtime directory,
873
+ normally `/run/user/<uid>`. `environment/runtime-directory.json` and the
874
+ ownership journal retain its identity in persistent storage. Resume recreates
875
+ missing transient files after logout or reboot without changing release
876
+ inputs or deadlines. Cleanup verifies the directory's ownership marker and
877
+ removes it after container storage cleanup. Static checks do not require a
878
+ login runtime directory.
879
+
880
+ `<platform>` is a filesystem key formed by joining the normalized OCI operating
881
+ system, architecture and optional variant with hyphens. For example,
882
+ `linux/amd64` becomes `linux-amd64`; records continue to use the canonical
883
+ slash-separated OCI value.
884
+
885
+ ConClear writes `run.json`, `resources.json` and state transitions atomically.
886
+ `resources.json` records each local path, Buildah storage location, Podman
887
+ import, test container, transport staging directory, imported layout, assembled
888
+ candidate layout, candidate reference, expiration and tag write before and after
889
+ mutation. Generated test outputs and private test material are created only
890
+ below a journaled run-owned directory. Each entry distinguishes ephemeral
891
+ run-owned resources from durable release outputs. Cleanup follows only ephemeral
892
+ ownership records and never deletes promoted tags, signatures, attestations,
893
+ transported inputs, caller-owned paths or pre-existing registry content.
894
+
895
+ Rejected runs retain reports with `verdict: rejected`. Interrupted runs are
896
+ `incomplete`; finished rescans are `completed`. Workspaces may be removed after
897
+ authoritative evidence has been retained. A run is dead when it is promoted,
898
+ completed or rejected; when it has nothing to resume, which covers an
899
+ interrupted rescan and a qualification never bound to a release profile; or when
900
+ its recorded qualification window has expired, since no release phase may
901
+ continue past it. `cleanup --retire` deletes the whole workspace of a dead run
902
+ and refuses a live one unless the operator adds `--abandon`, which declares the
903
+ run dead and gives the run up with whatever it still owns. `cleanup` reports
904
+ what it removed, what it retained and what the workspace still holds. Removing
905
+ run-owned test inputs keeps their ownership marker until the rest of the tree is
906
+ gone, so a removal a repository hook blocks stays retryable and names the
907
+ blocking path. `cleanup --retire` removes the run directory in an order that
908
+ keeps `run.json`, `resources.json` and `records/` until everything else is
909
+ gone; what it cannot remove, such as content a hook wrote outside its scratch
910
+ directory, is named with the deepest blocking path first, and the run stays
911
+ openable so the next attempt continues where this one stopped. ConClear never
912
+ presents its local state directory as an archive or registry backup.
913
+
914
+ `release`, `promote` and `rescan` write a compressed evidence archive after
915
+ completion to `--archive-dir`, or to the protected profile's `archive_dir` when
916
+ the option is omitted; one of the two must name an existing durable directory.
917
+ The allowlisted members include records, reports, OCI metadata and retained
918
+ Sigstore bundles; releases also include exact source. Non-secret test outputs
919
+ travel as a digest-bound qualification payload. Image layers are opt-in.
920
+ Profiles, credentials, keys, raw logs and private test outputs are excluded, but
921
+ source and reports still require disclosure review.
922
+
923
+ Archive writes are checked before atomic, non-overwriting publication. Failure
924
+ preserves the run and does not undo registry publication; `archive create`
925
+ retries export without publishing. `archive verify` checks content relationships
926
+ and verifies retained Sigstore bundles against the protected profile key,
927
+ without registry reads. Its manifest is unsigned; these checks do not sign
928
+ supplemental files or make diagnostic rescans authoritative.
929
+
930
+ `rescan --archive` restores source into a private temporary directory. Rescan
931
+ archives reference their source archive by SHA-256; retain it alongside them.
932
+ An optional basename hint in the unsigned archive manifest avoids a directory
933
+ search. Missing or stale hints fall back to a bounded search; both paths verify
934
+ the referenced digest and signatures. New rescans retain the filename found.
935
+ Current signed registry history supplies the next predecessor, and an archived
936
+ authoritative checkpoint must remain present. No completed run is reactivated.
937
+ Archive storage, support inventory, schedules and backups remain operator
938
+ duties. See [archive usage](./README.md#usage-archives).
939
+
940
+
941
+ ## Tool execution<a id="tool-execution"></a>
942
+
943
+ <a id="promise-ip0017"></a>
944
+ The required core tools are Git, Buildah, Podman, Skopeo, Hadolint, Trivy and
945
+ Cosign, executed as host executables. External updaters and Testinfra project
946
+ tests are not hidden ConClear services: an updater such as Renovate stays
947
+ outside ConClear as optional review delivery, while Testinfra may be invoked
948
+ through a declared repository hook whose interpreter and dependency lock are
949
+ recorded.
950
+
951
+ Each ConClear release carries a tool-specific compatibility policy for every
952
+ host tool: an inclusive minimum, an exclusive maximum and explicitly excluded
953
+ versions with known defects or advisories, derived from the flags, output fields
954
+ and behaviors each adapter uses. The policy is a compatibility statement, not
955
+ run identity; the exact versions ConClear's real-tool tiers ran against are
956
+ recorded separately as tested versions, and the real-tool tier fails on a host
957
+ whose version is not yet listed. Every command declares the host tools its call
958
+ path executes and resolves only those: it resolves each executable to an
959
+ absolute path, parses its canonical version, records that version and the
960
+ executable digest, and rejects a version outside the accepted interval or in the
961
+ exclusion list with a diagnostic naming the observed version, the interval, the
962
+ exclusions and the tested versions. A run pins a tool's identity from the first
963
+ phase that resolves it. A later phase that resolves the same tool must observe
964
+ the identical executable, a phase that first uses a tool binds it then, and a
965
+ promoted or rejected run records nothing further. `release` resolves the
966
+ complete toolchain at start and holds it constant; ConClear rechecks every
967
+ recorded identity before later use so a package upgrade during a run cannot
968
+ silently change the toolchain, and distributed qualifications of one release
969
+ must report identical normalized tool versions, never merely compatible ones. A
970
+ later release run may use newer accepted tools. `doctor` validates one scope,
971
+ `check`, `qualify` or `release`, by resolving the union of the tools those
972
+ commands declare and reporting every failure instead of the first.
973
+
974
+ All external commands use argument arrays, sanitized environments, explicit
975
+ timeouts, bounded retries and captured logs. Cosign machine responses use
976
+ private temporary files, with a 128 MiB response limit, separately from the 1
977
+ MiB redacted diagnostic capture. Overflow fails explicitly before JSON parsing;
978
+ DSSE payloads also have a 16 MiB base64-encoded size limit. Temporary responses
979
+ are removed after consumption or failure. ConClear never constructs a shell
980
+ command from project input. Logs redact credentials, authorization headers,
981
+ passphrases and secret mount paths before they are persisted or displayed.
982
+
983
+ Buildah receives a run-specific root and runroot. Podman imports use run-owned
984
+ names and are resolved back to their immutable manifest digest before testing.
985
+ No command relies on the user's mutable short-name search configuration.
986
+
987
+ <a id="promise-ip0018"></a>
988
+ The Trivy database cache lives under `$XDG_CACHE_HOME/conclear/`. Refresh uses a
989
+ lock, a same-filesystem temporary directory, validation and atomic rename. At
990
+ release start, ConClear selects one validated database snapshot and holds its
991
+ content digest constant across every platform scan in the release. That digest
992
+ binds both database files and their normalized schema versions, update times
993
+ and next-update times. Local download timestamps are recorded but do not change
994
+ snapshot identity. A stale or corrupt cache triggers one bounded refresh and
995
+ never falls back silently to unvalidated data.
996
+
997
+ `freshness.QUALIFICATION_WINDOW` defines a 24-hour maximum from the original
998
+ fresh snapshot selection. Both database components must have been updated by
999
+ that start time and must not yet have reached their next-update time. The
1000
+ orchestrated release records its start and database digest before qualification;
1001
+ resume reuses them. Distributed workers pin the same snapshot and pass the
1002
+ original `--qualification-started-at` value when joining an existing window.
1003
+ Without that option, a pinned snapshot must be fresh at the worker's own start.
1004
+ An expired pinned selection fails without refreshing or substituting databases.
1005
+
1006
+ Qualification records carry `qualificationWindow.startedAt` and `expiresAt`, and
1007
+ record the actual completion time. Pin freshness and divergence deadlines for
1008
+ the image and its test dependencies, and the expiry of applied vulnerability
1009
+ exceptions, can shorten approval. Assembly retains the earliest start and
1010
+ deadline across platforms. Completion, assembly, candidate publication and
1011
+ signed release verification require current approval; promotion checks the
1012
+ deadline in the authenticated verification statement. Delayed phases and resume
1013
+ cannot renew it. The seven-day candidate authorization limit does not extend
1014
+ release approval. Expiry requires a new qualification run with fresh evidence.
1015
+
1016
+ Historical record inspection does not impose a current-age gate. Rescanning a
1017
+ published digest verifies the original signed evidence and uses a fresh database
1018
+ for the new assessment; an expired release qualification window does not reject
1019
+ that historical evidence.
1020
+
1021
+
1022
+ ## Build and qualification<a id="build-and-qualification"></a>
1023
+
1024
+ <a id="promise-ip0019"></a>
1025
+ `release` holds the selected commit twice. It creates an isolated detached
1026
+ worktree under `checkout/` and exports that worktree's index, which is exactly
1027
+ the commit's tracked tree, into `source/`. Builds, static checks, evidence and
1028
+ archives read only the export, so neither the ordinary checkout's uncommitted
1029
+ and untracked files nor anything written during the run can enter a build
1030
+ context or an archive. ConClear validates `.containerignore`, rejects source
1031
+ paths outside the export and records the exact Containerfile and configuration
1032
+ digests. Hadolint runs with the image's context directory as its working
1033
+ directory and, when that directory contains a committed regular `.hadolint.yaml`
1034
+ or `.hadolint.yml`, receives it explicitly, so the reviewed source rather than
1035
+ the invoking directory or the operator's home defines lint policy.
1036
+
1037
+ The run binds the export's content digest: every regular file and symbolic link
1038
+ with its bytes, mode and link target. Reopening a run and the build, test and
1039
+ evidence boundaries recheck that binding twice: the export must be unchanged,
1040
+ and the Git checkout must still present every exported path with identical
1041
+ bytes, mode and link target. A change to either is rejected without repair;
1042
+ older runs without the binding must be restarted. Repository hooks run inside
1043
+ the checkout, and the tools they invoke may leave untracked files there, such as
1044
+ virtual environments, bytecode and test caches, because nothing later in the
1045
+ run reads the checkout. A clean Git status alone is not sufficient evidence of
1046
+ unchanged source bytes, so the tracked comparison hashes content rather than
1047
+ consulting Git's index.
1048
+
1049
+ Trivy runs from a fresh private directory with explicit ConClear-owned
1050
+ configuration, ignore and secret-rule files. Ambient and repository Trivy
1051
+ suppression files do not define release policy. Image scanning covers both
1052
+ filesystem contents and OCI configuration/history, including secrets inherited
1053
+ from a base image. A report without image-configuration check results is an
1054
+ operational failure, not a clean scan. The reviewed-root contract exempts only
1055
+ Trivy's DS-0002 root-user check; other findings remain subject to the normal
1056
+ policy and the applied root justification is retained in evidence.
1057
+
1058
+ <a id="promise-ip0020"></a>
1059
+ Buildah produces OCI format in rootless mode and exports an OCI layout. ConClear
1060
+ derives `SOURCE_DATE_EPOCH` from the source commit time where the project build
1061
+ supports it. Timestamp rewriting is a build input and is never applied after
1062
+ testing.
1063
+
1064
+ ConClear supplies `IMAGE_REVISION` from the full observed source commit,
1065
+ `IMAGE_VERSION` from the validated release version when present and
1066
+ `IMAGE_CREATED` as an RFC 3339 representation of the controlled source
1067
+ timestamp. It inspects the final image configuration and rejects missing
1068
+ mandatory `org.opencontainers.image.*` labels or source, revision, version and
1069
+ creation values that disagree with those observations. It rejects the
1070
+ `org.opencontainers.image.licenses` label and its legacy `license` forms, builds
1071
+ with label inheritance disabled and rejects any label the Containerfile did
1072
+ not declare, and verifies the `base.name` and `base.digest` manifest
1073
+ annotations Buildah writes against the declared pin and the platform manifest
1074
+ that pin resolves to, rejecting any other manifest annotation.
1075
+
1076
+ <a id="promise-ip0021"></a>
1077
+ Every release includes `linux/amd64`. `linux/arm64` is optional and required
1078
+ only when declared; omitting it needs no reason and leaves no trace in
1079
+ configuration or evidence. `native_test_platforms` must be a subset of
1080
+ `platforms` and makes native runtime testing mandatory for the listed targets;
1081
+ emulated tests reject qualification there. Every other target qualifies through
1082
+ native or QEMU user-mode emulated build and runtime tests alike, provided build
1083
+ and test records identify the target, host, execution architecture and emulation
1084
+ mechanism; ConClear neither asks for nor records a justification for emulation.
1085
+ KVM is recorded only as acceleration for an executable guest architecture and is
1086
+ never treated as cross-architecture emulation. Before building or testing a
1087
+ platform whose architecture differs from the host, ConClear requires an enabled
1088
+ `binfmt_misc` handler for that architecture; without one the phase fails
1089
+ operationally, names the missing handler and leaves the platform unqualified,
1090
+ and ConClear never installs emulators or registers handlers itself. Assembly
1091
+ rejects a qualification record whose execution observation claims native
1092
+ execution for a foreign architecture or emulated execution for the host
1093
+ architecture.
1094
+
1095
+ At configuration-to-layout boundaries, `linux/arm64` and `linux/arm64/v8` select
1096
+ the same target because an omitted OCI arm64 variant denotes the v8 baseline.
1097
+ This equivalence applies to required-platform, native-test, dependency-coverage,
1098
+ qualification-transport and assembly checks. Other variants remain distinct, and
1099
+ assembled descriptors and platform-manifest evidence retain the exact variant
1100
+ observed in the image configuration.
1101
+
1102
+ <a id="promise-ip0022"></a>
1103
+ For each platform, ConClear validates the primary layout and every declared
1104
+ test-image dependency recursively, imports them through a digest-preserving
1105
+ containers-storage path, resolves every imported manifest and compares it with
1106
+ the corresponding layout digest before starting preparation or tests. A mismatch
1107
+ is an operational failure. Tests address run-owned names created from those
1108
+ verified imports rather than mutable registry references.
1109
+
1110
+ Before the primary container starts, ConClear creates declared output
1111
+ directories with private ownership and validates every repository fixture
1112
+ without following symbolic links. It rejects path escape, symbolic links,
1113
+ special files, unsafe ownership or modes, writable repository fixtures,
1114
+ undeclared mount sources, overlapping container targets and writable
1115
+ destinations outside the selected image's declared runtime mounts. Preparation
1116
+ containers run sequentially from exact imported images. After each step ConClear
1117
+ verifies its exit status and the ownership, type and mode of every generated
1118
+ output before a later step may consume it.
1119
+
1120
+ <a id="promise-ip0023"></a>
1121
+ Built-in runtime checks cover the configured user, root filesystem mode,
1122
+ writable mounts, private user and cgroup namespaces, absence of privileged
1123
+ mode, capabilities, `no-new-privileges`, startup, health command, signal
1124
+ forwarding, expected exit-status propagation, shutdown, file ownership and
1125
+ resource behavior. The observed writable destinations must equal the effective
1126
+ declared set; this comparison includes anonymous volumes created from image
1127
+ metadata. Runtime application files expected to remain immutable are checked
1128
+ for root ownership and permission modes that deny group and other writes. They
1129
+ cannot overlap a writable runtime mount. For a non-root runtime identity,
1130
+ owner-write bits do not grant that identity access and are not rejected. For
1131
+ UID 0 with a read-only root, the root and non-overlap requirements keep those
1132
+ paths immutable. An authorized administrator on a writable root can change
1133
+ them; ownership checks then protect against direct unprivileged writes only.
1134
+ Launch arguments and non-secret environment values supplement the
1135
+ image's original entrypoint; they cannot replace it or override a built-in gate.
1136
+
1137
+ Every runtime container uses rootless Podman with an explicit private user
1138
+ namespace and private cgroup namespace. Container UID 0 therefore maps through
1139
+ the invoking rootless user's namespace and does not grant host root. ConClear
1140
+ never enables privileged mode, a host user or cgroup namespace, host devices or
1141
+ repository-selected writable host paths. Only separately declared, run-owned
1142
+ test outputs may be writable bind mounts. It drops every capability before
1143
+ adding only the exact reviewed set in configuration and verifies the resulting
1144
+ bounding and effective sets. These constraints apply equally to the systemd
1145
+ profile.
1146
+
1147
+ Permission probes use separate, journaled containers from the exact imported
1148
+ artifact. The sudo probe resolves declared executables, checks their set-ID
1149
+ modes and root ownership, and checks that their parent directories are not
1150
+ writable by unprivileged users. It validates sudoers with `visudo -c`, checks
1151
+ policy ownership and parents, and retains the validated files with their
1152
+ digest in the test report. The permitted operation must succeed with exact
1153
+ stdout; the denied caller must fail both authorization and execution. ConClear
1154
+ queries that caller's authorization as container root with `sudo -l -U`, so a
1155
+ missing password cannot be mistaken for policy denial. It executes the
1156
+ negative operation as the actual non-root caller and records both identities.
1157
+ The probe observes each non-root caller's UID and kernel `NoNewPrivs` flag.
1158
+
1159
+ A functional contract with escalation, a writable root or extra capabilities
1160
+ also receives a restrictive probe with read-only root, no capabilities and
1161
+ `no-new-privileges`. Sudo escalation must fail there. Generic restrictive probes
1162
+ check effective controls without requiring administrative startup to succeed.
1163
+ Set-ID inspection and the immutable-path probe require a POSIX shell, `sleep`,
1164
+ `readlink` and `stat`; both are spelled so that BusyBox satisfies them.
1165
+ Escalation tests on an emulated platform need a `binfmt_misc` handler
1166
+ registered with the `C` flag, because without it a set-user-ID binary runs with
1167
+ the caller's credentials under user-mode emulation; ConClear refuses the test
1168
+ with the host cause instead of reporting a policy failure.
1169
+ Sudo tests also require `id`, `cat`, `env` and the image's sudo/visudo
1170
+ implementation. The probes are
1171
+ removed before the primary lifecycle test; declared launch fixtures are the
1172
+ same in each container.
1173
+
1174
+ ConClear also inventories the merged filesystem of every qualified layout
1175
+ without running anything from the image: it reads the layer archives in order,
1176
+ applies replacements, `.wh.` whiteouts and opaque directory markers, and lists
1177
+ every regular executable carrying a set-user-ID or set-group-ID bit with its
1178
+ mode, ownership and hard-link aliases. Set-group-ID directories are recorded
1179
+ separately because they grant no privilege. An executable that no declared
1180
+ sudo or `setid_requirements` path resolves to, through the image's symbolic
1181
+ links, and a declared path that is not a set-ID executable are `CC0406`
1182
+ rejections, so a base-image or dependency update cannot reintroduce an
1183
+ unreviewed privilege. The inventory is retained in the platform record. The
1184
+ adequacy of each declared executable and authorization scope remains a reviewed
1185
+ responsibility.
1186
+
1187
+ For a systemd image, ConClear explicitly enables Podman's systemd mode and
1188
+ applies the `SIGRTMIN+3` stop signal. It verifies that PID 1 is `systemd`, that
1189
+ a `systemctl` manager query succeeds and that every configured required unit
1190
+ becomes active. The manager query, the required-unit probes and an optional
1191
+ application health command share the one monotonic startup budget, and a manager
1192
+ query that cannot reach `systemd` yet is retried at the same bounded interval
1193
+ because `systemd` creates its socket shortly after the container starts, and its
1194
+ result records the attempts and elapsed time like every other readiness probe.
1195
+ The profile then sends `SIGRTMIN+3` and applies the ordinary bounded shutdown
1196
+ and exit-status checks. Failure of PID 1, manager, unit, health or shutdown
1197
+ expectations produces a `CC0403` rejection; an inability to invoke or observe
1198
+ Podman remains an operational failure.
1199
+
1200
+ For a service health command, a nonzero application status means not ready and
1201
+ is retried at a bounded implementation-owned interval until success or the
1202
+ configured startup deadline. The startup timeout is one monotonic readiness
1203
+ budget: every probe is bounded by its remaining time and cannot restart the
1204
+ budget. A service that exits before readiness or remains unhealthy at the
1205
+ deadline produces a `CC0403` rejection. A Podman operation failure, an
1206
+ unavailable or unexecutable health command, or an inability to inspect the
1207
+ service remains an operational failure rather than an application-health result.
1208
+ Readiness evidence records the attempt count, configured timeout, elapsed wait,
1209
+ final command status, final container state and a digest of the final bounded
1210
+ redacted command output; run-owned command logs retain that bounded output for
1211
+ diagnostics.
1212
+
1213
+ Once a service or systemd container is ready, ConClear records its footprint as
1214
+ an advisory `footprint` test result: the cgroup memory peak, which includes
1215
+ page cache, the task peak and the number of open files of its processes,
1216
+ counted inside the rootless user namespace. `test` and `qualify` show these
1217
+ numbers next to the declared `memory`, `pids` and `nofile` limits so that the
1218
+ limits can start provisional and be tightened from evidence. A host without
1219
+ readable cgroup statistics records the result as skipped; a one-shot container
1220
+ exits before the observation and is skipped as well. The footprint never gates
1221
+ a qualification.
1222
+
1223
+ Smoke tests apply explicit memory, CPU, process and file-descriptor limits from
1224
+ repository configuration and record the effective values. Health checks run the
1225
+ repository-declared command; ConClear does not expect an OCI image to contain
1226
+ Docker-format `HEALTHCHECK` metadata. Test evidence records hashes of the launch
1227
+ declaration, each preparation declaration, the exact image manifest supplying
1228
+ its executable, non-secret fixture and output trees, and every dependency layout
1229
+ and manifest. Secret output facts identify the producing step and use but omit
1230
+ values, paths and content digests.
1231
+
1232
+ <a id="promise-ip0024"></a>
1233
+ Repository hooks add application-specific assertions but cannot skip built-in
1234
+ gates. A hook receives a run-owned non-secret test-input manifest containing the
1235
+ primary and dependency layout paths, immutable digests and non-secret generated
1236
+ output handles, and a run-owned scratch directory for its own working data; it
1237
+ receives no mutable image reference or secret output path.
1238
+ Hooks are reviewed source commands run with ConClear's sanitized host
1239
+ environment, but ConClear cannot sandbox them from invoking other host
1240
+ executables. Their recorded executable and output identities make that trust
1241
+ boundary observable; a hook-side rebuild or pull cannot replace ConClear's
1242
+ exact-image built-in results.
1243
+
1244
+ ConClear destroys preparation containers, generated outputs and secret material
1245
+ on success and on ordinary failure cleanup. Cleanup failures preserve failed
1246
+ journal entries and identify retained resources; they do not authorize deletion
1247
+ of an unjournaled path. ConClear does not provide a success-retention mode for
1248
+ test secrets.
1249
+
1250
+ <a id="promise-ip0025"></a>
1251
+ Trivy is the authoritative scanner for packages, vulnerabilities, secrets and
1252
+ configuration. It scans the build context for secrets, the Containerfile and
1253
+ image configuration for insecure settings, and the final layout for packages,
1254
+ vulnerabilities, secrets and configuration. A fixable `HIGH` or `CRITICAL`
1255
+ vulnerability rejects qualification unless an exact, approved and unexpired
1256
+ repository exception applies. Each such finding names the severity source the
1257
+ scanner selected, a distribution vendor's rating or NVD's, and the evidence
1258
+ keeps every vendor rating the scanner saw, because the two often disagree and an
1259
+ exception review has to know which rating it accepts. A package inventory is
1260
+ `assessed` only when the scanner produced a package vulnerability result for the
1261
+ operating system it detected; a detected operating system without such a result
1262
+ is `unassessed`, never clean, and rejects qualification (`CC0506`) unless a
1263
+ reviewed, expiring `package_assessment_exception` applies. The qualification
1264
+ record and every rescan result state the assessment status, the operating
1265
+ system, the package count and any applied exception. A record written before
1266
+ ConClear evaluated scanner coverage omits the field; absence means the writer
1267
+ did not evaluate it, never that the packages were assessed, and a rescan always
1268
+ records its own fresh assessment. Trivy is the only supported scanner stack, and
1269
+ exactly one vulnerability result gates a release. Every rejecting scan runs
1270
+ against local content and the digest-addressed layout before publication. Every
1271
+ failed Trivy configuration check rejects qualification except `DS-0026`, which
1272
+ demands a Containerfile `HEALTHCHECK` that the guide forbids in OCI-format
1273
+ images and `CC0112` rejects, and `DS-0002` when the image declares UID 0 with a
1274
+ reviewed `root_requirement`. The first check is inapplicable by construction;
1275
+ the second exemption records the declared root justification. Neither exempts
1276
+ secrets, vulnerabilities or unrelated configuration findings.
1277
+
1278
+ Each platform SBOM is SPDX 2.3 JSON. ConClear validates the document, records
1279
+ its exact specification version and exports the raw JSON. Scan reports, SBOMs
1280
+ and finding locations name the scanned subject by its repository and manifest
1281
+ digest or a path relative to the build context; the release host's directory
1282
+ layout never enters evidence.
1283
+
1284
+ <a id="promise-ip0026"></a>
1285
+ A qualification is validated in one of two ways and never by rewriting it. A
1286
+ record owned by the assembling run must name that run. A transported record
1287
+ keeps its worker run identity and is accepted only through a transport: the
1288
+ coordinator compares the transport with a digest the caller supplied
1289
+ independently before trusting any member, stages the content below its own
1290
+ workspace through the bounded, link-free archive extractor or a member-by-member
1291
+ copy that follows no symbolic link, and rejects absolute paths, traversal,
1292
+ symbolic and hard links, device nodes, duplicate, missing and undeclared members
1293
+ and size or member-count abuse. It then verifies every member against the
1294
+ manifest, the qualification-record digest, the record schema and verdict, the
1295
+ layout graph, the platform descriptor and image configuration, the
1296
+ platform-manifest digest and the evidence payloads, installs the verified copies
1297
+ at the standard workspace locations under journaled ownership, and only then
1298
+ reads the qualification. Failed imports retain their staging directory under a
1299
+ failed journal entry for cleanup.
1300
+
1301
+ <a id="promise-ip0027"></a>
1302
+ Assembly requires matching source repository and revision, repository
1303
+ configuration, guide and ConClear identity and release version across all
1304
+ qualifications and against the coordinator run; a qualification produced by
1305
+ another ConClear revision is rejected without an override. For every external
1306
+ tool used on multiple platform workers, its normalized reported version must
1307
+ match. Platform-specific executable digests may differ and remain recorded in
1308
+ each qualification. The authoritative vulnerability-database content digest, the
1309
+ declared pin set, the observed pin digests and the effective limits must match
1310
+ exactly across all platform qualifications. Assembly also compares OCI platform
1311
+ descriptors with image configuration, rejects missing, duplicate and unexpected
1312
+ platforms, and creates one image index for a multi-platform release whose
1313
+ candidate reference is named for the coordinator run. No record with an
1314
+ incomplete or rejected verdict can enter a candidate, and copying workspaces or
1315
+ records outside a transport is not a supported path.
1316
+
1317
+
1318
+ ## Publication and promotion<a id="publication-and-promotion"></a>
1319
+
1320
+ <a id="promise-ip0028"></a>
1321
+ Public foundata images are published to configured repositories on `quay.io`.
1322
+ Consumed images and prepublication release destinations may use another fully
1323
+ qualified authoritative registry. ConClear rejects short names and does not
1324
+ rewrite an upstream reference to prefer one provider.
1325
+
1326
+ ConClear separates OCI transport from provider control. Skopeo copies and
1327
+ resolves OCI content. A compiled registry backend observes and changes
1328
+ provider-specific tag controls. The release profile selects the backend
1329
+ explicitly, and the complete `publish` through `promote` workflow rejects an
1330
+ incompatible destination before qualification or remote mutation. The local
1331
+ `check`, `pins check`, `build`, `test`, `qualify`, `assemble` and `provenance`
1332
+ stages remain available for destinations without a supported backend.
1333
+
1334
+ `quay` is the only implemented backend. Another one is added only for a named
1335
+ provider that a maintained consumer must publish to, never as a generic OCI
1336
+ backend, because the contract needs control-plane operations the distribution
1337
+ API does not have. Before admission, the network tests must prove against the
1338
+ real service:
1339
+
1340
+ 1. Exact tag observation: digest, expiration and immutability from a
1341
+ single-tag query, `None` for an absent tag, and a refusal on duplicate or
1342
+ prefix matches.
1343
+ 2. Digest-preserving graph handling: a multi-platform index round-trips through
1344
+ Skopeo with an unchanged graph fingerprint.
1345
+ 3. Cosign referrers: signing, attesting, verifying and downloading
1346
+ attestations succeed under the protection policy production will use; a
1347
+ provider without the referrers API needs the `sha256-<digest>` fallback tag
1348
+ to stay writable under that policy, asserted by a test.
1349
+ 4. Independently enforced candidate lifetime: a per-tag deadline or a
1350
+ pattern-scoped retention policy that reads back through the API.
1351
+ 5. Exact tag assignment with a refusal when the tag already names another
1352
+ digest.
1353
+ 6. Owned-tag deletion, and the provider's refusal to delete a protected
1354
+ version tag.
1355
+ 7. Recovery from an ambiguous write by re-reading the state after an injected
1356
+ transport failure.
1357
+ 8. Immutability against the real service: a protected tag rejects a re-push,
1358
+ selective policies are accepted and repository-wide protection is rejected.
1359
+ 9. Provider independence: publication, promotion and registry policy code stay
1360
+ unchanged; the backend lives in its own adapter module and reports through
1361
+ the existing `doctor`, `config show` and release summary fields.
1362
+
1363
+ The protected profile requires two explicit choices:
1364
+
1365
+ - `registry.tag_protection.mode`: `required` verifies selective version-tag
1366
+ protection before upload and promotion, and protection on assignment.
1367
+ `not-enforced` needs a nonempty rationale and owner. ConClear still refuses
1368
+ conflicting version tags, but cannot prevent another writer from changing
1369
+ them. Restrict writer permissions and consume released images by digest.
1370
+ - `registry.candidate_cleanup.mode`: `manual`, `tag-expiration` or `auto-prune`.
1371
+ Every mode needs a cleanup owner and procedure for abandoned runs. Automation
1372
+ is recommended; manual cleanup needs no provider policy API.
1373
+
1374
+ There are no implicit defaults or fallback after provider errors. Required
1375
+ protection and selected cleanup APIs must work. Repository-wide locking is
1376
+ unsuitable because candidates and moving tags must remain mutable. ConClear
1377
+ does not create immutability policies. Profile choices appear in `config show`,
1378
+ `doctor`, publication and promotion results, and signed release verification.
1379
+ Rationale, owner and procedure are public evidence: keep secrets out of them.
1380
+
1381
+ <a id="promise-ip0029"></a>
1382
+ The default candidate lives in the final release repository so signatures and
1383
+ OCI referrers remain with the subject. A versioned release uses
1384
+ `<version>-candidate.<run-id>.g<source-revision-short>`. An unversioned release
1385
+ uses `g<source-revision-short>-candidate.<run-id>`. ConClear generates the
1386
+ lowercase ULID, uses the first eight hexadecimal characters of the full source
1387
+ revision for the short form and validates every component before creating the
1388
+ tag.
1389
+
1390
+ `publish` checks that the candidate tag is unused and journals its digest,
1391
+ selected policy and original authorization deadline before uploading content.
1392
+ This deadline bounds ConClear authorization even if registry expiration is
1393
+ absent or changed. Resume reuses the recorded deadline; it cannot renew
1394
+ approval.
1395
+
1396
+ With `auto-prune`, the Quay adapter first creates or reuses a `creation_date`
1397
+ policy whose anchored pattern matches only generated ConClear candidates.
1398
+ Its maximum age cannot exceed the configured candidate lifetime. Existing
1399
+ policies are never broadened or relaxed; stricter policies can remove candidates
1400
+ earlier. The policy remains across runs, with its ID, pattern and age journaled.
1401
+ Unavailable or unverifiable policy APIs stop publication before upload.
1402
+
1403
+ ConClear then copies the accepted
1404
+ manifest or index with Skopeo's digest-preserving path, including every platform
1405
+ for an index. It resolves the remote index, platform manifests and referenced
1406
+ content and compares the complete graph with the local candidate. Registries do
1407
+ not provide a portable compare-and-swap operation, so pre-write checks detect
1408
+ ordinary collisions while post-write verification determines success.
1409
+
1410
+ After upload, `tag-expiration` and `auto-prune` set and verify per-tag
1411
+ expiration. Only auto-prune covers the interval between a successful upload and
1412
+ setting expiration without another ConClear invocation. Manual mode observes the
1413
+ candidate without requiring expiration. All modes retain ownership state for
1414
+ cleanup after a lost acknowledgement or interrupted upload. ConClear keeps
1415
+ candidate tags mutable so expiration and deletion remain possible. A failed or
1416
+ ambiguous publication is recorded for cleanup; resume reuses its tag only after
1417
+ conclusively resolving it to the unchanged expected digest within its lifetime.
1418
+ Candidate content and evidence must be safe for public disclosure; later
1419
+ provider garbage collection is outside the release verdict.
1420
+
1421
+ Quay's auto-pruner runs asynchronously. When selected, operators must keep that
1422
+ service enabled, monitor its execution and account for its scheduling delay
1423
+ when setting retention limits. Observing the policy through the API proves
1424
+ its configuration, not the health or timing of a remote worker. This is a
1425
+ registry operation and does not require a build CI service.
1426
+
1427
+ <a id="promise-ip0030"></a>
1428
+ Promotion first confirms that the candidate has not expired, then resolves and
1429
+ verifies the signed release-verification attestation. It refuses to replace an
1430
+ version tag that already names another digest. Both the original candidate
1431
+ deadline and qualification window must remain current before each tag write.
1432
+ The signed record binds the original deadline and selected registry policy;
1433
+ changing local state cannot renew that authorization. Registry expiration may
1434
+ shorten the usable interval but cannot extend it.
1435
+
1436
+ When protection is `required`, ConClear reads repository and organization
1437
+ policies and requires coverage of every final version tag while excluding
1438
+ the candidate and declared moving tags. Quay policy checks
1439
+ use the same regular-expression engine and full-match semantics as Quay, with a
1440
+ bounded match time; malformed or timed-out policies fail closed. ConClear never
1441
+ changes these policies. The API token needs repository and organization policy
1442
+ read access. Promotion writes only the verified digest, requires protection on
1443
+ assignment when selected, resolves every tag afterward and records the result.
1444
+ `immutabilityEnabled` in promotion results and the release summary is true only
1445
+ when protection was required and verified for all configured version tags; it
1446
+ is not a claim about other tags or future registry administration. A partial
1447
+ multi-tag update is an operational failure and is never hidden by rollback or
1448
+ repointing.
1449
+
1450
+ After successful promotion, ConClear deletes the candidate tag and verifies its
1451
+ removal in every cleanup mode. The declared owner handles abandoned or rejected
1452
+ candidates with `cleanup` or the selected registry mechanism and monitors any
1453
+ cleanup automation. Failure to delete after successful promotion is
1454
+ reported as cleanup failure without changing the release digest's verified
1455
+ status.
1456
+
1457
+
1458
+ ## Provenance, signing and verification<a id="provenance-signing-and-verification"></a>
1459
+
1460
+ <a id="promise-ip0031"></a>
1461
+ ConClear generates release provenance as an in-toto Statement with a SLSA
1462
+ Provenance v1 predicate. It derives the subject graph from
1463
+ `release-candidate.json`, the source revision from the isolated Git checkout,
1464
+ the public source URL from the reviewed configuration,
1465
+ builder identity from the protected release profile, ConClear implementation
1466
+ identity from embedded data, and the run identity from observed execution.
1467
+ Repository configuration, CI environment metadata, labels and arbitrary
1468
+ command-line values cannot override those identities.
1469
+
1470
+ The SLSA builder ID is a stable, credential-free HTTPS documentation URI naming
1471
+ one complete build-platform trust domain. The protected release profile supplies
1472
+ it, and the workspace binds it as an immutable release input.
1473
+ Security-significant environments use different builder IDs.
1474
+ `runDetails.builder.version` records the ConClear version and full source
1475
+ revision, so application upgrades do not change the identity of an otherwise
1476
+ unchanged build platform.
1477
+
1478
+ The first documented builder is
1479
+ `https://foundata.com/en/projects/conclear/builder/simple-v1/`. It covers the
1480
+ foundata operator-controlled workstation environment and claims SLSA Build L1
1481
+ only. An arbitrary CI worker is outside that trust domain, even when it invokes
1482
+ ConClear. A separately controlled CI platform needs its own builder identity and
1483
+ documentation; ordinary provider environment variables cannot authenticate or
1484
+ select it.
1485
+
1486
+ Materials include the declared public source URL with the full commit,
1487
+ Containerfile, repository configuration, external image digests and other
1488
+ integrity-checked dependencies known to the build. Parameters exclude
1489
+ credentials and secret values. Verification requires the exact profile-selected
1490
+ builder ID and embedded ConClear version, then binds the accepted builder ID
1491
+ into `release-verification.json`. Consumers accept only explicitly configured
1492
+ signer and builder pairs.
1493
+
1494
+ The predicate is generated from the accepted candidate before publication. After
1495
+ publication, ConClear verifies the final registry digest, validates that it
1496
+ equals the predicate subject and only then attaches provenance. Cosign wraps
1497
+ every attestation around exactly one subject, so ConClear attaches the
1498
+ provenance predicate to the index digest and, separately, to each platform
1499
+ manifest digest; the local `provenance.json` statement records the complete
1500
+ subject set and every attached copy must carry the identical predicate.
1501
+
1502
+ <a id="promise-ip0032"></a>
1503
+ The baseline signer is a foundata-managed Cosign key pair. The encrypted private
1504
+ key and its passphrase are supplied to the authorized release environment
1505
+ through protected secret mechanisms; the approved public key is supplied
1506
+ independently through maintainer-controlled trust configuration. A KMS- or
1507
+ HSM-protected key SHOULD be used when that infrastructure is available. A
1508
+ workstation holding the managed signing authority can produce a valid release.
1509
+
1510
+ Signer identity is the SHA-256 fingerprint that ConClear computes from the
1511
+ approved public key, or the managed-key identity resolved by the Cosign adapter.
1512
+ It remains distinct from the build-platform identity, ConClear implementation
1513
+ identity and Git source identity. Neither private key material nor its
1514
+ passphrase appears in a project file, command-line literal, ordinary environment
1515
+ configuration, log, provenance statement or evidence record.
1516
+
1517
+ The managed-key-pair baseline requires a supported registry backend, the private
1518
+ key, the independently supplied public key and access to Cosign's supported
1519
+ default public Sigstore transparency service. The Cosign adapter uses run-owned
1520
+ configuration directories and an adapter-controlled release configuration so
1521
+ ambient user settings cannot replace or disable that service. It does not expose
1522
+ a release option to disable log upload or ignore log verification. Failure to
1523
+ obtain or verify log inclusion stops the release.
1524
+
1525
+ No ConClear command signs before `publish`. In particular, `check`, `build`,
1526
+ `test`, `qualify`, `assemble` and `provenance` produce no signature or
1527
+ transparency-log entry. ConClear provides neither a manual signing-experiment
1528
+ mode nor a no-log release mode; manual signing experiments use disposable test
1529
+ keys outside ConClear as described by the guide.
1530
+
1531
+ `attest` resolves the remote subject again, attaches one signed SBOM attestation
1532
+ to each platform manifest, attaches provenance covering the index and platforms,
1533
+ and signs the index digest and every platform-manifest digest. Every operation
1534
+ obtains public transparency-log inclusion. A single-platform release signs its
1535
+ manifest once. Partial attachment, signing or log inclusion leaves an unverified
1536
+ candidate and blocks promotion; retry first verifies the unchanged expected
1537
+ subject graph.
1538
+
1539
+ The signed SPDX attestation is the repository-scoped consumer copy. ConClear
1540
+ retrieves and validates its predicate through Cosign during verification and
1541
+ rescans; it does not use Cosign's deprecated unsigned raw SBOM attachment
1542
+ command. The platform qualification and the release-verification record name
1543
+ the SPDX version of each SBOM and the exact predicate type it was attached
1544
+ under. Verification, rescans and archive checks take both values from the record
1545
+ and reject a retrieved document that declares another SPDX version; a record
1546
+ written before these fields existed is read as SPDX 2.3 under
1547
+ `https://spdx.dev/Document`.
1548
+
1549
+ Attestation consumers decode the DSSE envelopes returned by
1550
+ `cosign verify-attestation` with the approved public key. Those authenticated
1551
+ payloads supply the predicates used by verification, promotion, retry recovery
1552
+ and rescan history. A retry may download an attestation to observe its presence;
1553
+ downloaded payloads never substitute for verified entries, even when the entry
1554
+ counts or subject names agree.
1555
+
1556
+ <a id="promise-ip0033"></a>
1557
+ `verify` starts from the candidate digest rather than its tag. It recursively
1558
+ compares the registry graph with the candidate, verifies every required image
1559
+ signature and transparency-log inclusion against the external trust root,
1560
+ retrieves and verifies one SBOM per platform, validates the recorded SPDX
1561
+ version, verifies provenance subject coverage, signer identities and log
1562
+ inclusion, and checks that all evidence digests match the qualification records.
1563
+
1564
+ After those checks pass, ConClear creates `release-verification.json`, records
1565
+ the single-subject in-toto Statement it expects Cosign to produce, has Cosign
1566
+ sign and attach the record as that statement's predicate with public log
1567
+ inclusion, retrieves it again and verifies its subject, predicate digest, signer
1568
+ and log inclusion. Only that post-attachment success advances the run to
1569
+ `verified`. Promotion immediately repeats verification of this attestation, its
1570
+ log inclusion and the subject digest.
1571
+
1572
+
1573
+ ## Rescans<a id="rescans"></a>
1574
+
1575
+ <a id="promise-ip0034"></a>
1576
+ `conclear rescan --subject <repository>@<digest> --image <image>` accepts an
1577
+ immutable released subject. It retrieves and verifies the signed
1578
+ release-verification attestation, including transparency-log inclusion, and
1579
+ rejects a missing or conflicting result. It takes the required
1580
+ repository-configuration digest from that predicate, then enumerates every
1581
+ platform manifest, retrieves each signed SBOM, verifies the attestation, signer
1582
+ and log inclusion against the external trust root, and evaluates the current
1583
+ vulnerability data for the complete platform set.
1584
+
1585
+ Repeat releases may attach several accepted records to the same digest.
1586
+ ConClear deduplicates identical predicates and selects the earliest record
1587
+ matching the exact configuration, with the record digest breaking timestamp
1588
+ ties. Matching records must agree on source, platform, builder and signer
1589
+ identities. Each consumed SBOM must match its verified platform subject and a
1590
+ hash referenced by the selected record. SPDX files are canonical JSON before
1591
+ their hashes are recorded, so attestation reserialization preserves those
1592
+ identities. A rescan records `releaseRecordDigest`;
1593
+ later rescans retain that anchor and fail if its evidence is missing.
1594
+
1595
+ An SBOM rescan is explicitly recorded as vulnerability matching against retained
1596
+ inventory only. A rescan that requires secret or configuration analysis
1597
+ repeats those scans against immutable image content. Both scopes currently
1598
+ retrieve the released image graph; neither is an offline bundle reader. Partial
1599
+ platform coverage cannot produce an accepted result.
1600
+
1601
+ The supplied repository configuration must have the exact byte digest recorded
1602
+ in the original signed release verification. Retain the original checkout,
1603
+ because loading the configuration also resolves its Containerfiles, contexts
1604
+ and test paths. Editing today's `conclear.toml` cannot change historical rescan
1605
+ policy. Separate triage input records later decisions. Rescans do not rebuild
1606
+ or execute the retained source, and an expired historical qualification window
1607
+ does not invalidate the original signed evidence.
1608
+
1609
+ The rescan result records the released subject, platform manifests, scanner and
1610
+ database identity, ConClear and guide identity, repository-configuration digest,
1611
+ findings, triage state, previous result digest and verdict. A change in triage,
1612
+ remediation or exception state produces a new linked result and never mutates an
1613
+ earlier result.
1614
+
1615
+ An authoritative rescan signs and attaches its result to the released digest,
1616
+ after which ConClear retrieves and verifies it. The successful post-attachment
1617
+ verification time starts the remediation clock. The signed rescan attestations
1618
+ on the released digest are the authoritative history. ConClear also keeps that
1619
+ history in protected durable state outside the project checkout and requires
1620
+ each later authoritative or diagnostic rescan to link the exact latest result,
1621
+ so omitting a prior result cannot reset a finding's clock. Durable state is a
1622
+ cache of the attested history rather than a separate source of truth: when it is
1623
+ absent or older than the subject, as on a first rescan from another authorized
1624
+ release environment or a replaced machine, ConClear reconstructs the chain from
1625
+ the verified rescan attestations and continues it instead of starting a new one.
1626
+ Durable state that conflicts with the attested chain is an operational failure.
1627
+ The result records each active fixable finding's effective deadline when a prior
1628
+ authoritative observation started its clock and rejects an overdue finding. An
1629
+ invocation without signing authority emits a local diagnostic only and does not
1630
+ advance the history. Scheduling, the supported-release inventory, triage,
1631
+ advisory publication and rebuilds remain external responsibilities.
1632
+
1633
+
1634
+ ## Implementation structure<a id="implementation-structure"></a>
1635
+
1636
+ <a id="promise-ip0035"></a>
1637
+ ConClear is implemented in Python 3.12 or newer with a `src/` package layout,
1638
+ `uv_build` and a committed `uv.lock`. Click provides the command hierarchy and
1639
+ JSON Schema validates configuration and public records. Internal models are
1640
+ typed dataclasses or narrowly typed value objects; there is no generic artifact
1641
+ framework.
1642
+
1643
+ The implementation separates these responsibilities:
1644
+
1645
+ - CLI parsing and human or JSON presentation.
1646
+ - Version and guide identity embedded at build time.
1647
+ - Configuration loading, path validation and effective-limit calculation.
1648
+ - Check catalog and guide-conformance generation.
1649
+ - Structured external-command execution and redaction.
1650
+ - Run workspace, ownership journal and atomic state transitions.
1651
+ - Git source selection and isolated worktree management.
1652
+ - Buildah, Podman, Skopeo, scanner and Cosign adapters, plus a provider-neutral
1653
+ registry control contract and compiled backend selection.
1654
+ - OCI layout, descriptor and registry-graph validation.
1655
+ - Qualification, assembly, provenance, publication, attestation, verification
1656
+ and promotion services.
1657
+ - Versioned JSON schemas and deterministic record serialization.
1658
+
1659
+ Repository configuration and maintainer-controlled release profiles are
1660
+ independent readers. Shared TOML narrowing helpers live in `parsing.py` and
1661
+ shared identifier syntax in `values.py`; `release_profile.py` does not import
1662
+ `config.py`. Schema rules, credential checks and URL normalization stay with the
1663
+ reader responsible for them.
1664
+
1665
+ Adapters return typed observations and never decide the release verdict
1666
+ themselves. Workflow services apply the guide rules to those observations.
1667
+ Presentation consumes the same result objects used for JSON output so human and
1668
+ machine modes cannot disagree.
1669
+
1670
+ Network operations are bounded and classified by idempotency. Reads may retry. A
1671
+ write retries only when the remote state can be checked first and the ownership
1672
+ journal makes the result unambiguous. Errors retain tool output after redaction
1673
+ and add actionable context without converting an unknown state into success.
1674
+
1675
+
1676
+ ## Testing<a id="testing"></a>
1677
+
1678
+ <a id="promise-ip0036"></a>
1679
+ Ruff, strict mypy, pytest and coverage run for the Python code. The hermetic
1680
+ unit suite carries an enforced minimum branch-coverage floor that the
1681
+ distribution gate applies; the floor is raised as coverage grows and is never
1682
+ met by excluding code. Tests use explicit markers for unit, local integration,
1683
+ emulation and network access so the default suite never publishes or requires
1684
+ credentials.
1685
+
1686
+ Unit tests cover configuration validation, limit narrowing, check identifiers,
1687
+ candidate naming, state transitions, record schemas, digest binding, platform
1688
+ coverage, command redaction, error classification, deterministic pin-proposal
1689
+ generation with an injected resolver and clock, and all-or-nothing proposal
1690
+ application with injected write, flush, replace and verification faults.
1691
+ Property tests cover reference parsing, path containment, archive extraction and
1692
+ OCI descriptor graphs.
1693
+
1694
+ Rootless integration tests exercise real supported versions of Buildah, Podman,
1695
+ Skopeo, Hadolint, Trivy and Cosign. Fixtures include a non-root service, a
1696
+ one-shot image, a `scratch` image, a documented PID-1 supervisor and a
1697
+ multi-platform index. Tests assert that runtime resource controls remain
1698
+ effective and that OCI format does not preserve Docker-only health metadata.
1699
+
1700
+ Network tests for the implemented backend use a disposable Quay repository, a
1701
+ dedicated test signing key and credentials with the narrowest practical scope.
1702
+ Release-path tests use the public transparency service because they must
1703
+ exercise the production policy; their repository and key identity clearly mark
1704
+ the resulting permanent entries as tests. They cover digest-preserving
1705
+ publication, candidate expiration, referrers, partial signing, log inclusion and
1706
+ verification, tag races, promotion and candidate deletion. Destructive tests
1707
+ never target a shared production repository or use a release signing key.
1708
+
1709
+ The dedicated drill project
1710
+ [oci-conclear-drill](https://github.com/foundata/oci-conclear-drill) serves as
1711
+ the continuing end-to-end check. Its synthetic images exercise every runtime
1712
+ profile, the documented supervisor contract, root-owned immutable runtime
1713
+ files, health checks, measured resource limits, hooks, exceptions and the
1714
+ negative cases a real project must never carry. Compatibility with real
1715
+ projects is asserted by running their normal release configuration, not by
1716
+ adding product-specific rules to ConClear.
1717
+
1718
+ The acceptance test for release behavior is a complete workstation invocation
1719
+ from an ordinary checkout, even when that checkout is dirty: ConClear must
1720
+ isolate the selected reviewed commit, qualify every required platform, publish
1721
+ and verify a unique candidate, sign with externally supplied managed key
1722
+ material, promote the verified digest and retain the required evidence without
1723
+ CI-only services.
1724
+
1725
+ The provider-independent distribution gate may retain its validated source
1726
+ distribution and wheel in a caller-selected new directory. It embeds the clean
1727
+ committed ConClear revision before building, builds the wheel from the source
1728
+ distribution, validates both artifacts, installs and smoke-tests that exact
1729
+ wheel, and makes the artifact directory visible only after every gate succeeds.
1730
+ It never rebuilds retained artifacts, derives identity from an application
1731
+ repository, follows a symbolic-link destination or overwrites a pre-existing
1732
+ output.
1733
+
1734
+
1735
+ ## Adopting an existing repository<a id="adopting-an-existing-repository"></a>
1736
+
1737
+ <a id="promise-ip0037"></a>
1738
+ `adopt` assesses an existing repository before it has a `conclear.toml`. It is
1739
+ read-only and hermetic: it executes only Git to observe the origin URL and
1740
+ revision, contacts no registry, resolves no pin and writes nothing except an
1741
+ explicitly requested draft, which it creates atomically and refuses to
1742
+ overwrite. It discovers only the conventional `Containerfile`,
1743
+ `Containerfile.<name>`, `Dockerfile` and `Dockerfile.<name>` files at the
1744
+ repository root, refuses a root that mixes both families or has none unless
1745
+ paths are given, and confines explicit paths below the root. Through the same
1746
+ structural parsers `check` uses, it observes the Containerfile path, the
1747
+ current Git revision, every external image input and its pin quality, the
1748
+ final `USER`, `VOLUME` destinations, `STOPSIGNAL`, static labels and a
1749
+ recognizable systemd entrypoint. The structural facts select the proposed
1750
+ runtime profile before the profile-dependent checks run: a systemd entrypoint is
1751
+ checked against the numeric `USER 0`, the fixed systemd stop signal and the
1752
+ systemd writable mounts that profile requires, every other image against a
1753
+ numeric non-root user, so findings, suggestions, decisions and draft values
1754
+ agree. A build context is not observable; a conventional root Containerfile
1755
+ receives the repository root as a suggestion and any other selection leaves it a
1756
+ decision. Every result separates observed facts from suggestions and from
1757
+ required decisions. Suggestions are limited to image ids derived from file
1758
+ names, release tag templates, the runtime profile
1759
+ the entrypoint implies, the numeric user the Containerfile states and writable
1760
+ mounts equal to observed `VOLUME` destinations. It never invents a release
1761
+ destination, platforms, a root justification, application writable paths, health
1762
+ behavior, test inputs, dependencies, hooks, exceptions or credentials; each of
1763
+ those is a listed decision, as is whether an image is released or exists only as
1764
+ a test dependency, and the draft states what a test-only image must drop and
1765
+ that an image no image depends on is invalid. The draft names every unresolved
1766
+ value with a reserved `DECIDE` placeholder. Resource limits are placeholders
1767
+ until measured; no plausible-looking defaults stand in for observations.
1768
+ Configuration loading collects every pending value before narrowing and reports
1769
+ schema errors together as well. There is no extra `[adopt]` table or decision
1770
+ ledger to remove. An incomplete draft cannot pass `check` or `qualify`.
1771
+ The JSON result is a closed schema of observations,
1772
+ suggestions, required decisions, findings and the draft text.
1773
+
1774
+ <a id="promise-ip0038"></a>
1775
+ `config show` loads the validated configuration and reports a summary of its
1776
+ effective values, their origins, fixed limits and owner-decision reasons. It
1777
+ shows runtime-profile mounts separately from the complete writable set. It
1778
+ runs no host tools and contacts no external services. An optional protected
1779
+ release profile adds only its public name, builder identity, registry host and
1780
+ public-key digest; credential paths and values are never displayed. Unresolved
1781
+ `DECIDE` values are reported together instead of producing a partial effective
1782
+ configuration, and schema validation reports missing or invalid fields together.
1783
+ The summary is not an export format or a qualification result.
1784
+
1785
+
1786
+ ## Maintaining this document<a id="maintaining-this-document"></a>
1787
+
1788
+ This document contains only behavior that is implemented and tested in the
1789
+ current source tree. Planned or speculative behavior belongs in a
1790
+ [GitHub issue](https://github.com/foundata/conclear/issues) until its
1791
+ implementation, tests and contract text land together. Contributors update the
1792
+ matching `IPnnnn` promise in `src/conclear/data/implementation.json` whenever
1793
+ current behavior, its implementation ownership or its verification changes, then
1794
+ regenerate the versioned implementation matrix.
1795
+
1796
+ A guide revision update requires reviewing every added, removed or reworded
1797
+ requirement, updating the embedded guide identity, requirement inventory, check
1798
+ catalog, coverage file, generated conformance document, affected schemas and
1799
+ tests. ConClear must not advertise the new guide revision until its automatable
1800
+ rules are implemented and passing.
1801
+
1802
+ Public command behavior and record schemas change deliberately. Each JSON schema
1803
+ has its own integer version; incompatible field or meaning changes increment its
1804
+ major schema version, while readers may accept explicitly documented older
1805
+ versions. Stable check identifiers, implementation-promise identifiers and
1806
+ published Markdown anchors are never silently repurposed. A generated, committed
1807
+ internal inventory enumerates the command hierarchy with its options and
1808
+ arguments, the schema identifiers and versions, the public record types, the
1809
+ exit statuses, the active and retired check identifiers and the current
1810
+ implementation-promise identifiers. Its JSON layout is not a supported external
1811
+ interface. Tests and the release gate verify that the inventory is current, so
1812
+ every change to an inventoried compatibility surface is an explicit, reviewable
1813
+ regeneration.