diff --git a/CHANGELOG.md b/CHANGELOG.md index cf993a17..442227cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **The OCSF audit plugin refuses `signing: dsse` with `chain: false` instead of emitting unsigned records.** The signature covers the chained record, so with the chain off the chain wrapper returned before the signer ever ran and a config that promised signatures produced none. `integrations/cpex-ocsf-audit` now fails construction for the pair with an error naming the fix, pinned by a new test. The second round adds `#[serde(deny_unknown_fields)]` on the config, since every field has a default and a misspelled `authority_uid` was silently dropped (a test pins the rejection), and the canonicalizer now writes object keys without cloning them (37 tests on each host, vectors byte-identical). Found by the first review round on praxis-proxy/policy #171; the same guard is staged for the in-tree copy in `praxis-port/review/0003-review-171.patch`, with the `read_subject` capability for `panic_drive` (PPE only) and a reworded key-ordering note in SAMPLE-OUTPUT.md, which this crate also takes. `praxis-port/review/0004-review-173.patch` carries the two accepted findings on the #166 spike document (praxis #173): a stream gap is loss evidence rather than a verification failure, and digests of low-entropy content are keyed where the host provides a key; `0005-review-173-tail.patch` answers the second round: a dense sequence from 0 shows no leading or interior gap but cannot show a trailing loss, so tail completeness needs a trusted terminal sequence or checkpoint from the host. (#591) ### Changed +- **The AID-EMIT-1 validator reads stream stamps from `aid.stream` as well as `cpex.stream`.** Emitters that target the AI Identity platform and OCSF rather than a gateway write stream stamps under AI Identity's own vocabulary, `unmapped."aid.stream"`, proposed for AID-EMIT-1 as a 1.2.0 vocabulary profile; the CPEX host family keeps `unmapped."cpex.stream"`. `scripts/aid_emit1_validator.py` now looks up `aid.stream` first, then `cpex.stream`, so the density, head and ordering checks (`GAP-STREAM`, `W-STREAM-HEAD`, `E-STREAM-ORDER`) cover both. A record carrying both keys is read from `aid.stream` and warned as `W-STREAM-VOCAB`, since two sets of stamps let a SIEM and a verifier read different sequences. Records in the `cpex` vocabulary validate exactly as before. The validator's tests run against its synced copy in the platform repository. - **The integrations page and CPEX plugin README link the upstream OCSF audit Epic.** They identify praxis-proxy/policy Epic #12 as tracking the Praxis Policy Engine reference-plugin work, and keep the status clear: the PPE port is prepared as patches in this repository, not described as merged upstream. (#599) - **`capture-modes.md` is marked implemented, and records what the build decided where the spec was open.** Every item of section 10 is now built on the platform (AI-66). Section 7 records how redaction works: - an item's `redact_after` comes from the rule matching the row that carries its digest, so responses can be redacted on a different clock from requests; diff --git a/scripts/aid_emit1_validator.py b/scripts/aid_emit1_validator.py index c0076db1..a8150cff 100644 --- a/scripts/aid_emit1_validator.py +++ b/scripts/aid_emit1_validator.py @@ -33,7 +33,10 @@ must be dense per `(epoch, stream_id)` and start at 0 (a higher first value is warned as `W-STREAM-HEAD`) — a gap is **surfaced as a finding, never repaired** (warning by default, error with - ``--strict-gaps``); a decrease is an ordering error. + ``--strict-gaps``); a decrease is an ordering error. Stream stamps are + read from `unmapped."aid.stream"` (AI Identity's vocabulary), else + `unmapped."cpex.stream"` (the CPEX host family's); a record carrying + both is warned as `W-STREAM-VOCAB`. Non-6003 records (e.g. proposed-class drafts using the same `record_integrity` carrier) get the integrity/chain checks with the @@ -78,6 +81,12 @@ DSSE_PAYLOAD_TYPE = b"application/vnd.ocsf.event+json" HEX64 = re.compile(r"^[0-9a-f]{64}$") +# Where a record's stream stamps live, by vocabulary, in lookup order: +# "aid.stream" is AI Identity's own (emitters that target the platform and +# OCSF rather than a gateway), "cpex.stream" is the CPEX host family's. The +# member structure is identical; only the key differs. +STREAM_STAMP_KEYS = ("aid.stream", "cpex.stream") + # --------------------------------------------------------------------------- # Canonicalization (§4) # --------------------------------------------------------------------------- @@ -496,7 +505,17 @@ def check_chains( ) heads[chain] = (uid, fp) - stream = (ev.get("unmapped") or {}).get("cpex.stream") + unmapped = ev.get("unmapped") + unmapped = unmapped if isinstance(unmapped, dict) else {} + stamp_keys = [k for k in STREAM_STAMP_KEYS if isinstance(unmapped.get(k), dict)] + if len(stamp_keys) > 1: + out.warn( + "W-STREAM-VOCAB", + idx, + f"stream stamps in both {' and '.join(stamp_keys)}; read from {stamp_keys[0]}: " + "a conforming emitter uses one vocabulary", + ) + stream = unmapped[stamp_keys[0]] if stamp_keys else None if isinstance(stream, dict) and stream.get("stream_seq") is not None: key = (chain, stream.get("epoch"), stream.get("stream_id")) streams.setdefault(key, []).append((stream["stream_seq"], idx))