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.
- conclear-1.0.0/ARCHITECTURE.md +1813 -0
- conclear-1.0.0/CONTRIBUTING.md +92 -0
- conclear-1.0.0/DEVELOPMENT.md +1263 -0
- conclear-1.0.0/LICENSES/GPL-3.0-or-later.txt +232 -0
- conclear-1.0.0/PKG-INFO +735 -0
- conclear-1.0.0/README.md +696 -0
- conclear-1.0.0/REUSE.toml +20 -0
- conclear-1.0.0/docs/backup.md +132 -0
- conclear-1.0.0/docs/compatibility-inventory.json +2652 -0
- conclear-1.0.0/docs/conformance.md +712 -0
- conclear-1.0.0/docs/distributed-qualification.md +58 -0
- conclear-1.0.0/docs/implementation.md +51 -0
- conclear-1.0.0/pyproject.toml +171 -0
- conclear-1.0.0/pyproject.toml.orig +170 -0
- conclear-1.0.0/src/conclear/__init__.py +5 -0
- conclear-1.0.0/src/conclear/__main__.py +6 -0
- conclear-1.0.0/src/conclear/_development_identity.py +3 -0
- conclear-1.0.0/src/conclear/_embedded_identity.py +3 -0
- conclear-1.0.0/src/conclear/adapters/__init__.py +1 -0
- conclear-1.0.0/src/conclear/adapters/base.py +107 -0
- conclear-1.0.0/src/conclear/adapters/buildah.py +128 -0
- conclear-1.0.0/src/conclear/adapters/ci.py +198 -0
- conclear-1.0.0/src/conclear/adapters/cosign.py +475 -0
- conclear-1.0.0/src/conclear/adapters/git.py +127 -0
- conclear-1.0.0/src/conclear/adapters/hadolint.py +84 -0
- conclear-1.0.0/src/conclear/adapters/podman.py +661 -0
- conclear-1.0.0/src/conclear/adapters/quay.py +492 -0
- conclear-1.0.0/src/conclear/adapters/registry_backends.py +100 -0
- conclear-1.0.0/src/conclear/adapters/skopeo.py +193 -0
- conclear-1.0.0/src/conclear/adapters/trivy.py +402 -0
- conclear-1.0.0/src/conclear/archive.py +340 -0
- conclear-1.0.0/src/conclear/archive_source.py +113 -0
- conclear-1.0.0/src/conclear/artifacts.py +710 -0
- conclear-1.0.0/src/conclear/attestations.py +111 -0
- conclear-1.0.0/src/conclear/build_identity.py +18 -0
- conclear-1.0.0/src/conclear/catalog.py +415 -0
- conclear-1.0.0/src/conclear/checks.py +687 -0
- conclear-1.0.0/src/conclear/cli.py +229 -0
- conclear-1.0.0/src/conclear/commands/__init__.py +1 -0
- conclear-1.0.0/src/conclear/commands/adopt.py +64 -0
- conclear-1.0.0/src/conclear/commands/archive.py +143 -0
- conclear-1.0.0/src/conclear/commands/common.py +209 -0
- conclear-1.0.0/src/conclear/commands/configuration.py +39 -0
- conclear-1.0.0/src/conclear/commands/local.py +540 -0
- conclear-1.0.0/src/conclear/commands/maintenance.py +836 -0
- conclear-1.0.0/src/conclear/commands/remote.py +462 -0
- conclear-1.0.0/src/conclear/commands/transport.py +88 -0
- conclear-1.0.0/src/conclear/commands/version.py +32 -0
- conclear-1.0.0/src/conclear/compatibility_inventory.py +260 -0
- conclear-1.0.0/src/conclear/config.py +1611 -0
- conclear-1.0.0/src/conclear/config_decisions.py +53 -0
- conclear-1.0.0/src/conclear/conformance.py +37 -0
- conclear-1.0.0/src/conclear/containerfile.py +325 -0
- conclear-1.0.0/src/conclear/context.py +381 -0
- conclear-1.0.0/src/conclear/data/__init__.py +1 -0
- conclear-1.0.0/src/conclear/data/checks.json +76 -0
- conclear-1.0.0/src/conclear/data/guide-options.json +133 -0
- conclear-1.0.0/src/conclear/data/guide-requirements.json +3045 -0
- conclear-1.0.0/src/conclear/data/implementation.json +408 -0
- conclear-1.0.0/src/conclear/data/requirement-coverage.json +244 -0
- conclear-1.0.0/src/conclear/database.py +129 -0
- conclear-1.0.0/src/conclear/dependencies.py +267 -0
- conclear-1.0.0/src/conclear/emulation.py +138 -0
- conclear-1.0.0/src/conclear/errors.py +119 -0
- conclear-1.0.0/src/conclear/fileio.py +119 -0
- conclear-1.0.0/src/conclear/freshness.py +146 -0
- conclear-1.0.0/src/conclear/guide_options.py +200 -0
- conclear-1.0.0/src/conclear/guide_requirements.py +567 -0
- conclear-1.0.0/src/conclear/hook_scratch.py +132 -0
- conclear-1.0.0/src/conclear/hooks.py +139 -0
- conclear-1.0.0/src/conclear/identity.py +77 -0
- conclear-1.0.0/src/conclear/implementation.py +367 -0
- conclear-1.0.0/src/conclear/jsonutil.py +158 -0
- conclear-1.0.0/src/conclear/layout_assembly.py +269 -0
- conclear-1.0.0/src/conclear/oci.py +480 -0
- conclear-1.0.0/src/conclear/parsing.py +97 -0
- conclear-1.0.0/src/conclear/path_safety.py +135 -0
- conclear-1.0.0/src/conclear/pin_application.py +393 -0
- conclear-1.0.0/src/conclear/pin_occurrences.py +292 -0
- conclear-1.0.0/src/conclear/pin_updates.py +572 -0
- conclear-1.0.0/src/conclear/pins.py +327 -0
- conclear-1.0.0/src/conclear/presentation.py +101 -0
- conclear-1.0.0/src/conclear/process.py +532 -0
- conclear-1.0.0/src/conclear/provenance.py +129 -0
- conclear-1.0.0/src/conclear/records.py +223 -0
- conclear-1.0.0/src/conclear/registry_control.py +101 -0
- conclear-1.0.0/src/conclear/registry_policy.py +165 -0
- conclear-1.0.0/src/conclear/release_check.py +737 -0
- conclear-1.0.0/src/conclear/release_profile.py +362 -0
- conclear-1.0.0/src/conclear/rescan_history.py +337 -0
- conclear-1.0.0/src/conclear/runtime.py +204 -0
- conclear-1.0.0/src/conclear/runtime_directory.py +197 -0
- conclear-1.0.0/src/conclear/scan_identity.py +86 -0
- conclear-1.0.0/src/conclear/scan_policy.py +508 -0
- conclear-1.0.0/src/conclear/schema.py +125 -0
- conclear-1.0.0/src/conclear/schemas/__init__.py +1 -0
- conclear-1.0.0/src/conclear/schemas/archive.schema.json +40 -0
- conclear-1.0.0/src/conclear/schemas/config.schema.json +398 -0
- conclear-1.0.0/src/conclear/schemas/profile.schema.json +128 -0
- conclear-1.0.0/src/conclear/schemas/proposal.schema.json +113 -0
- conclear-1.0.0/src/conclear/schemas/provenance.schema.json +99 -0
- conclear-1.0.0/src/conclear/schemas/record.schema.json +869 -0
- conclear-1.0.0/src/conclear/schemas/result.schema.json +2482 -0
- conclear-1.0.0/src/conclear/schemas/triage.schema.json +43 -0
- conclear-1.0.0/src/conclear/secrets.py +127 -0
- conclear-1.0.0/src/conclear/services/__init__.py +1 -0
- conclear-1.0.0/src/conclear/services/adoption.py +126 -0
- conclear-1.0.0/src/conclear/services/adoption_draft.py +443 -0
- conclear-1.0.0/src/conclear/services/adoption_observation.py +459 -0
- conclear-1.0.0/src/conclear/services/archive_rescan.py +130 -0
- conclear-1.0.0/src/conclear/services/archives.py +519 -0
- conclear-1.0.0/src/conclear/services/assembly.py +788 -0
- conclear-1.0.0/src/conclear/services/attestation.py +567 -0
- conclear-1.0.0/src/conclear/services/attestation_reads.py +41 -0
- conclear-1.0.0/src/conclear/services/checking.py +89 -0
- conclear-1.0.0/src/conclear/services/ci_context.py +135 -0
- conclear-1.0.0/src/conclear/services/cleanup.py +462 -0
- conclear-1.0.0/src/conclear/services/configuration_view.py +309 -0
- conclear-1.0.0/src/conclear/services/doctor.py +159 -0
- conclear-1.0.0/src/conclear/services/local_phases.py +105 -0
- conclear-1.0.0/src/conclear/services/preflight.py +178 -0
- conclear-1.0.0/src/conclear/services/privilege_tests.py +392 -0
- conclear-1.0.0/src/conclear/services/promotion.py +335 -0
- conclear-1.0.0/src/conclear/services/publication.py +428 -0
- conclear-1.0.0/src/conclear/services/qualification.py +724 -0
- conclear-1.0.0/src/conclear/services/qualification_inputs.py +128 -0
- conclear-1.0.0/src/conclear/services/registry_diagnostics.py +210 -0
- conclear-1.0.0/src/conclear/services/release.py +672 -0
- conclear-1.0.0/src/conclear/services/rescan.py +675 -0
- conclear-1.0.0/src/conclear/services/rescan_evidence.py +145 -0
- conclear-1.0.0/src/conclear/services/run_context.py +340 -0
- conclear-1.0.0/src/conclear/services/runtime_controls.py +133 -0
- conclear-1.0.0/src/conclear/services/runtime_lifecycle.py +804 -0
- conclear-1.0.0/src/conclear/services/runtime_tests.py +795 -0
- conclear-1.0.0/src/conclear/services/verification.py +363 -0
- conclear-1.0.0/src/conclear/setid_inventory.py +303 -0
- conclear-1.0.0/src/conclear/source_integrity.py +161 -0
- conclear-1.0.0/src/conclear/spdx.py +174 -0
- conclear-1.0.0/src/conclear/test_inputs.py +309 -0
- conclear-1.0.0/src/conclear/test_output_archive.py +92 -0
- conclear-1.0.0/src/conclear/tool_matrix.py +93 -0
- conclear-1.0.0/src/conclear/tools.py +324 -0
- conclear-1.0.0/src/conclear/transport.py +981 -0
- conclear-1.0.0/src/conclear/triage.py +110 -0
- conclear-1.0.0/src/conclear/values.py +264 -0
- conclear-1.0.0/src/conclear/version_sources.py +148 -0
- conclear-1.0.0/src/conclear/workspace.py +628 -0
- conclear-1.0.0/tests/__init__.py +1 -0
- conclear-1.0.0/tests/conftest.py +70 -0
- conclear-1.0.0/tests/local_integration/__init__.py +0 -0
- conclear-1.0.0/tests/local_integration/conftest.py +50 -0
- conclear-1.0.0/tests/local_integration/fixtures.py +255 -0
- conclear-1.0.0/tests/local_integration/sudo_fixture/Containerfile +7 -0
- conclear-1.0.0/tests/local_integration/sudo_fixture/sudoers +1 -0
- conclear-1.0.0/tests/local_integration/test_adapter_failures.py +595 -0
- conclear-1.0.0/tests/local_integration/test_archive_signatures.py +107 -0
- conclear-1.0.0/tests/local_integration/test_backup_recipe.py +283 -0
- conclear-1.0.0/tests/local_integration/test_containerfile.py +119 -0
- conclear-1.0.0/tests/local_integration/test_distribution_recipe.py +58 -0
- conclear-1.0.0/tests/local_integration/test_emulation.py +134 -0
- conclear-1.0.0/tests/local_integration/test_source_integrity.py +189 -0
- conclear-1.0.0/tests/local_integration/test_sudo.py +88 -0
- conclear-1.0.0/tests/local_integration/test_tools.py +816 -0
- conclear-1.0.0/tests/local_integration/test_transport_cli.py +487 -0
- conclear-1.0.0/tests/local_integration/test_trivy_database.py +317 -0
- conclear-1.0.0/tests/local_integration/test_trivy_policy.py +171 -0
- conclear-1.0.0/tests/network/test_cosign_attestation.py +100 -0
- conclear-1.0.0/tests/network/test_quay_candidate.py +102 -0
- conclear-1.0.0/tests/network/test_release_lifecycle.py +188 -0
- conclear-1.0.0/tests/network_support.py +103 -0
- conclear-1.0.0/tests/registry_policy_fixtures.py +22 -0
- conclear-1.0.0/tests/release_fakes.py +731 -0
- conclear-1.0.0/tests/release_scenarios.py +181 -0
- conclear-1.0.0/tests/unit/test_adapters.py +2130 -0
- conclear-1.0.0/tests/unit/test_adoption.py +704 -0
- conclear-1.0.0/tests/unit/test_archives.py +890 -0
- conclear-1.0.0/tests/unit/test_assembly.py +373 -0
- conclear-1.0.0/tests/unit/test_assembly_verification.py +830 -0
- conclear-1.0.0/tests/unit/test_attestation_binding.py +155 -0
- conclear-1.0.0/tests/unit/test_attestations.py +67 -0
- conclear-1.0.0/tests/unit/test_candidate_retention.py +231 -0
- conclear-1.0.0/tests/unit/test_catalog.py +105 -0
- conclear-1.0.0/tests/unit/test_catalog_loading.py +181 -0
- conclear-1.0.0/tests/unit/test_checks.py +798 -0
- conclear-1.0.0/tests/unit/test_ci_context.py +346 -0
- conclear-1.0.0/tests/unit/test_cleanup.py +612 -0
- conclear-1.0.0/tests/unit/test_cleanup_ownership.py +297 -0
- conclear-1.0.0/tests/unit/test_cli.py +442 -0
- conclear-1.0.0/tests/unit/test_commands.py +2138 -0
- conclear-1.0.0/tests/unit/test_compatibility_inventory.py +167 -0
- conclear-1.0.0/tests/unit/test_config.py +1485 -0
- conclear-1.0.0/tests/unit/test_config_rejections.py +389 -0
- conclear-1.0.0/tests/unit/test_configuration_view.py +337 -0
- conclear-1.0.0/tests/unit/test_containerfile.py +267 -0
- conclear-1.0.0/tests/unit/test_context.py +163 -0
- conclear-1.0.0/tests/unit/test_dependencies.py +192 -0
- conclear-1.0.0/tests/unit/test_doctor.py +307 -0
- conclear-1.0.0/tests/unit/test_emulation.py +133 -0
- conclear-1.0.0/tests/unit/test_evidence_retention.py +145 -0
- conclear-1.0.0/tests/unit/test_fileio.py +65 -0
- conclear-1.0.0/tests/unit/test_freshness.py +384 -0
- conclear-1.0.0/tests/unit/test_guide_options.py +162 -0
- conclear-1.0.0/tests/unit/test_guide_requirements.py +498 -0
- conclear-1.0.0/tests/unit/test_hook_scratch.py +171 -0
- conclear-1.0.0/tests/unit/test_hooks.py +50 -0
- conclear-1.0.0/tests/unit/test_identity.py +52 -0
- conclear-1.0.0/tests/unit/test_implementation.py +231 -0
- conclear-1.0.0/tests/unit/test_jsonutil.py +22 -0
- conclear-1.0.0/tests/unit/test_layout_assembly_errors.py +134 -0
- conclear-1.0.0/tests/unit/test_lifecycle_cli.py +54 -0
- conclear-1.0.0/tests/unit/test_local_phases.py +108 -0
- conclear-1.0.0/tests/unit/test_network_support.py +171 -0
- conclear-1.0.0/tests/unit/test_oci.py +119 -0
- conclear-1.0.0/tests/unit/test_parsing.py +59 -0
- conclear-1.0.0/tests/unit/test_path_confinement.py +155 -0
- conclear-1.0.0/tests/unit/test_path_safety.py +111 -0
- conclear-1.0.0/tests/unit/test_pin_updates.py +855 -0
- conclear-1.0.0/tests/unit/test_pins.py +124 -0
- conclear-1.0.0/tests/unit/test_pins_cli.py +403 -0
- conclear-1.0.0/tests/unit/test_pins_state.py +201 -0
- conclear-1.0.0/tests/unit/test_preflight.py +289 -0
- conclear-1.0.0/tests/unit/test_privilege_contracts.py +629 -0
- conclear-1.0.0/tests/unit/test_process.py +402 -0
- conclear-1.0.0/tests/unit/test_provenance.py +61 -0
- conclear-1.0.0/tests/unit/test_publication.py +1047 -0
- conclear-1.0.0/tests/unit/test_publication_resume.py +259 -0
- conclear-1.0.0/tests/unit/test_publication_retry.py +174 -0
- conclear-1.0.0/tests/unit/test_qualification.py +2544 -0
- conclear-1.0.0/tests/unit/test_readme.py +98 -0
- conclear-1.0.0/tests/unit/test_records.py +149 -0
- conclear-1.0.0/tests/unit/test_registry_backends.py +67 -0
- conclear-1.0.0/tests/unit/test_registry_diagnostics.py +239 -0
- conclear-1.0.0/tests/unit/test_registry_policy.py +381 -0
- conclear-1.0.0/tests/unit/test_release.py +433 -0
- conclear-1.0.0/tests/unit/test_release_check.py +238 -0
- conclear-1.0.0/tests/unit/test_release_check_gate.py +412 -0
- conclear-1.0.0/tests/unit/test_release_profile.py +454 -0
- conclear-1.0.0/tests/unit/test_release_scenarios.py +195 -0
- conclear-1.0.0/tests/unit/test_release_workflow.py +579 -0
- conclear-1.0.0/tests/unit/test_rescan.py +842 -0
- conclear-1.0.0/tests/unit/test_rescan_evidence.py +313 -0
- conclear-1.0.0/tests/unit/test_rescan_history.py +229 -0
- conclear-1.0.0/tests/unit/test_rescan_history_state.py +147 -0
- conclear-1.0.0/tests/unit/test_run_context.py +617 -0
- conclear-1.0.0/tests/unit/test_runtime.py +214 -0
- conclear-1.0.0/tests/unit/test_runtime_controls.py +121 -0
- conclear-1.0.0/tests/unit/test_runtime_directory.py +224 -0
- conclear-1.0.0/tests/unit/test_runtime_inputs.py +237 -0
- conclear-1.0.0/tests/unit/test_scan_identity.py +113 -0
- conclear-1.0.0/tests/unit/test_scan_policy.py +451 -0
- conclear-1.0.0/tests/unit/test_schema.py +204 -0
- conclear-1.0.0/tests/unit/test_schema_fixtures.py +355 -0
- conclear-1.0.0/tests/unit/test_secrets.py +135 -0
- conclear-1.0.0/tests/unit/test_setid_inventory.py +235 -0
- conclear-1.0.0/tests/unit/test_source_integrity.py +175 -0
- conclear-1.0.0/tests/unit/test_spdx.py +114 -0
- conclear-1.0.0/tests/unit/test_spdx_validation.py +204 -0
- conclear-1.0.0/tests/unit/test_tag_protection.py +217 -0
- conclear-1.0.0/tests/unit/test_test_inputs.py +184 -0
- conclear-1.0.0/tests/unit/test_test_output_archive.py +59 -0
- conclear-1.0.0/tests/unit/test_tool_matrix.py +65 -0
- conclear-1.0.0/tests/unit/test_tools.py +225 -0
- conclear-1.0.0/tests/unit/test_transport.py +1262 -0
- conclear-1.0.0/tests/unit/test_triage.py +109 -0
- conclear-1.0.0/tests/unit/test_values.py +140 -0
- conclear-1.0.0/tests/unit/test_version_sources.py +276 -0
- conclear-1.0.0/tests/unit/test_workspace.py +298 -0
- conclear-1.0.0/tests/unit/test_workspace_journal.py +292 -0
- 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.
|