Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions crates/ourios-core/src/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ use crate::tenant::TenantId;
/// compiler enforces those per-variant contracts.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TemplateChange {
/// A new leaf was allocated — the template's initial (version 1)
/// creation (RFC 0017 §3.1). Audited so a read-time template
/// registry can recover the v1 tokens once the originating rows
/// age out; without it, v1 rows would have no derivable tokens.
///
/// Carries **only** the initial tokens. A leaf is *always* born at
/// version 1, so the variant deliberately omits a `new_version` field:
/// the v1 invariant is made unrepresentable rather than carried-and-
/// validated (there is no way to construct a creation at any other
/// version). The on-disk row still stores `new_version = 1` (the
/// writer supplies it) with `old_version` / `old_template` left `NULL`
/// — the RFC 0005 §3.7 "not applicable" sentinel (no prior template).
Created {
/// Canonical-form template at creation (the initial tokens,
/// literals + `<*>`), the same encoding `Widened` carries.
new_template: String,
},
/// An existing template gained one or more wildcard slots
/// because a clean attach would otherwise mismatch positions
/// (RFC §6.2 step 5).
Expand Down Expand Up @@ -221,6 +238,10 @@ pub const EVENT_KIND_COMPACTION: u8 = 3;
pub const EVENT_KIND_ALIAS_ASSERTED: u8 = 4;
/// See [`EVENT_KIND_TEMPLATE_WIDENED`]. RFC 0001 §6.7 alias write path.
pub const EVENT_KIND_ALIAS_RETRACTED: u8 = 5;
/// See [`EVENT_KIND_TEMPLATE_WIDENED`]. RFC 0017 §3.1 leaf-creation audit —
/// an **append-only** addition (next free ordinal, no renumber): old readers
/// surface it via the [`AuditPayload::Unknown`] tolerance path (RFC 0005 §3.7).
pub const EVENT_KIND_TEMPLATE_CREATED: u8 = 6;

/// Canonical `event_type` strings paired with the ordinals above
/// (RFC 0005 §3.7 / RFC 0001 §6.4 / RFC 0009 §3.6).
Expand All @@ -236,6 +257,14 @@ pub const EVENT_TYPE_COMPACTION: &str = "compaction";
pub const EVENT_TYPE_ALIAS_ASSERTED: &str = "alias_asserted";
/// See [`EVENT_TYPE_TEMPLATE_WIDENED`]. RFC 0001 §6.7 alias write path.
pub const EVENT_TYPE_ALIAS_RETRACTED: &str = "alias_retracted";
/// See [`EVENT_TYPE_TEMPLATE_WIDENED`]. RFC 0017 §3.1 leaf-creation audit.
pub const EVENT_TYPE_TEMPLATE_CREATED: &str = "template_created";

/// The `template_version` a leaf is born at (RFC 0017 §3.1). The
/// [`TemplateChange::Created`] variant omits a version field — the invariant
/// is unrepresentable — so the writer supplies this for the on-disk
/// `new_version` column and the read-time registry keys creation rows by it.
pub const TEMPLATE_INITIAL_VERSION: u32 = 1;

impl AuditPayload {
/// The stable `event_kind` ordinal for this payload (RFC 0005
Expand All @@ -245,6 +274,7 @@ impl AuditPayload {
pub fn event_kind(&self) -> u8 {
match self {
Self::Template { change, .. } => match change {
TemplateChange::Created { .. } => EVENT_KIND_TEMPLATE_CREATED,
TemplateChange::Widened { .. } => EVENT_KIND_TEMPLATE_WIDENED,
TemplateChange::TypeExpanded { .. } => EVENT_KIND_TEMPLATE_TYPE_EXPANDED,
TemplateChange::RejectedDegenerate { .. } => {
Expand All @@ -266,6 +296,7 @@ impl AuditPayload {
pub fn event_type(&self) -> &str {
match self {
Self::Template { change, .. } => match change {
TemplateChange::Created { .. } => EVENT_TYPE_TEMPLATE_CREATED,
TemplateChange::Widened { .. } => EVENT_TYPE_TEMPLATE_WIDENED,
TemplateChange::TypeExpanded { .. } => EVENT_TYPE_TEMPLATE_TYPE_EXPANDED,
TemplateChange::RejectedDegenerate { .. } => {
Expand Down Expand Up @@ -314,6 +345,7 @@ impl TemplateChange {
#[must_use]
pub fn event_type(&self) -> &'static str {
match self {
Self::Created { .. } => EVENT_TYPE_TEMPLATE_CREATED,
Self::Widened { .. } => EVENT_TYPE_TEMPLATE_WIDENED,
Self::TypeExpanded { .. } => EVENT_TYPE_TEMPLATE_TYPE_EXPANDED,
Self::RejectedDegenerate { .. } => EVENT_TYPE_TEMPLATE_WIDENING_REJECTED_DEGENERATE,
Expand Down
80 changes: 65 additions & 15 deletions crates/ourios-core/tests/rfc0017_audit.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,75 @@
//! RFC 0017 — read-time template registry & query-row rendering, the
//! audit-schema arm of scenario `.1`.
//!
//! **Status: `red`.** Failing stub driving the `green` implementation: it
//! encodes the audit-contract half of RFC 0017 §5 scenario .1 (the new
//! `template_created` `event_kind`/`event_type` is an append-only addition —
//! ordinal `6`, no existing ordinal renumbered) and currently `todo!()`s. It
//! is `#[ignore]`d so the default `cargo test` (and CI) stays green until the
//! `green` slice lands `TemplateChange::Created`; `green` replaces the body
//! with the real assertions and removes the `#[ignore]`.
//! Asserts the `template_created` audit event is an **append-only** addition:
//! a new `event_kind` ordinal `6` paired with the `event_type` string
//! `template_created`, with every existing ordinal (`0`–`5`) unchanged
//! (RFC 0005 §3.7), and that a `Created` payload derives the new
//! kind/type and does not count as a merge.
//!
//! See `docs/rfcs/0017-template-registry-query-rendering.md` §3.1 / §5 / §6.

/// Scenario RFC0017.1 (audit-schema arm) — the `template_created` event is an
/// append-only audit addition: a new `event_kind` ordinal `6` paired with the
/// `event_type` string `template_created`, with every existing ordinal (`0`–`5`)
/// left unchanged (RFC 0005 §3.7 append-only rule).
use std::time::SystemTime;

use ourios_core::audit::{
AuditEvent, AuditPayload, EVENT_KIND_ALIAS_ASSERTED, EVENT_KIND_ALIAS_RETRACTED,
EVENT_KIND_COMPACTION, EVENT_KIND_TEMPLATE_CREATED, EVENT_KIND_TEMPLATE_TYPE_EXPANDED,
EVENT_KIND_TEMPLATE_WIDENED, EVENT_KIND_TEMPLATE_WIDENING_REJECTED_DEGENERATE,
EVENT_TYPE_TEMPLATE_CREATED, TemplateChange, hash_triggering_line,
};
use ourios_core::tenant::TenantId;

/// Scenario RFC0017.1 (audit-schema arm) — `template_created` is an
/// append-only audit addition: ordinal `6` / `event_type = "template_created"`,
/// existing ordinals `0`–`5` unchanged (RFC 0005 §3.7).
/// See `docs/rfcs/0017-template-registry-query-rendering.md` §5.
#[test]
#[ignore = "RFC0017.1 — red until TemplateChange::Created + event_kind 6 land (green)"]
fn rfc0017_1_template_created_is_append_only_audit_addition() {
todo!(
"RFC0017.1: template_created = event_kind ordinal 6 / event_type \"template_created\", existing ordinals unchanged"
)
// The new ordinal is the next free value, and the existing ordinals
// are untouched (the RFC 0005 §3.7 append-only rule — no renumber, so
// old readers are unaffected).
assert_eq!(EVENT_KIND_TEMPLATE_CREATED, 6);
assert_eq!(EVENT_TYPE_TEMPLATE_CREATED, "template_created");
assert_eq!(EVENT_KIND_TEMPLATE_WIDENED, 0);
assert_eq!(EVENT_KIND_TEMPLATE_TYPE_EXPANDED, 1);
assert_eq!(EVENT_KIND_TEMPLATE_WIDENING_REJECTED_DEGENERATE, 2);
assert_eq!(EVENT_KIND_COMPACTION, 3);
assert_eq!(EVENT_KIND_ALIAS_ASSERTED, 4);
assert_eq!(EVENT_KIND_ALIAS_RETRACTED, 5);

// All seven ordinals are distinct — no collision with the new one.
let mut ordinals = [
EVENT_KIND_TEMPLATE_WIDENED,
EVENT_KIND_TEMPLATE_TYPE_EXPANDED,
EVENT_KIND_TEMPLATE_WIDENING_REJECTED_DEGENERATE,
EVENT_KIND_COMPACTION,
EVENT_KIND_ALIAS_ASSERTED,
EVENT_KIND_ALIAS_RETRACTED,
EVENT_KIND_TEMPLATE_CREATED,
];
let count = ordinals.len();
ordinals.sort_unstable();
let mut deduped = ordinals.to_vec();
deduped.dedup();
assert_eq!(deduped.len(), count, "event_kind ordinals must be distinct");

// A `Created` payload derives the new kind/type and is not a merge.
let event = AuditEvent {
tenant_id: TenantId::new("tenant-x"),
timestamp: SystemTime::UNIX_EPOCH,
payload: AuditPayload::Template {
template_id: 7,
triggering_line_hash: hash_triggering_line(b"user 42 logged in"),
triggering_line_sample: Some("user 42 logged in".to_owned()),
change: TemplateChange::Created {
new_template: "user <*> logged in".to_owned(),
},
},
};
assert_eq!(event.payload.event_kind(), EVENT_KIND_TEMPLATE_CREATED);
assert_eq!(event.payload.event_type(), EVENT_TYPE_TEMPLATE_CREATED);
assert!(
!event.payload.counts_as_merge(),
"leaf creation is not a merge",
);
}
Loading
Loading