feat: carry + adjudicate agentic_posture binding (OAP spec 210 verify half)#12
Conversation
tenant-tail now carries the agenticPostureBinding certificate field and adjudicates it, completing the VERIFY half of spec 210's tenant close-out (the emit half is tenant-emit). Carrying the field is mandatory for hash parity: compute_certificate_hash round-trips the certificate through the typed struct, so a field the verifier did not know would be dropped on re-serialisation and the re-derived hash + signature would diverge from what tenant-emit produced. Adjudication (append_posture_binding_findings, composed into verify_certificate_with_platform): - Internal consistency (agentic_posture_binding_inconsistencies): none-with-surfaces, declared/governed-empty, unknown posture, governed surface missing/non-conformant envelope. The FR-004 governed shape check is TOP-LEVEL (recognised schema_version + required spec-198 sections present with correct JSON types); the full nested GovernanceEnvelope is not vendored (Apache-2.0). - SBOM watchlist cross-check under --sbom-dir (adjudicate_agentic_posture): a `none` posture (authored or defaulted) contradicted by a watchlisted agent/LLM SDK in the BOM is fatal (exit 1); a miss is a stated-residual notice, never a silent pass. purl_matches is the FIXED matcher: it anchors on the purl package-name segment (exact-equal), so the bare `ai` watchlist entry no longer over-flags `@scope/ai` packages via the `/ai@` substring (the bug OAP's substring matcher carries). The watchlist is embedded as JSON (data/agentic-sdk-watchlist.json) so tenant-tail adds no YAML dependency. CERTIFICATE_VERSION stays 1.5.0 (additive, skipped when absent, so unbound certs stay byte-identical and the parity fixture is unaffected). Tests: 19 posture unit tests (consistency, cross-check, the purl fix, hash participation, byte-identity, wire form); existing core + parity tests stay green. Cross-tool round-trip validated locally against tenant-emit: a posture-bearing cert verifies clean, a tampered posture fails signature+hash (AC-2), a none-posture with @anthropic-ai/sdk in the BOM is Contradicted (AC-3). specs/001 updated (establishes + territory + behavior); .derived regenerated.
AI Code ReviewNow I have enough context for a complete review. Code ReviewBugs and Logic Errors[HIGH] The function strips Fix: compare the extracted type against the entry's let (type_part, coord) = match core.split_once('/') {
Some((scheme, rest)) if scheme.starts_with("pkg:") =>
(&scheme["pkg:".len()..], rest),
_ => ("", core),
};
// guard: watchlist entry is npm-only
if !type_part.eq_ignore_ascii_case(&entry.ecosystem) { continue; }There is no test exercising [MEDIUM] Unknown posture string emits a misleading In [LOW] The [LOW]
Security[LOW] Recursive The inner Spec-Spine ComplianceSpec 210 is an OAP reference, not a tenant-tail spec — that is fine; the code is correctly claimed by The new Performance
SummaryThe purl-ecosystem mismatch (bug 1) and the unknown-posture/Declared notice contradiction (bug 2) are the only issues that could produce incorrect verification verdicts in the field. The rest are quality/robustness concerns. The spec-spine coupling discipline is correctly followed — spec updated, derives regenerated, new file claimed. Automated review by Claude. Diff size: 784 lines. |
…posture notice (spec 210 verify) (#13) * fix(001): ecosystem-scope the SDK watchlist + drop the contradictory unknown-posture notice (spec 210) Two verify-verdict fixes surfaced by review of PR #12: - purl_matches gates on the watchlist entry's ecosystem: a same-named dependency from another ecosystem (pkg:pypi/openai, pkg:cargo/ai) no longer false-matches the npm openai/ai entries on a mixed-language BOM. The ecosystem field (previously dead_code) is now load-bearing; a purl-less component keeps the name-only fallback. - append_posture_binding_findings skips the SBOM cross-check when the binding is internally inconsistent (e.g. an unknown posture string), so a "maybe" posture no longer emits BOTH an error AND a reassuring "DECLARED" notice. Also depth-bounds the recursive BOM component walk (untrusted --sbom-dir input) at MAX_BOM_DEPTH so a pathologically nested BOM stops instead of overflowing the stack. Tests: purl_matcher_gates_on_ecosystem + posture_findings_unknown_posture_errors_without_declared_notice; 108 lib tests pass. spec 001 §3 documents the ecosystem-scoping + the inconsistency short-circuit. * style(001): rustfmt-wrap the purl_matches qualifier test call (spec 210)
First release since v0.3.0, bundling PRs #12 and #13 (OAP spec 210 verify half). Additive feature, so this is a minor bump under 0.x. - Added (#12, spec 001): verify-certificate carries and adjudicates the agenticPostureBinding: round-trips it through the typed cert struct so the self-hash and signature hold, then adjudicates internal consistency, an SBOM cross-check of a declared none under --sbom-dir, and the spec 210 FR-004 governance-envelope shape check. - Added (#13, spec 001): ecosystem-scoped the agentic-SDK watchlist (npm openai no longer matches pkg:pypi/openai; purl match anchors on the name segment) and skipped the cross-check when internal consistency already failed, dropping the contradictory unknown-posture notice. Version bumped in lockstep across Cargo.toml, npm/package.json (main + five platform pins), and py/pyproject.toml; Cargo.lock and the codebase-index shard regenerated.
What
Complete the verify half of OAP spec 210 (
agentic_posture) in tenant-tail:carry the
agenticPostureBindingcertificate field and adjudicate it. Pairs withstatecrafting/tenant-emit#12 (the emit half).
Why carrying the field is mandatory (not optional)
compute_certificate_hashre-derives the certificate hash by round-tripping throughthe typed
GovernanceCertificatestruct. If tenant-emit emits anagenticPostureBindingand tenant-tail's struct does not know the field, serde dropsit on re-serialisation, the re-derived hash diverges, and the signature check
fails. So a cert that tenant-emit produces with a posture would fail to verify. This
is the same shape as the 203 SBOM binding (field + adjudicator).
Adjudication
verify_certificate_with_platform→append_posture_binding_findings:agentic_posture_binding_inconsistencies, no BOM):none-with-surfaces,declared/governed-empty, unknown posture,governedsurface missing / non-conformant envelope. A validly-signed but self-inconsistent
binding is rejected.
adjudicate_agentic_posture, under--sbom-dir):a
noneposture (authored OR defaulted) contradicted by a watchlisted agent/LLMSDK dependency in the CycloneDX BOM is fatal;
declared/governedacknowledgeagency and never fail on a match; a miss is a stated-residual notice, never a silent
pass.
The purl-matcher fix
purl_matchesanchors on the purl's package-name segment (exact-equal), so thebare
aiwatchlist entry no longer over-flags@scope/aipackages via the/ai@substring — the false-positive OAP's substring matcher carries (flagged in the
spec-210 AI review).
pkg:npm/%40langchain/ai@xno longer matchesai;pkg:npm/ai@xstill does.Notes
data/agentic-sdk-watchlist.json, same package setas OAP's YAML) so tenant-tail adds no YAML dependency.
CERTIFICATE_VERSIONstays 1.5.0 (additive, skipped when absent; unbound certsand the parity fixture are byte-identical).
schema_version+ required spec-198 sections present); the full nestedGovernanceEnvelopeis not vendored (Apache-2.0). Documented in spec 001.notices/errorsand already has--sbom-dir.Test plan
cargo test --workspace --lockedgreen (19 new posture unit tests; existing core +cross-tool parity + provenance suites unchanged).
cargo clippy --workspace --all-targets --locked -- -D warningsclean;cargo fmt --checkclean;spec-spine compile/index check/lint --fail-on-warn/coupleclean (no waiver).
a posture-bearing cert verifies clean and reports "posture: DECLARED"; tampering the
bound posture fails signature + hash (spec 210 AC-2); a defaulted-
nonecert whoseBOM carries
@anthropic-ai/sdkis rejected "contradicted by SBOM dependency" (AC-3).Version bump lands in a follow-up release PR.