Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c9fd6e6
docs: define independent verifier milestone
MarcoPorcellato Aug 29, 2026
9831208
test: freeze verifier compatibility envelope
MarcoPorcellato Aug 29, 2026
bfa4e53
docs: record verifier compatibility freeze
MarcoPorcellato Aug 29, 2026
f8c1e36
chore: keep SDD report local
MarcoPorcellato Aug 29, 2026
b4e98bc
test: tighten verifier compatibility contracts
MarcoPorcellato Aug 29, 2026
e6cb6a0
test: reject duplicate envelope keys
MarcoPorcellato Aug 29, 2026
e5286af
test: complete duplicate key parser
MarcoPorcellato Aug 29, 2026
795db2f
test: add exhaustive public error witnesses
MarcoPorcellato Aug 29, 2026
614cb57
test: complete public error compatibility contract
MarcoPorcellato Aug 29, 2026
d071f14
test: exhaustively assert public error contracts
MarcoPorcellato Aug 29, 2026
03ac7a4
test: enforce exact public error displays
MarcoPorcellato Aug 29, 2026
65263b5
refactor: establish verifier workspace boundary
MarcoPorcellato Aug 29, 2026
772b993
test: tighten workspace boundary contract
MarcoPorcellato Aug 29, 2026
ed09b89
refactor: extract canonical receipt core
MarcoPorcellato Aug 29, 2026
a88d796
refactor: untangle core protocol dependencies
MarcoPorcellato Aug 29, 2026
5ff262d
docs: split matrix contract and runner errors
MarcoPorcellato Aug 29, 2026
7433c04
refactor: establish core verification model
MarcoPorcellato Aug 29, 2026
efa548c
refactor: add pure matrix plan core
MarcoPorcellato Aug 29, 2026
2ee39f9
test: qualify pure matrix plan core
MarcoPorcellato Aug 29, 2026
a3fb9b9
refactor: add pure matrix receipt policy core
MarcoPorcellato Aug 29, 2026
af9617c
style: format pure matrix contract core
MarcoPorcellato Aug 29, 2026
0fa7aed
refactor: add matrix profile binding core
MarcoPorcellato Aug 29, 2026
92cc1ed
refactor: move combined receipt schema to core
MarcoPorcellato Aug 29, 2026
da4b822
refactor: expose core matrix plan facade
MarcoPorcellato Aug 29, 2026
5ab0dc3
docs: gate new development on stable release
MarcoPorcellato Aug 29, 2026
21c5316
refactor: isolate matrix verification contracts
MarcoPorcellato Aug 29, 2026
7bcaeb1
test: package matrix identity contract
MarcoPorcellato Aug 29, 2026
a787864
refactor: align core matrix policy verification
MarcoPorcellato Aug 29, 2026
bcc3a13
refactor: unify matrix receipt policy contracts
MarcoPorcellato Aug 29, 2026
004aba7
refactor: extract policy verification core
MarcoPorcellato Aug 29, 2026
11f71ba
test: freeze policy document error sources
MarcoPorcellato Aug 29, 2026
53cf351
feat: add independent receipt verifier
MarcoPorcellato Aug 29, 2026
ad40ddf
fix: preserve verifier exit and parity provenance
MarcoPorcellato Aug 29, 2026
dc09664
test: harden verifier parity cleanup
MarcoPorcellato Aug 29, 2026
2eb792f
test: enforce verifier dependency isolation
MarcoPorcellato Aug 29, 2026
d6216be
test: harden verifier graph evidence
MarcoPorcellato Aug 29, 2026
6beaa9f
docs: correct verifier tree evidence command
MarcoPorcellato Aug 29, 2026
24417e5
docs: document independent verifier boundary
MarcoPorcellato Aug 29, 2026
fc0443f
docs: enforce valid M2 cargo tree command
MarcoPorcellato Aug 29, 2026
bccd659
fix ccp-core clippy contract findings
MarcoPorcellato Aug 29, 2026
cb49eec
fix verifier test module ordering
MarcoPorcellato Aug 29, 2026
6ae3f6c
chore: close workspace clippy findings
MarcoPorcellato Aug 29, 2026
0c83801
merge: integrate cache payload symlink support
MarcoPorcellato Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .superpowers/sdd/2026-08-29-independent-verifier/task-4-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Task 4B1 report: pure Matrix plan core

## Slice A implementation attempt (2026-08-29)

- Scope: replaced the duplicate root Matrix config/plan block in `src/matrix.rs` with core re-exports and the compatibility `build_matrix_plan` wrapper; retained execution constants and removed now-unused imports.
- Initial focused tests compiled the library but failed at root integration: core plan accessors now return `ccp_core::matrix::MatrixContractError`, while existing tests and CLI call sites still match/pass `MatrixError`; `prepare_source_snapshot_overlay` is also no longer an inherent method on the re-exported core envelope.
- Exact command: `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-task4-slice-a cargo test --locked --test independent_verifier_contract --test matrix_contract --test receipt_contract`
- Result: FAIL, 18 compile errors (8 test nominal error-type mismatches; 10 binary call-site/method integration errors). No heavy commands run.
- `rtk cargo fmt --all` completed after escalation; `rtk git diff --check` passed. No commit created because focused tests are not green.
- Blocker: root execution/CLI adaptation and/or test contract migration is required before this slice can be green; scope is beyond the mechanical re-export-only edit as currently staged.

### Fix round 1 (2026-08-29)

- Added core `LegacyPlanNotRepresentable` error and root adapter mapping; restored root free-function source-overlay preparation and updated CLI/internal callers; updated pure-method assertions to core error variants.
- Focused command reached execution: 11 passed, 5 failed. Failures are legacy profile digest/validation parity (4 tests) plus pinned schema naming (fixed by restoring `EnvironmentConfig` schemars rename; rerun still required).
- Exact command: `rtk cargo fmt --all && rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-task4-slice-a cargo test --locked --test independent_verifier_contract --test matrix_contract --test receipt_contract && rtk git diff --check`
- No commit created; required GREEN gates remain outstanding.

### Packaging correction (2026-08-29)

- The previously omitted eight-type Matrix identity test was verified independently: 1 passed; `git diff --check` passed. It is packaged in the follow-up commit.
Implemented the first bounded extraction slice in `ccp-core::matrix`.

## Scope

- Added Matrix V2 configuration, runtime/check declarations, plan envelope,
normalization, canonical digest binding, and validation.
- Added `MatrixContractError` with source propagation for I/O, TOML, config,
and receipt failures.
- Kept execution, runtime, cache, process, Docker, source-snapshot, receipt
publication, and root `src/matrix.rs` untouched.
- Exposed the module through `ccp_core::matrix`.

## Verification

- `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-target cargo check -p ccp-core --locked`: PASS.
- `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-target cargo test -p ccp-core --locked`: blocked by a pre-existing unrelated test compile error in `crates/ccp-core/src/receipt.rs` (`Value` is not imported at line 986).
- `rtk git diff --check`: PASS.
- `cargo fmt --all`: blocked by the environment returning `Operation not permitted` when writing this worktree; source was kept formatted manually.

## Commit

Local commit: `refactor: add pure matrix plan core`.

# Task 4B2 report: pure Matrix receipt/policy core

## Task 4B1 parity completion (2026-08-29)

RED fixture (exact pre-B1 HEAD `7bcaeb12d39cf36c5880f7063d9b90df7d88f4f7`): corrected parity used a valid resealed runtime-image mismatch and failed because old core omitted `policy.runtime_image`; invalid-policy and adapter tests also failed as intended. Active GREEN: all three new tests passed.

Final gate counts: matrix 19/19, verification 21 passed plus 1 explicit ignored, receipt 11/11, independent 7/7, core 46/46, fmt check PASS, diff check PASS.

- Core Matrix policy validation now enforces project/digest/image/freshness/platform constraints and complete runtime coverage, matching the root policy contract.
- Core receipt verification now preserves root finding order and semantics for repository, commit, dirty state, configuration, runtime set/configuration/image/platform, required checks, and freshness.
- The root adapter preserves the nominal core `VerificationError` directly (`Core::Verification(error) => Self::Verification(error)`).
- `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-task4-b1 cargo test -p ccp-core --locked`: PASS (46 passed, 0 failed).
- `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-task4-b1 cargo test --locked --test matrix_contract --test receipt_contract --test independent_verifier_contract`: PASS (34 passed, 0 failed; existing dead-code warnings only).
- `rtk git diff --check`: PASS. `rtk cargo fmt --all` remains blocked by worktree `Operation not permitted` while writing `crates/ccp-core/src/matrix.rs`; no formatter changes were applied.

Implemented the bounded receipt, policy, verification, historical digest, and
schema slice in `ccp-core`, retaining root compatibility definitions for B3.

## Task 4 B2 final integration RED (2026-08-29)

- Added `matrix_receipt_policy_and_required_check_paths_are_nominally_identical`
to `tests/independent_verifier_contract.rs`, covering the six receipt/policy
nominal identities and source-boundary assertions.
- Exact command: `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-task4-b2 cargo test --locked --test independent_verifier_contract matrix_receipt_policy_and_required_check_paths_are_nominally_identical`
- Result: RED as required. Compilation reported six nominal type mismatches;
root duplicate definitions were identified at `src/matrix.rs` lines 288,
295, 309, 401, 412, and 419. No production migration was completed in this
slice yet.
- GitNexus attempt was inconclusive: checkout is not indexed and the CLI
reported multiple unrelated indexed repositories. Bounded `rg`/line-number
inspection was used instead.

## Scope

- Added `MatrixReceiptEnvelopeV2`, `MatrixReceiptV2`, and
`MatrixRuntimeReceiptV2` with canonical seal/verify and semantic checks.
- Added `MatrixVerificationPolicyV2` and strict parsing/validation plus the
pure `verify_matrix_receipt_document` path.
- Added `MatrixContractError` variants for JSON, receipt-ID mismatch,
verification, and invalid evaluation time.
- Added `ccp_core::matrix_legacy` historical digest projection and
`ccp_core::schema` schema entry points.
- Did not remove root Matrix definitions or alter execution integration.

## Verification

- `rtk env CARGO_TARGET_DIR=/private/tmp/ccp-independent-target cargo test -p ccp-core --locked`: PASS (46 passed, 0 failed).
- `rtk git diff --check`: PASS.
- `rtk cargo fmt --all`: blocked by `Operation not permitted` writing `src/lib.rs`; source was manually kept formatter-compatible.

## Commit

Local commit: `refactor: add pure matrix receipt policy core`.
## B2 cleanup and focused verification

- Removed the obsolete root legacy module and updated the source-boundary assertion to the core legacy implementation.
- Removed root-only imports and constants left unused after receipt/policy contract migration; test-only imports remain scoped to the unit-test module.
- Preserved the root schema compatibility bytes and mapped core receipt errors through the existing root adapter.
- Focused verification: identity 8/8, matrix contract 19/19, receipt contract 11/11, verification contract 21 passed/1 ignored, root matrix unit tests 5/5, ccp-core tests 46/46.
- cargo fmt all check and git diff check: PASS.

## B2 final integration and independent controller verification

- Replaced the six duplicate root receipt/policy definitions with nominal
`ccp_core::matrix` re-exports. Root execution and `MatrixError` remain
root-owned.
- Retained the public root schema and verification free functions as thin
compatibility wrappers; core `MatrixContractError` is converted through the
exhaustive root adapter. The policy-document dispatcher performs the same
explicit conversion while preserving its public error variant.
- Removed the duplicated root policy evaluator and the dead root legacy module;
the independent-verifier test proves all six cross-crate identities and the
absence of root look-alike definitions.
- Fresh controller command:
`rtk cargo test --locked --test independent_verifier_contract --test matrix_contract --test receipt_contract --test verification_contract && rtk cargo test --locked --lib matrix && rtk cargo test --locked -p ccp-core && rtk cargo fmt --all -- --check && rtk git diff --check`.
- Result: 59 passed plus 1 explicitly ignored across the four integration
suites; 5/5 root Matrix unit tests; 46/46 core tests; formatting and diff
checks PASS.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ Semantic Versioning after its first public release.

### Added

- Unix cache generations now preserve ordinary opaque payload links during inventory and reuse; strict control paths remain link-free, failed preparation owns cleanup before reuse, and native candidate qualification remains pending.
- Added the M2 physically independent verifier boundary: `ccp-core` owns the
protocol and pure verification contracts, while `ccp-verifier` is available
as a bounded local source build with `verify` and schema commands. This does
not claim a published binary, platform qualification, static distribution,
or producer identity; `verify-benchmark` remains in the root CLI and static
multi-platform distribution is deferred to M3.

- Unix cache generations now preserve ordinary opaque payload links during
inventory and reuse; strict control paths remain link-free, failed
preparation owns cleanup before reuse, and native candidate qualification
remains pending.

- Added the adoption-help issue-form source and a 1280 × 640 social-preview PNG
upload candidate. GitHub form rendering/live chooser remains unverified until
Expand Down
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ readme = "README.md"
keywords = ["ci", "preflight", "containers", "evidence", "github-actions"]
categories = ["command-line-utilities", "development-tools::testing"]

[workspace]
members = [".", "crates/ccp-core", "crates/ccp-verifier"]
default-members = ["."]
resolver = "3"

[dependencies]
ccp-core = { path = "crates/ccp-core" }
clap = { version = "4.6.6", default-features = false, features = ["derive", "error-context", "help", "std", "usage"] }
ctrlc = { version = "=3.5.2", features = ["termination"] }
fs2 = "=0.4.3"
Expand All @@ -23,5 +29,8 @@ saphyr-parser = { version = "0.0.6", default-features = false }
sha2 = { version = "0.11.0", default-features = false }
toml = { version = "1.1.4", default-features = false, features = ["parse", "serde", "std"] }

[dev-dependencies]
ccp-core = { path = "crates/ccp-core" }

[target.'cfg(unix)'.dependencies]
nix = { version = "=0.31.1", default-features = false, features = ["process", "signal"] }
83 changes: 81 additions & 2 deletions SBOM.spdx.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"comment": "Generated from locked Cargo metadata; Cargo.lock SHA-256: 0955cbe3fe37d43df351ca9e90bc6fb6485ed1d3acdb015c5ad37c7294d21d66",
"comment": "Generated from locked Cargo metadata; Cargo.lock SHA-256: c766c0e70a6964f28097802db428100ca5f1b2d2f5e0da344df4148173ade804",
"created": "2026-08-10T00:00:00Z",
"creators": [
"Person: Marco Porcellato",
"Tool: commit-ci-preflight-release-metadata/0.1.0"
]
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://github.com/MarcoPorcellato/commit-ci-preflight/sbom/0.1.0/0955cbe3fe37d43df351ca9e90bc6fb6485ed1d3acdb015c5ad37c7294d21d66",
"documentNamespace": "https://github.com/MarcoPorcellato/commit-ci-preflight/sbom/0.1.0/c766c0e70a6964f28097802db428100ca5f1b2d2f5e0da344df4148173ade804",
"name": "commit-ci-preflight-0.1.0",
"packages": [
{
Expand Down Expand Up @@ -150,6 +150,40 @@
"name": "block2",
"versionInfo": "0.6.2"
},
{
"SPDXID": "SPDXRef-Package-ccp-core-0.1.0",
"copyrightText": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/ccp-core@0.1.0",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0",
"name": "ccp-core",
"versionInfo": "0.1.0"
},
{
"SPDXID": "SPDXRef-Package-ccp-verifier-0.1.0",
"copyrightText": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/ccp-verifier@0.1.0",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0",
"name": "ccp-verifier",
"versionInfo": "0.1.0"
},
{
"SPDXID": "SPDXRef-Package-cfg-if-1.0.4",
"checksums": [
Expand Down Expand Up @@ -1794,6 +1828,46 @@
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-block2-0.6.2"
},
{
"relatedSpdxElement": "SPDXRef-Package-schemars-1.2.2",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-core-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-serde-1.0.229",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-core-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-serde-json-1.0.151",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-core-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-sha2-0.11.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-core-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-toml-1.1.4-spec-1.1.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-core-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-ccp-core-0.1.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-verifier-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-clap-4.6.6",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-verifier-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-serde-json-1.0.151",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-ccp-verifier-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-clap-builder-4.6.6",
"relationshipType": "DEPENDS_ON",
Expand Down Expand Up @@ -1834,6 +1908,11 @@
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-clap-derive-4.6.4"
},
{
"relatedSpdxElement": "SPDXRef-Package-ccp-core-0.1.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-commit-ci-preflight-0.1.0"
},
{
"relatedSpdxElement": "SPDXRef-Package-clap-4.6.6",
"relationshipType": "DEPENDS_ON",
Expand Down
4 changes: 4 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Generated deterministically from the locked Rust dependency graph for Commit CI
| bitflags | 2.13.1 | The Rust Project Developers | MIT OR Apache-2.0 | https://github.com/bitflags/bitflags | b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da |
| block-buffer | 0.12.1 | RustCrypto Developers | MIT OR Apache-2.0 | https://github.com/RustCrypto/utils | d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa |
| block2 | 0.6.2 | Mads Marquart <mads@marquart.dk> | MIT | https://github.com/madsmtm/objc2 | cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5 |
| ccp-core | 0.1.0 | Not declared | Apache-2.0 | Not declared | Not present |
| ccp-verifier | 0.1.0 | Not declared | Apache-2.0 | Not declared | Not present |
| cfg-if | 1.0.4 | Alex Crichton <alex@alexcrichton.com> | MIT OR Apache-2.0 | https://github.com/rust-lang/cfg-if | 9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801 |
| cfg_aliases | 0.2.2 | Zicklag <zicklag@katharostech.com> | MIT | https://github.com/katharostech/cfg_aliases | f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527 |
| clap | 4.6.6 | Not declared | MIT OR Apache-2.0 | https://github.com/clap-rs/clap | 473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca |
Expand Down Expand Up @@ -86,6 +88,8 @@ Generated deterministically from the locked Rust dependency graph for Commit CI
The following packaged crates declared a license expression but did not contain a UTF-8 file whose name begins with LICENSE, COPYING, NOTICE, or UNLICENSE. Consult the source link and declared expression above before redistribution.

- block2 0.6.2
- ccp-core 0.1.0
- ccp-verifier 0.1.0
- dispatch2 0.3.1
- objc2 0.6.4
- objc2-encode 4.1.0
Expand Down
14 changes: 14 additions & 0 deletions crates/ccp-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "ccp-core"
version = "0.1.0"
edition = "2024"
rust-version = "1.87"
description = "Core receipt and verification contracts for Commit CI Preflight."
license = "Apache-2.0"

[dependencies]
schemars = "1.2.2"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
sha2 = { version = "0.11.0", default-features = false }
toml = { version = "1.1.4", default-features = false, features = ["parse", "serde", "std"] }
43 changes: 43 additions & 0 deletions crates/ccp-core/src/canonical.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::collections::BTreeMap;

use serde::Serialize;
use serde_json::Value;
use sha2::{Digest, Sha256};

use crate::errors::ReceiptError;

pub fn canonical_json<T: Serialize>(value: &T) -> Result<Vec<u8>, ReceiptError> {
let value = serde_json::to_value(value).map_err(ReceiptError::Serialization)?;
let normalized = normalize_json(value);
serde_json::to_vec(&normalized).map_err(ReceiptError::Serialization)
}

pub fn canonical_digest<T: Serialize>(value: &T) -> Result<String, ReceiptError> {
let bytes = canonical_json(value)?;
let digest = Sha256::digest(bytes);
Ok(format!("{}{}", "sha256:", encode_hex(&digest)))
}

fn normalize_json(value: Value) -> Value {
match value {
Value::Array(items) => Value::Array(items.into_iter().map(normalize_json).collect()),
Value::Object(items) => {
let sorted: BTreeMap<_, _> = items
.into_iter()
.map(|(key, value)| (key, normalize_json(value)))
.collect();
Value::Object(sorted.into_iter().collect())
}
scalar => scalar,
}
}

fn encode_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut output = String::with_capacity(bytes.len() * 2);
for byte in bytes {
output.push(HEX[(byte >> 4) as usize] as char);
output.push(HEX[(byte & 0x0f) as usize] as char);
}
output
}
Loading