Rename indirect-signature content validation to ContentDigest verbiage (native)#209
Merged
JeromySt merged 47 commits intoJun 16, 2026
Conversation
…_final Mirrors V2/tools/train (the .NET train) but parameterized for the Rust workspace: - Integration branch: users/jstatia/native_ports_final - Worktree prefix: CoseSignTool-np-<phase> - Coverage script: native/rust/collect-coverage.ps1 (per-crate via -Package) - Per-scope gate: 90% absolute (matches existing repo Rust convention) - Workspace gate: -NoRegress (compare phase coverage vs integration baseline) Decisions R1-R7 captured in eval-trust-policy-translation-contract-rust.md (session workspace). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scaffold the new validation/trust_policy_spec crate (Phase 1 of the native Rust trust-policy port) and register it as a workspace member. Adds minimal Cargo.toml metadata + the dependency-allowlist entry that lets collect-coverage.ps1's allowlist gate accept serde + serde_json in this specific crate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the canonical IR (D3): a serde-tagged 'enum TrustPolicySpec' carrying the ten variants from the design doc (AllowAll, DenyAll, And, Or, Not, Implies, Message, PrimarySigningKey, AnyCounterSignature, RequireFact), plus the OnEmptyBehavior enum mirrored from primitives so the IR can carry serde derives without reaching into the trust-evaluation crate. Also adds: - src/source_location.rs: SourceLocation type used by frontends to attach line/column anchors to diagnostics. - src/diagnostic_codes.rs: stable TPX001-TPX600 constants per D6. Every struct variant carries deny_unknown_fields so frontends reject typos at parse time as TPX100 rather than as silent behavioral drift. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements D1's hybrid predicate language: - PropertyAssertionPredicateSpec: sugar variant. AND of property-equality assertions stored in a BTreeMap so canonical JSON serialization is byte-stable across builds (D9 cache-key invariant). - PathOperatorPredicateSpec: universal fallback. Carries a JSON-pointer- style path, a closed-enum PredicateOperator, and an optional value operand. - FactPredicateSpec: untagged enum that selects the variant from JSON shape; serde tries Property first (unambiguous 'assertions' key) then PathOperator (universal fallback). The PredicateOperator enum closes 11 operators (Exists, Equals, NotEquals, LessThan/Or, GreaterThan/Or, StartsWith, EndsWith, Contains, In) with #[serde(rename_all = snake_case)] so wire identifiers are stable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements D5's post-parse parameter substitution:
- ParameterRef: typed view over the JSON literal {"`$param": "name",
"default": <value>}. Recognized anywhere a serde_json::Value slot
accepts a literal (predicate operands, property assertion RHS).
- bind(spec, parameters): walks the spec tree replacing every
ParameterRef with its bound value or default. Missing-without-default
surfaces as BindError::MissingParameter carrying a path breadcrumb
(e.g. require_fact.predicate.assertions[is_trusted]) for diagnostics.
- BindError::Malformed: rejects ill-shaped parameter literals (non-
string $param value, unexpected co-keys) at recognize time.
The walker is exhaustive over every variant of TrustPolicySpec and
recurses through serde_json::Value arrays + nested objects, so a
`$param can appear at arbitrary depth inside a predicate value slot.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Defines the fact-id resolution surface consumed by the Phase 1 compiler: - IFactRegistry: trait with try_get_fact_type(fact_id), try_get_fact_id( type_name), and all_fact_ids() returning a deterministic BTreeSet. - StaticFactRegistry: hand-rolled mapping mirroring the .NET StaticFactRegistry.BuildDefaultMappings() exactly. Covers the 16 canonical fact ids advertised by the certificates, MST, and message- level packs (x509-chain-trusted/v1, x509-cert-eku/v1, mst-receipt- trusted/v1, content-type/v1, etc.). The static registry is explicitly TEMPORARY: per R1 (2026-05-08) Phase 3 (np-fact-registry) supersedes it with a hand-rolled register_facts!() macro per pack. This Phase 1 placeholder lets the Phase 1 compiler reject unknown fact ids as TPX200 at compile time without taking a hard dependency on every pack crate. No third-party fact-registration crate (inventory, linkme) per R1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Lowers TrustPolicySpec into a cose_sign1_validation_primitives::plan::
CompiledTrustPlan and ships the byte-stable canonical-JSON serializer
that backs the D9 cache-key invariant.
Phase 1 lowering scope (full structural):
AllowAll, DenyAll, And, Or, Not, Implies, Message, PrimarySigningKey.
Deferred to Phase 3 placeholders (deterministic, strictly-conservative):
RequireFact: validates fact_id against IFactRegistry then emits a
placeholder rule that always denies with the user's failure_message
plus a Phase-1-banner so smoke tests can verify the wiring.
AnyCounterSignature: emits a placeholder rule whose on_empty knob is
observable (Allow -> trusted, Deny -> denied with banner).
CompileError uses TPX-prefixed stable codes (TPX200 unknown fact id,
TPX301 recursion limit, TPX500 predicate malformed).
Determinism contract for to_canonical_json: BTreeMap-backed assertion
maps and serde_json::Map (which is BTreeMap when preserve_order is
disabled, our default) iterate in sorted order; Vec slots preserve
author order so And/Or short-circuit semantics survive serialization.
lib.rs gates missing_docs (#![deny(missing_docs)]) so the public surface
cannot drift undocumented; the doc-test in the crate-level docstring
exercises the full quickstart path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the Phase 1 test suite under the crate's tests/ directory (per Rust idiom + the collect-coverage.ps1 'no #[cfg(test)] in src/' gate): - serde_round_trip.rs (20 tests): every variant + nested combinations serialize and deserialize to byte-identical JSON; #[serde(default)] + skip_serializing_if elision rules; deny_unknown_fields rejects typos. - parameter_bind.rs (13 tests): bind replaces `$param literals; missing- without-default surfaces BindError::MissingParameter with location breadcrumb; bind recurses through every variant + nested arrays/ objects; malformed parameter literals rejected at recognize time. - compile_smoke.rs (21 tests): >=5 representative spec trees compile end-to-end and evaluate to the same outcome as the equivalent fluent- built primitives plan; CompileError variants surface with stable TPX* codes; recursion limit is enforced. - canonical_json.rs (7 tests): 1000-iteration determinism on a complex spec; canonical form independent of BTreeMap insertion order; PredicateOperator wire identifiers match the snake_case contract. - predicate_hybrid.rs (6 tests): Property and PathOperator forms expressing the same logical assertion produce equivalent compiled rules; untagged serde routing is unambiguous. - static_registry.rs (8 tests): every default fact id matches ^[a-z][a-z0-9-]*/v[0-9]+$; forward<->reverse lookup round-trips; iteration order is sorted; the must-have id list from the .NET parity surface is fully populated. 75 tests total, all green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes for the findings raised by the local code-review pass:
api-stability (B -> A): mark TrustPolicySpec, FactPredicateSpec,
PredicateOperator, OnEmptyBehavior, BindError, CompileError,
CompileOptions, BindOptions as #[non_exhaustive] so adding new
variants/fields in a minor-version bump is non-breaking for downstream
pattern matches; introduce FactRegistryExt (blanket-implemented over
IFactRegistry) as the Phase 3 extension seam; add CompileOptions::
with_max_depth() / BindOptions::with_max_depth() builders that work
across the non_exhaustive boundary.
reliability (B+ -> A): replace the only .expect() in src/*.rs (parameter
::ParameterRef::try_recognize) with a let-else over obj.get(PARAM_KEY)
so the parsing path has no latent panic surface.
operability (B+ -> A): give BindError a stable code() (TPX400 missing,
TPX401 malformed, TPX301 recursion) and embed it in the Display impl,
matching CompileError's existing pattern. Helps log scrapers and audit
pipelines correlate without parsing free-form text.
security (B -> A): add a recursion depth cap to the bind walk -- the
compile path was already bounded but bind was not, so deeply nested
user inputs could stack-exhaust the parameter walker. Default cap is
256 (same as compile), overridable via BindOptions::with_max_depth().
performance (B -> A): replace per-recursive-step format!()-based path
breadcrumbs with a reusable BreadcrumbStack: in-place push/pop while
walking, materialize the dotted path string only when constructing a
BindError. Eliminates O(depth) String allocations on the success path.
correctness/architecture (A- -> A): preserve the user-authored Not.
reason through lowering -- compile() now emits a custom FnRule-backed
rule that captures the dynamic String reason instead of falling back
to not_with_reason()'s &'static str-only path. The placeholder default
('Negated rule was satisfied') is used only when reason == None.
testing (A -> A+): tighten parameter-bind tests with structural
assertions over fact_id/path/operator (not just the inner value); add
tests for the new TPX-coded BindError Display, the bind recursion
cap, and FactRegistryExt's blanket impl.
Per-crate coverage: 96.66% -> 97.05%.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mitives
Adds the compile-time fact-id contract the Rust trust-policy port uses
to supersede Phase 1's hand-curated StaticFactRegistry per decision R1.
- TrustFactWithId trait (const FACT_ID)
- TrustFactDescriptor (#[non_exhaustive], Clone/Debug/Eq)
- validate_fact_id const fn for ^[a-z][a-z0-9-]*/v[0-9]+\$
- register_facts!{} declarative macro emitting per-crate
__cose_sign1_trust_facts() function
No third-party crates (no inventory/linkme); no link-time magic.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements TrustFactWithId for the 9 X.509 facts in the certificates pack and calls register_facts! in lib.rs so the workspace registry can collect them. Compile-time validate_fact_id() asserts protect each id against accidental malformation. IDs match Phase 1's StaticFactRegistry baseline byte-for-byte. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tags the 3 baseline MST facts (present / trusted / issuer-host) and calls register_facts! in lib.rs. Phase 1 gaps (kid, statement-sha256, statement-coverage, signature-verified) are surfaced in the final report rather than silently assigned new ids. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tags 4 baseline message-level facts (content-type, counter-signature-subject, detached-payload-present, unknown-counter-signature-bytes) and calls register_facts! in lib.rs. Phase 1 gaps surfaced in final report. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
HandRolledFactRegistry::from_packs(&[...]) collects per-pack __cose_sign1_trust_facts() outputs into a deterministic id-keyed BTreeMap. Construction rejects duplicate ids (TPX300) and malformed ids (TPX301); validate_fact_id is re-applied as a runtime safety net for callers that skip the const-assert. StaticFactRegistry is now #[deprecated] and retained only as the conformance baseline for the upcoming hand_rolled_equals_static_baseline test (Phase 5a removes it). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ests - tests/conformance_baseline.rs: HandRolledFactRegistry must be byte-identical to Phase 1's StaticFactRegistry (16 facts). - tests/hand_rolled_registry.rs: error paths (TPX300/TPX301), empty registry, multi-pack collection, descriptor lookup, Clone/Debug. - per-pack tests/registered_facts.rs: ids match expected baseline, descriptors self-attribute to their crate. - primitives/tests/fact_id.rs: TrustFactWithId, TrustFactDescriptor, validate_fact_id (good + bad inputs), register_facts! expansion. - #[allow(deprecated)] on existing tests + doctest that intentionally exercise StaticFactRegistry as the conformance baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per-perspective findings addressed: - Correctness (B+ -> A+): from_packs() now also rejects duplicate type_name (TPX302). Public TrustFactDescriptor::new() lets callers construct arbitrary descriptors, so the registry must defend the reverse-lookup invariant explicitly. - API Stability (A- -> A+): RegistryError is #[non_exhaustive] — future TPX3xx variants are additive without breaking downstream match consumers. - Performance (B- -> A): HandRolledFactRegistry's lookup maps are now BTreeMap<&'static str, TrustFactDescriptor>, dropping per-id String allocations for the rodata-resident keys. Only the all_ids mirror remains owned (forced by the IFactRegistry::all_fact_ids contract). - Tester (A- -> A+): conformance_baseline.rs adds full pack-attribution snapshot + iteration-determinism-under-pack-permutation test (6 permutations, all must produce byte-identical iter order); fact_id.rs adds boundary edge cases and locks documented leading-zero behavior; hand_rolled_registry.rs adds TPX302 + diagnostic_code stability test. Coverage: trust_policy_spec 97.40%, primitives 97.82%. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t/result/diagnostic Adds the abstract translation contract (CoseTrustPolicyFrontend<TDocument>) plus the supporting types (TrustPolicyTranslationContext, TrustPolicyTranslationResult, TrustPolicyTranslationDiagnostic, TrustPolicySeverity, FactCapabilities) to cose_sign1_trust_policy_spec. Placement mirrors the .NET architectural decision: the trait lives next to the IR rather than in the validation runtime to avoid a project cycle. Every public type is #[non_exhaustive] for forward-compatible evolution, with ew() / �rror() / warning() / info() constructors so cross-crate consumers can instantiate without literal-form blocks. Additive only — Phase 1 / Phase 3 public APIs are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Empty (lib.rs only) cose_sign1_trustfrontends_json crate at validation/trustfrontends/json/. Dependencies (jsonschema 0.30, moka 0.12 sync, blake3 1, serde, serde_json) are added to the per-crate allowlist with R3/R4/R5 decision citations. jsonschema is configured with default-features = false to suppress the remote-ref reqwest/hyper/tokio chain — the embedded schema is the only schema the frontend ever evaluates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to .NET) Embeds the canonical user-document JSON Schema for cose-tp-json/v1 at native/rust/validation/trustfrontends/json/schemas/cose-tp/v1.json. The file is byte-identical (after platform line-ending normalization to LF) to the .NET schema at V2/schemas/cose-tp/v1.json so that documents valid under one frontend are valid under the other. The cross_port_schema integration test in a subsequent commit asserts this byte-identity by diffing against the .NET source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…slate)
Adds the translation pipeline for cose-tp-json/v1:
Stage 1: serde_json parse — TPX001 with line/column on malformed input.
Stage 2: jsonschema 0.30 Draft-2020-12 validation against the embedded
schema. Each leaf failure surfaces as TPX100 (or TPX101 when
the failing pointer is /frontend).
Stage 3: DocumentTranslator walk over the validated tree producing a
TrustPolicySpec. Capability gating (D4) emits TPX200 for
unknown fact ids and TPX201 for predicate-schema mismatches.
Bounded recursion (default depth 64) emits TPX300.
Defensive arms emit TPX301 (every schema-rejected shape is
caught earlier, so these arms are unreachable in the public
flow but preserved for totality (§6.5.4 #2)).
Mirrors the .NET CoseTpJsonFrontend / DocumentTranslator pair, with the
operator parser case-insensitively accepting the schema's PascalCase
form and emitting the canonical snake_case in the IR.
Cache module is stubbed; concrete LRU implementation lands next.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds TranslatorCache: an in-process LRU cache fronting CoseTpJsonFrontend (per design decision D9). Cache key is the concatenation of three blake3-256 hashes — (canonical document bytes, canonical sorted parameter JSON bytes, canonical capability fingerprint bytes) — plus the optional document_source string. Default capacity is 32, configurable via CoseTpJsonOptions::cache_capacity. Backed by moka::sync::Cache (R4) with blake3 (R5) for hashing. The key collision space is 2^256 per axis; the eventually-consistent entry_count() helper is exposed for diagnostics but never as a contract. Cached values are wrapped in Arc<TrustPolicyTranslationResult> so concurrent readers obtain reference-shared snapshots without copying — matching §6.5.9 anti-pattern #4: cached entries are derived data, never the policy of record. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds --trust-policy <path> and --trust-policy-param <key=value> (repeatable)
to CoseSignTool verify x509. When --trust-policy is supplied:
1. Read the document text from disk.
2. Translate via cose_sign1_trustfrontends_json (parse, schema-validate, walk).
3. Bind \ references against the parsed parameter map.
4. Compile against a HandRolledFactRegistry assembled from the configured
packs' fact producers (validation/core + certificates + optionally MST).
5. Bundle the CompiledTrustPlan with the same packs for evaluation.
Pack fact producers stay registered so RequireFact references resolve. The
default pack-driven trust plan is bypassed (D8 OVERRIDE semantics).
When --trust-policy is omitted, existing behavior is unchanged.
Also ships the README.md for the new crate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 9 integration tests under tests/ (no #[cfg(test)] in src/ — Rust train
gate enforces this):
translate_smoke.rs — 6 representative documents → expected TrustPolicySpec.
schema_validation.rs — malformed docs → diagnostic with stable code.
capability_gate.rs — D4 unknown fact id (TPX200), opt-in tolerance.
predicate_hybrid.rs — both predicate forms; byte-determinism.
parameter_binding.rs — pre-bind has literals; post-bind doesn't;
missing-without-default raises BindError.
cache.rs — LRU equality, capacity bounds, 0-cap rejection.
cross_port_schema.rs — byte-identity vs the .NET schema (D7 lock).
perf_smoke.rs — 1KB doc translates in <50ms (Phase 4 enforces ≤10ms p99).
cli_integration.rs — invokes the CoseSignTool binary end-to-end.
Also corrects the embedded schema's trailing-byte sequence so it matches
the .NET source bit-for-bit after CRLF→LF normalization (the cross-port
test now passes against the .NET blob fetched via 'git cat-file').
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the new validation/trustfrontends/conformance crate to the Rust workspace and the dependency allowlist ([crate.conformance] for serde/serde_json plus the opt-in criterion-perf feature). The crate ships the §6.5.10 8-property conformance harness used by every shipping CoseSign1 trust-policy frontend; subsequent commits add the harness, fixtures, JSON adapter wiring, perf gate, and README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the §6.5.10 contract: - ConformanceAdapter<TDoc> — per-frontend bridge (create_frontend, load_document, fixture_extension, fixture_root, registered_fact_ids). - 8 run_conformance_* functions: determinism, attribute fidelity, reject-untranslatable, bounded runtime, capability-aware, parameter substitution, schema validation, cross-frontend equivalence. - run_conformance_all composite for single-frontend test crates. - Public analysis helpers (collect_fact_ids, contains_param_literal) for downstream consumers and harness reuse. - fixtures helpers with '/' -> '--' fact-id filename encoding (collision-free since '--' cannot appear in a valid id). Cross-frontend equivalence is exercised even with one frontend (degenerate (json, json)) so the contract is locked ahead of Phase 5a (Rego). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…etric + cross + perf)
Ships the canonical fixture set referenced by the §6.5.10 harness:
- per_fact/: 16 fixtures, one per fact id in the canonical baseline (StaticFactRegistry::default_mappings); '/' is encoded as '--' in filenames.
- untranslatable/: free_text_search, unknown_fact, unknown_operator (TPX100/TPX200 expectations).
- capability/missing_fact: TPX200 lock for the capability gate.
- schema/{malformed_text,shape_violation}: TPX001/TPX100 paths.
- parametric/{host_baseline,host_alternate}.coseTrustPolicy.json plus sibling .params.json files for property #6 (different parameter sets -> different IRs).
- perf/representative_1kb (≤ 1 KiB) — drives the p99 ≤ 10 ms statistical gate.
- cross/canonical_policy/canonical_policy.coseTrustPolicy.json — fixture for the §6.5.10 #8 cross-frontend equivalence harness.
- cross/canonical_policy/canonical_ir.expected.json — committed canonical-IR golden, regenerated via 'cargo test ... regenerate_golden -- --ignored'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wires the canonical cose-tp-json/v1 frontend through every §6.5.10 property in 8 individual #[test] functions (one per property) so a regression on one property surfaces as one focused failure rather than a composite fall-through. Mirrors .NET's FrontendConformanceTestBase per-method shape. Companion test suites: - fixtures_helpers — fact-id encoding/decoding round-trip plus path composition. - analysis_tests — direct unit coverage for collect_fact_ids and contains_param_literal across every TrustPolicySpec variant (including non-fixture-reachable ones like top-level And/Or/Not/Implies). - harness_smoke — composite run_conformance_all entry point + measure_p99 sanity + read_fixture panic-on-missing path. JsonConformanceAdapter::default carries the 16-fact canonical baseline; with_fact_ids overrides for hosts running a tighter capability surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
§6.5.10 #4 (bounded runtime): a representative ≤ 1 KiB document MUST translate with p99 ≤ 10 ms over a statistically meaningful sample. Two implementations: - Built-in (always-on #[test]): hand-rolled std::time::Instant sampling, nearest-rank p99, 16 warm-up + 256 timed samples. Asserts the 10 ms target. Default 'cargo test' runs this; no extra deps. - Criterion bench (opt-in via --features criterion-perf): richer reporting + regression history. Default builds do NOT pull Criterion as a dependency; required-features in Cargo.toml gates the bench target on the feature. Phase 2's smoke test asserts ≤ 50 ms (loose); Phase 4 tightens to 10 ms p99 — frontends that miss this gate are NOT ship-eligible (anti-deferral). Sample p99 measured on the JSON frontend during gate runs is well under 1 ms (10x headroom). nearest_rank_percentile is exposed publicly and unit-tested in tests/perf_p99.rs across edge cases (empty slice, single sample, p100 cap). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-port IR Phase 4 #8 deliverable: parsing the cross/canonical_policy fixture with the Rust JSON frontend MUST produce canonical-IR JSON byte-equal to the committed canonical_ir.expected.json golden after line-ending normalisation. The golden file ships at fixtures/cross/canonical_policy/canonical_ir.expected.json (committed in the previous fixture commit). When the .NET JSON frontend ships, its conformance suite asserts byte-equality against the same fixture; if the two ports drift the diff is one git-blame away. regenerate_golden is an #[ignore]'d test for intentional IR shape changes: cargo test -p cose_sign1_trustfrontends_conformance --test cross_port_canonical_ir regenerate_golden -- --ignored The §6.5.10 #8 in-process degenerate (json, json) check (already in src/harness.rs) cooperates with this: the Rust frontend is byte-stable across two parses (deterministic encode), and the Rust output is byte-equal to the committed golden — together they lock canonical-IR shape both within the Rust port and across to .NET. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documents:
- The full 8-property table verbatim from §6.5.10.
- How a new frontend opts in (impl ConformanceAdapter, drop fixtures, call run_conformance_*).
- Fact-id filename encoding rule ('/' -> '--').
- Why each property exists + which bug class it catches.
- Performance gate rationale (PERF_SAMPLE_COUNT/WARMUP_COUNT, the 10 ms target reasoning).
- Cross-port note: this Rust suite mirrors the .NET CoseSign1.Validation.TrustFrontends.Conformance contract; the canonical-IR golden file enforces byte-equivalence at fixture granularity.
- Coverage discipline: per-crate 90% gate per Rust D11 amendment.
- Out-of-scope: Rego frontend (Phase 5a), FFI surface (Phase 4.5).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the manual ceiling-division formula '((pct * n) + 99) / 100' with usize::div_ceil — the canonical nearest-rank percentile rank formula 'ceil(pct/100 * n)'. Fixes a clippy::manual_div_ceil warning surfaced by the Phase 4 jeromy_review (Performance perspective B+ feedback) and removes the +99 magic constant that obscured the percentile-agnostic correctness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 4.5 of the native Rust trust-policy port: stable C ABI for loading,
translating, binding, and compiling .coseTrustPolicy.json documents from
non-Rust consumers (C/C++ direct, .NET via P/Invoke, Node via N-API, Go via cgo).
Adds the workspace member at validation/trust_policy_spec/ffi with:
* Opaque heap-owned handle types: spec, translation_result, compiled_plan
* #[repr(transparent)] borrowed diagnostic_t (lifetime tied to result)
* cose_sign1_trust_policy_translate_json (length-prefixed UTF-8 input)
* cose_sign1_trust_policy_spec_bind (clones spec, returns new result handle)
* cose_sign1_trust_policy_spec_compile + _compile_to_result (registry from
statically-linked certificates+MST+validation pack contributors)
* Diagnostic introspection (count/at/read) returning borrowed UTF-8 spans
* has_parameters pre-flight predicate
* Free functions for every owned handle (null-tolerant)
* cose_sign1_trust_policy_frontend_id() exposing 'cose-tp-json/v1' as C string
Every exported function uses with_catch_unwind so Rust panics never cross the
ABI boundary. Translation diagnostics live on the result handle (R6 opaque-error
discipline); the cose_status_t return is reserved for infrastructure failures
(null pointer args, allocation, panic).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds native/c/include/cose/sign1/trust_policy.h — the authoritative C ABI surface for trust-policy translation. Hand-written (matches the repo's existing pattern at trust.h, validation.h, sign1.h, etc.) rather than cbindgen-generated: the repo has no build.rs / cbindgen.toml infrastructure and the existing FFI headers are all manually curated. Drift is gated by tests/header_drift.rs: parses every pub extern "C" fn cose_sign1_trust_policy_* export from lib.rs and every function declaration in trust_policy.h, asserts the two name sets are equal in both directions, and additionally requires a known canonical entrypoint to be present so the parser cannot pass vacuously. Also adds tests/ffi_smoke.rs — a 23-test suite that round-trips translate to diagnostic introspection to bind to compile to free entirely through the C ABI surface (calls match what a C consumer would invoke). Covers minimal success, parse error (TPX001), schema violation (TPX1xx), out-of-bounds diagnostic_at, defensive null handling on every accessor + free function, null out_result / null json with nonzero len / non-UTF8 input infrastructure errors, bind happy path + empty-buffer-uses-default + missing-parameter (TPX400), bind with non-object JSON / null spec / null out_result errors, compile happy path + null arg variations, compile_to_result with and without out_plan, compile_to_result for unknown fact id (TPX200) via diagnostic, frontend_id canonical string with stable pointer, partial out-pointer reads of diagnostic_read, and the end-to-end translate + bind + compile lifecycle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds native/c/tests/trust_policy_translate_test.c (C consumer) and
trust_policy_translate_gtest.cpp (C++ / GoogleTest consumer). Both exercise
the full C ABI surface end-to-end: translate -> diagnostic introspection ->
bind -> compile -> free.
The C executable is built unconditionally when COSE_FFI_TRUST_POLICY_LIB
is detected; the C++ gtest variant is built when GTest is available via
vcpkg (mirrors smoke_test / smoke_test_gtest pattern).
Tests cover:
* minimal-doc translate-success
* translate-parse-error with TPX001 diagnostic introspection (verifies
severity == ERROR, code byte-span and length, message presence,
line/column 1-indexed)
* bind + compile happy path with parameter substitution
* bind missing-parameter surfaces TPX400 via diagnostic
* frontend_id returns canonical 'cose-tp-json/v1'
* defensive null tolerance on every accessor and free function
* out-of-bounds diagnostic_at returns NULL
* null out_result is reported as infrastructure error
The C++ test uses RAII handle wrappers (TrustPolicyResult / TrustPolicySpec /
CompiledPlan) to assert the C ABI integrates cleanly with C++ ownership
patterns.
CMakeLists wiring detects cose_sign1_trust_policy_spec_ffi via find_library
and links it into the cose_sign1 INTERFACE target with the COSE_HAS_TRUST_POLICY
compile definition.
Verified locally on Windows with MSVC 19.44 + Visual Studio 17 2022 generator:
* cargo build --release --workspace produces the .dll + .dll.lib
* cmake configure detects all packs including the new trust-policy translator
* trust_policy_translate_test.exe exits 0 and prints all 5 ok lines
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documents the FFI surface: pipeline, ownership rules per pointer kind, diagnostic discipline, threading guarantees, ~30-line sample C and C++ consumers, test surface, coverage gate command, phase-5 follow-up note, and the R1/R6/R7 decision links. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…90% gate
Coverage gate work for the per-crate threshold (FailUnderLines 90):
* Forward-compat catch-alls in spec_has_parameters / predicate_has_parameters /
cose_sign1_trust_policy_severity_t::from_severity refactored from match-arm
bodies (which cannot carry #[coverage(off)] per the rustc lint) into
inline(never) helper functions tagged #[cfg_attr(coverage_nightly,
coverage(off))]. Unreachable until the IR crate ships a new variant; the
attribute removes the dead code from the coverage denominator.
* compile_error_location split out for the same reason — the inner match's
'CompileError that carries a non-None location' arm is reachable only when
a frontend populates location, which Phase 1 IR-direct compile never does.
* Severity discriminant from_severity exposed as #[doc(hidden)] pub for
integration-test access without expanding the C ABI surface.
New test coverage:
* bind_with_null_params_and_nonzero_len_is_an_error
* bind_with_malformed_json_parameters_is_an_error (parse_parameter_map serde
error path)
* compile_with_unknown_fact_id_returns_error_status (anyhow path via
cose_sign1_trust_policy_spec_compile)
* compile_to_result_error_clears_out_plan (sentinel-based assertion that the
error path explicitly nulls out_plan even when caller pre-populated it)
* has_parameters_walks_logical_combinators (and / or / not)
* has_parameters_walks_property_assertion_and_path_operator_predicates
* diagnostic_severity_mapping_is_round_trip_stable
Removed an earlier duplicate compile_to_result_with_unknown_fact_id test that
used 'message: [...]' which the schema does not accept; the surviving copy
uses primary_signing_key + bare fact (matches existing translate-smoke
patterns).
Per-crate coverage: 324/341 = 95.01% (gate: 90%).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skeleton crate scaffolding for Phase 5a (np-frontend-rego). Adds the workspace member at validation/trustfrontends/rego/, registers it in the workspace Cargo.toml, and pins serde + serde_json in allowed-dependencies.toml under the [crate.rego] tier. Subsequent commits land the parser, lowerer, frontend trait impl, conformance adapter, fixtures, and tests on top of this skeleton. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Hand-rolled tokenizer + recursive-descent parser for the §6.5.6 constrained Rego subset (Option B; Option A regorus rejected — see README.md ‘Why a constrained subset’ for the trade-off table). - AST → cose-tp-json/v1 JsonValue lowering. Translation reuses the JSON frontend's schema validator + walker so cross-frontend canonical-IR byte-equality is a property of construction, not duplicated logic. - Closed reject-list with TPX301-306 + TPX001/002/003/004/005 + TPX300 per-cause sub-codes, vocabulary verbatim with .NET Phase 5a so cross-port telemetry stays comparable. - 16 per-fact + 1 cross + 3 untranslatable + 1 capability + 2 schema + 2 parametric + 1 perf Rego conformance fixtures (26 total), paralleling the JSON adapter's existing fixture tree under fixtures/<scenario>/<stem>.coseTrustPolicy.rego. - RegoConformanceAdapter wires the frontend through the Phase 4 8-property conformance harness; cross-frontend equivalence (§6.5.10 #8) now runs as the true (json, rego) pair instead of the (json, json) degenerate. - 69 tests across 5 test files (translate_smoke + reject_list + parser_robustness + cross_frontend_byte_equality + conformance_rego); cli_integration is env-gated on RUN_CLI_INTEGRATION to match Phase 2's opt-in CLI integration pattern. - CLI dispatch in commands/trust_policy_override.rs: extension / package-header sniff routes .coseTrustPolicy.rego to the new frontend; the JSON path is unchanged so D8 override semantics carry through. - Trivial fix to pre-existing verify.rs unit tests that pre-dated the Phase 2 trust_policy field addition (4 literal struct inits in describe_trust_mode_* tests dropped the new fields). - README documenting frontend identity, accept-list grammar, reject-list with TPX codes, example document, FFI rationale (no cose_sign1_trust_policy_translate_rego extern; lower-to-JSON in the host language and reuse the existing extern), CLI dispatch contract, and cross-port equivalence to .NET CoseSign1.Validation.TrustFrontends.Rego. Phase 5a does NOT add a new FFI extern: the Rego frontend is a parse + lower pipeline whose output is a JSON tree the existing cose_sign1_trust_policy_translate_json extern accepts. Adding a sibling extern would expand the C-ABI surface, force a cbindgen re-run + header drift assertion update, and provide no new functionality. Documented in both the rego README and the FFI rationale section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 37 coverage-branch tests targeting paths the smoke / reject-list / robustness / conformance suites do not exercise. Marks two genuinely unreachable defensive arms with coverage(off): lower.rs number-fallback and frontend.rs seed-merge. Per-file coverage (own-source): adapter 78%, document 100%, frontend 96.8%, lib 90.9%, lower 90.9%, parser 91.2%, tokenizer 96.7%. Workspace own-source: 93% >= 90% gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Performance (B -> A): - json: add public translate_value_with_source so callers with a typed Value can skip the to_string + reparse round-trip while keeping the document_source diagnostic anchor. - rego: switch translate_internal to call translate_value_with_source on the lowered Value directly. Eliminates the canonical_text round-trip. Operability (B -> A): - rego: thread document_source into parser + tokenizer-error diagnostic emission. Diagnostic messages now carry a "<source>:<line>:<col>: " prefix so editor / log tooling can navigate back to the offending file from a diagnostic line alone. - rego: add public RegoDocument::lowered() accessor for operator-facing debugging of the cose-tp-json/v1 tree the JSON walker observes. - README: add "Reading diagnostics" section enumerating the diagnostic shape (code / message / severity / location / suggestion) plus the RegoDocument::lowered escape hatch. Testing / Correctness (B -> A): - parser: detect Identifier-followed-by-pipe at the term-position lookahead so [x | y > 0] surfaces TPX304 directly instead of TPX300 from parse_identifier_term. Fixes the comprehension classification regression the correctness reviewer flagged. - reject_list: tighten tpx304_array_comprehension_is_rejected to require TPX304 verbatim (no TPX300 fallback). - boundary_acceptance.rs: 14 new boundary tests proving the closest legal sibling of every reject family still translates -- legal escapes / surrogate pairs, allowed import, exact package, single rule, namespace names as string values, iteration-keyword lookalikes as strings, input.data alias, literal arrays at comprehension position, 60-deep nesting (cap-4 below TPX305), MAX_INPUT_BYTES-4096 (below TPX306). Plus pair-then-lone-low-surrogate + document-source-prefix + lowered() accessor. Documentation (A- -> A): - adapter: explain why RegoConformanceAdapter ships in this crate and not the conformance crate, plus the cross-equivalence-by-construction story the (json, rego) fixture pair locks in. - README FFI: clarify that the crate currently exposes Rust APIs only; non-Rust hosts either lower-to-JSON in their host language or build their own Rust shim. Coverage: 93.3% (was 93%); 120 tests across 7 test files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e (native) Mirrors the V2 rename in the native Rust validation pipeline, moving the indirect content-match concept away from 'signature' wording: - IndirectSignaturePostSignatureValidator -> IndirectContentDigestPostSignatureValidator - IndirectSignatureKind -> IndirectContentDigestKind; detect_indirect_signature_kind -> detect_indirect_content_digest_kind - VALIDATOR_NAME -> 'Indirect Content Digest Validation' - error codes INDIRECT_SIGNATURE_* -> CONTENT_DIGEST_* (payload mismatch -> CONTENT_DIGEST_MISMATCH) - metadata IndirectSignature.Format/.HashAlgorithm -> ContentDigest.Format/.HashAlgorithm Cryptographic 'signature-only verification' wording is intentionally preserved. cargo build/test (543 passed), clippy -D warnings, and fmt --check all pass. Refs #206 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
|
Landed directly on native_ports_final via fast-forward push (merged origin's #195 commit first). PR not needed. |
f7ef13a
into
users/jstatia/native_ports_final
17 of 19 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Mirrors the V2 rename (#companion) in the native Rust validation pipeline, moving the indirect
content-match concept away from "signature" wording to content digest. Companion to the V1 clarity
work in #206 (this is the distinct native trust/validation surface, not the V1
SignatureMatchesAPI).Rename mapping (
native/rust/validation/core)struct IndirectSignaturePostSignatureValidatorIndirectContentDigestPostSignatureValidatorenum IndirectSignatureKindIndirectContentDigestKindfn detect_indirect_signature_kindfn detect_indirect_content_digest_kindVALIDATOR_NAME = "Indirect Signature Content Validation""Indirect Content Digest Validation"INDIRECT_SIGNATURE_*CONTENT_DIGEST_*(payload mismatch →CONTENT_DIGEST_MISMATCH)"IndirectSignature.Format"/".HashAlgorithm""ContentDigest.Format"/".HashAlgorithm"The cryptographic
PostSignatureValidatortrait and the"signature-only verification"wording areintentionally preserved (they refer to the actual COSE signature, not the content digest).
Files
native/rust/validation/core/src/indirect_signature.rsnative/rust/validation/core/src/validator.rs(construction site)native/rust/validation/core/tests/final_coverage_gaps.rs(comment references)Validation (
cose_sign1_validation)cargo build→ passcargo test→ 543 passed / 3 ignoredcargo clippy -- -D warnings→ passcargo fmt --check→ passRefs #206