Skip to content

feat: carry + adjudicate agentic_posture binding (OAP spec 210 verify half)#12

Merged
bartekus merged 1 commit into
mainfrom
feat/210-agentic-posture-verify
Jul 8, 2026
Merged

feat: carry + adjudicate agentic_posture binding (OAP spec 210 verify half)#12
bartekus merged 1 commit into
mainfrom
feat/210-agentic-posture-verify

Conversation

@bartekus

@bartekus bartekus commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

Complete the verify half of OAP spec 210 (agentic_posture) in tenant-tail:
carry the agenticPostureBinding certificate field and adjudicate it. Pairs with
statecrafting/tenant-emit#12 (the emit half).

Why carrying the field is mandatory (not optional)

compute_certificate_hash re-derives the certificate hash by round-tripping through
the typed GovernanceCertificate struct. If tenant-emit emits an
agenticPostureBinding and tenant-tail's struct does not know the field, serde drops
it 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_platformappend_posture_binding_findings:

  • Internal consistency (agentic_posture_binding_inconsistencies, no BOM):
    none-with-surfaces, declared/governed-empty, unknown posture, governed
    surface missing / non-conformant envelope. A validly-signed but self-inconsistent
    binding is rejected.
  • SBOM watchlist cross-check (adjudicate_agentic_posture, under --sbom-dir):
    a none posture (authored OR defaulted) contradicted by a watchlisted agent/LLM
    SDK dependency in the CycloneDX BOM is fatal; declared/governed acknowledge
    agency and never fail on a match; a miss is a stated-residual notice, never a silent
    pass.

The purl-matcher fix

purl_matches anchors on the purl's package-name segment (exact-equal), so the
bare ai watchlist entry no longer over-flags @scope/ai packages via the /ai@
substring — the false-positive OAP's substring matcher carries (flagged in the
spec-210 AI review). pkg:npm/%40langchain/ai@x no longer matches ai;
pkg:npm/ai@x still does.

Notes

  • Watchlist embedded as JSON (data/agentic-sdk-watchlist.json, same package set
    as OAP's YAML) so tenant-tail adds no YAML dependency.
  • CERTIFICATE_VERSION stays 1.5.0 (additive, skipped when absent; unbound certs
    and the parity fixture are byte-identical).
  • FR-004 governed-envelope check is a top-level shape check (recognised
    schema_version + required spec-198 sections present); the full nested
    GovernanceEnvelope is not vendored (Apache-2.0). Documented in spec 001.
  • No CLI change: the verify verb already surfaces notices/errors and already has
    --sbom-dir.

Test plan

  • cargo test --workspace --locked green (19 new posture unit tests; existing core +
    cross-tool parity + provenance suites unchanged).
  • cargo clippy --workspace --all-targets --locked -- -D warnings clean;
    cargo fmt --check clean; spec-spine compile/index check/lint --fail-on-warn/couple
    clean (no waiver).
  • Cross-tool round-trip validated locally against the tenant-emit#12 binary:
    a posture-bearing cert verifies clean and reports "posture: DECLARED"; tampering the
    bound posture fails signature + hash (spec 210 AC-2); a defaulted-none cert whose
    BOM carries @anthropic-ai/sdk is rejected "contradicted by SBOM dependency" (AC-3).

Version bump lands in a follow-up release PR.

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.
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

AI Code Review

Now I have enough context for a complete review.


Code Review

Bugs and Logic Errors

[HIGH] purl_matches ignores the purl type/ecosystem

The function strips pkg:<type>/ but does not validate that the type matches the watchlist entry's ecosystem field. A pkg:pypi/openai@1.0.0 or pkg:cargo/ai@0.1.0 purl would match npm watchlist entries named openai / ai. CycloneDX BOMs produced by non-npm ecosystems will contain false positives, causing spurious Contradicted errors on mixed-language projects.

Fix: compare the extracted type against the entry's ecosystem (e.g., npmpkg:npm/):

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 pkg:pypi/openai@1.0.0 against the npm openai entry.


[MEDIUM] Unknown posture string emits a misleading Declared notice

In append_posture_binding_findings, agentic_posture_binding_inconsistencies correctly pushes an error for posture "maybe", but then adjudicate_agentic_posture short-circuits on binding.posture != "none" and returns Declared, appending "agentic posture: DECLARED (agency acknowledged)" as a notice. The result carries both an error and a reassuring notice for the same binding, which is contradictory. The adjudicate_agentic_posture caller should skip the cross-check when internal consistency already failed (i.e., unknown posture string).


[LOW] load_agentic_sdk_watchlist panics at runtime, not compile time

The expect in load_agentic_sdk_watchlist fires at runtime during the first BOM cross-check. The comment calls it a "build-time invariant" but include_str! only bakes the bytes in; schema validity is tested, not guaranteed by the compiler. The tests do exercise this path (which is sufficient gating), but a corrupted workspace artefact would produce an uncaught panic in the verifier rather than an exit-2 I/O error consistent with the rest of the error surface.


[LOW] _comment field implicitly tolerated

AgenticSdkWatchlist derives Deserialize with default serde behaviour (unknown fields ignored). The _comment key in the committed JSON works today but is invisible contract: a future schema version adding deny_unknown_fields would silently break it. No field for it, no #[serde(deny_unknown_fields)] either way, but the asymmetry should be documented or the comment moved out of band.


Security

[LOW] Recursive walk in first_watchlist_match_in_bom has no depth limit

The inner walk recurses on components[].components without bound. A maliciously crafted BOM (--sbom-dir takes user-supplied paths) with a pathologically deep nesting could overflow the stack. In practice CycloneDX nesting is shallow, but for a tool that explicitly processes untrusted build artefacts a bounded walk (e.g., max depth 32) is safer.


Spec-Spine Compliance

Spec 210 is an OAP reference, not a tenant-tail spec — that is fine; the code is correctly claimed by specs/001-certificate-verify-core/spec.md, and the spec is updated in this PR. The derived artefacts (.derived/) are regenerated and committed. No coupling-gate violation.

The new data/agentic-sdk-watchlist.json file is correctly added to establishes: in the spec and reflected in both .derived/ shards. ✓


Performance

load_agentic_sdk_watchlist() re-parses the embedded JSON on every call

first_watchlist_match_in_bom calls load_agentic_sdk_watchlist() each invocation, re-deserialising the embedded 1 KB JSON. For a one-shot CLI this is negligible, but a std::sync::OnceLock<AgenticSdkWatchlist> static would make the intent ("static constant data") explicit without any meaningful cost change.

validate_governance_envelope_shape clones potentially large JSON values

serde_json::from_value::<GovernanceEnvelopeShape>(env.clone()) — the clone allocates for every governed surface's envelope. If a governance envelope carries large nested data (e.g., deep constituents), this doubles peak memory briefly. The straightforward fix is serde_json::from_str(&env.to_string()) or restructuring the API to take bytes; for typical envelopes it's immaterial.


Summary

The 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.

@bartekus
bartekus merged commit d51ff80 into main Jul 8, 2026
9 checks passed
@bartekus
bartekus deleted the feat/210-agentic-posture-verify branch July 8, 2026 23:12
bartekus added a commit that referenced this pull request Jul 9, 2026
…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)
bartekus added a commit that referenced this pull request Jul 9, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant