Skip to content

feat(audit)!: add ApplyAudit records and v0.2.0 schema migration#7

Closed
BunsDev wants to merge 6 commits into
mainfrom
cody/apply-audit-v014
Closed

feat(audit)!: add ApplyAudit records and v0.2.0 schema migration#7
BunsDev wants to merge 6 commits into
mainfrom
cody/apply-audit-v014

Conversation

@BunsDev

@BunsDev BunsDev commented Jul 18, 2026

Copy link
Copy Markdown
Member

Summary

Adds daemon-owned Gate-4 applied-write audit records and releases the public API as v0.2.0 because AuditEventType::ApplyAudit and WardAuditRecord::detail are breaking changes for exhaustive downstream Rust code.

Audit contract

  • Adds ApplyAudit, WardAuditRecord::for_apply(...), and stable detail decoding helpers.
  • Stores next_sha256 in diff_hash; stores prev_sha256 and bytes_written in detail JSON.
  • Adds WARD_AUDIT_SCHEMA_STATE_SQL with fail-closed states: missing, legacy_v013, current_v020, unknown.
  • Fingerprints exact main.ward_audit table/index/trigger SQL and column metadata; rejects partial schemas, constraint drift, reserved durable objects, and temporary shadows.
  • Makes schema initialization and v0.1.3 → v0.2.0 migration atomic under BEGIN IMMEDIATE, with exact pre/postcondition guards and explicit rollback requirements.
  • Preserves unrelated database-wide user_version; Ward migration state is table-local.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace (145 tests)
  • Fresh-context spec review: approved
  • Fresh-context internal quality review: approved with daemon integration gate

Merge gate

Keep this PR draft. Do not merge/release v0.2.0 until the coven daemon adopts WARD_AUDIT_SCHEMA_STATE_SQL and dispatches WARD_AUDIT_MIGRATION_V020_SQL for legacy_v013. The current daemon still calls only WARD_AUDIT_SCHEMA_SQL; coordinated integration is tracked by threads-uqx.6.

Closes #5 after the daemon integration gate is satisfied.

BunsDev and others added 5 commits July 18, 2026 04:18
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s-k9s)

Cave implementation wraps route-1 data as { weaves, degraded } rather than
overloading a WeaveSummary[] with degraded entries — the wrapped shape avoids
any fabricated health fields on degraded familiars. Same-day alignment of the
2026-07-18 §2.7/R12 amendment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All 8 open questions resolved on Sage's recommended defaults:
- ApprovalPath separate from Channel (load axis preserved)
- Delayed apply only for veto windows (fail-closed posture)
- Classify-first for harness regions
- Deterministic + probe for invariants (not LLM-judge-only)
- RFC #3/#4 blocker resolved (familiar-contract@2a5bb98)
- Daemon contract before Cave ProposalView extension
- Display labels preserved, typed variants internal
- proposal_window_opened audit event lands with delayed apply

Rename PHASE-5-PROPOSAL -> PHASE-5-APPROVAL-SEMANTICS to reflect open status.

Co-authored-by: Nova <nova@opencoven.dev>
…uqx)

Add substantive annotations from Sage and Echo review (2026-07-18):

Decision #1: rationale callback — ApprovalPath is authoritative, Channel is
derived; if enforcement rule shifts, revisit all eight decisions.

Decision #2: audit implication — close event needs reason field
(applied|vetoed|expired|superseded); delayed-apply without it is a post-hoc
audit black hole.

Decision #3: forward-only promotion — retroactive region projection corrupts
authority trail with apparently-authored writes from before promotion.

Decision #4: fail-closed on extraction ambiguity — not silent LLM fallback;
ambiguity is an explicit ignored/blocked state.

Decision #5: RFC provenance pointer added.

Decision #6: Cave ProposalView labeled [DESIGNED, NOT SHIPPED] from the start.

Decision #7: label-variant round-trip is daemon wire contract (load-time reject
on drift); Cave has zero policy freedom over label strings.

Decision #8: proposal_window_opened paired with close event; window is a
first-class audit interval.

threads-uqx.3 design constraints (Sage): evidence_replay_hash on
ProposalClassification; VetoWindow.min_visible: Duration; label mapping as
daemon wire contract.

Co-authored-by: Sage <sage@opencoven.dev>
Co-authored-by: Echo <echo@opencoven.dev>
Co-authored-by: Nova <nova@opencoven.dev>
…(v0.1.4)

Closes coven-threads#5. Unblocks coven#414.

Changes:
- AuditEventType::ApplyAudit variant + apply_audit CHECK tag
- WardAuditRecord.detail: nullable JSON column for event-specific payload
- WardAuditRecord::for_apply() constructor
- apply_prev_sha256_hex() / apply_bytes_written() decode helpers
- WARD_AUDIT_MIGRATION_V014_SQL: transaction rebuild for v0.1.3 stores
- APPLY_AUDIT_DETAIL_KEY_PREV / _BYTES stable constants
- Exhaustiveness test extended; 3 new tests (shape, roundtrip, migration)
- Workspace version bumped 0.1.3 -> 0.1.4
- CHANGELOG.md created

Design: prev_sha256+bytes_written ride in detail JSON (Option A);
migration exports SQL constant, no rusqlite dep added (Option B-lite).

Bead: threads-3ym
Copilot AI review requested due to automatic review settings July 18, 2026 21:24
Addresses two Sage review notes (neither was blocking):

1. WARD_AUDIT_MIGRATION_V014_SQL now stamps PRAGMA user_version = 14
   inside the transaction. Future migrations gate on 'user_version < N'
   instead of substring-sniffing sqlite_master DDL. Migration doc-comment
   updated to name the gate rule and explain the intentional non-idempotence
   of the CREATE step (double-run fails loudly, not silently).

2. WardAuditRecord::detail doc-comment now names the full invariant for
   ApplyAudit rows (prev_sha256 hex 64 chars + bytes_written u64) and
   explicitly states other event types MAY leave it None or use their
   own documented shape. Turns the implicit decoder contract into a
   searchable spec.

CHANGELOG updated. Tests still green, clippy clean.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends coven-threads-core’s audit surface so the coven daemon can persist Gate-4 applied-write records into the existing ward_audit append-only store, including a guarded SQLite rebuild migration for stores created before the new event_type CHECK tag existed.

Changes:

  • Adds AuditEventType::ApplyAudit plus WardAuditRecord.detail (JSON payload) and a WardAuditRecord::for_apply(...) constructor + decode helpers.
  • Introduces WARD_AUDIT_MIGRATION_V014_SQL to rebuild ward_audit in a transaction for v0.1.3-era schemas (SQLite CHECK constraint limitation).
  • Adds/updates specs and release documentation (new Phase-5 proposal doc, Phase-4 API return shape tweak, new CHANGELOG.md, workspace version bump).

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
specs/PHASE-5-APPROVAL-SEMANTICS.md Adds Phase-5 approval-semantics proposal/decision record text (includes audit-event implications).
specs/PHASE-4-CAVE-SURFACES.md Updates /api/weaves return type documentation to WeaveListView (includes degraded list).
crates/coven-threads-core/src/audit.rs Implements ApplyAudit, detail JSON, constructor/helpers, schema CHECK update, and v0.1.4 migration SQL + tests.
CHANGELOG.md Introduces changelog entries for v0.1.4 changes.
Cargo.toml Bumps workspace package version to 0.1.4 (crate inherits via version.workspace = true).
.beads/interactions.jsonl Records beads tracker interactions relevant to recent work.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +233 to +238
let prev_hex = prev_hash.map(bytes_to_hex);
let detail = serde_json::json!({
APPLY_AUDIT_DETAIL_KEY_PREV: prev_hex.unwrap_or_default(),
APPLY_AUDIT_DETAIL_KEY_BYTES: bytes_written,
})
.to_string();
Comment thread crates/coven-threads-core/src/audit.rs Outdated
Comment on lines +314 to +318
/// returns a string that does NOT contain the literal `'apply_audit'` — i.e.
/// the store was created against v0.1.3. Idempotent if re-run on a v0.1.4
/// store because `ward_audit_new` won't contain `apply_audit` in that schema.
/// The daemon should gate this migration on the presence/absence of the tag
/// in `sqlite_master`.
Comment on lines +25 to +26
probes. RFC amendments for closure precondition and provenance predicate are
pending on issues #3/#4 and are treated as upstream-in-flight.
Comment on lines +14 to +15
This is a proposal document only. It does not start Phase 5, amend frozen
Phase 0, or change the daemon. Val and Nova decide whether Phase 5 opens.
@BunsDev

BunsDev commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

Superseded by #15, which contains this audit work in its ancestry and replaces the database-global PRAGMA user_version approach with component-scoped schema metadata, preserves existing detail payloads, and adds the reviewed Phase 5 lifecycle/provenance constraints.

@BunsDev BunsDev closed this Jul 20, 2026
@BunsDev BunsDev changed the title feat(audit): add ApplyAudit event type, detail column, migration SQL (v0.1.4) feat(audit)!: add ApplyAudit records and v0.2.0 schema migration Jul 20, 2026
@BunsDev

BunsDev commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

Repair review completed at 31ae385. Root cause was migration SQL that could erase immutable ApplyAudit detail when invoked against a current schema. The branch now uses exact main-schema fingerprints, atomic fail-closed init/migration guards, temp/durable namespace rejection, rollback/concurrency regressions, and a semver-correct v0.2.0 API. All workspace tests and clippy pass. Merge remains blocked on threads-uqx.6 updating the coven daemon to classify and migrate legacy stores before normal schema initialization.

@BunsDev

BunsDev commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

Archive note: this closed PR remains superseded by PR #15, with daemon integration in OpenCoven/coven#430. The pushed branch tip 31ae385 is preserved as a reviewed migration-hardening investigation; do not reopen or merge it. Its findings were reconciled against the active Phase 5 line, which already uses component-owned schema metadata and executable migration/lifecycle coverage.

@BunsDev BunsDev mentioned this pull request Jul 21, 2026
BunsDev added a commit that referenced this pull request Jul 21, 2026
* spec(proposal): draft Phase 5 approval semantics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* spec(phase-4): align route-1 Returns with WeaveListView shape (threads-k9s)

Cave implementation wraps route-1 data as { weaves, degraded } rather than
overloading a WeaveSummary[] with degraded entries — the wrapped shape avoids
any fabricated health fields on degraded familiars. Same-day alignment of the
2026-07-18 §2.7/R12 amendment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* spec(phase-5): record Val+Nova decisions, open Phase 5 (threads-uqx)

All 8 open questions resolved on Sage's recommended defaults:
- ApprovalPath separate from Channel (load axis preserved)
- Delayed apply only for veto windows (fail-closed posture)
- Classify-first for harness regions
- Deterministic + probe for invariants (not LLM-judge-only)
- RFC #3/#4 blocker resolved (familiar-contract@2a5bb98)
- Daemon contract before Cave ProposalView extension
- Display labels preserved, typed variants internal
- proposal_window_opened audit event lands with delayed apply

Rename PHASE-5-PROPOSAL -> PHASE-5-APPROVAL-SEMANTICS to reflect open status.

Co-authored-by: Nova <nova@opencoven.dev>

* spec(phase-5): fold Sage + Echo review into decision record (threads-uqx)

Add substantive annotations from Sage and Echo review (2026-07-18):

Decision #1: rationale callback — ApprovalPath is authoritative, Channel is
derived; if enforcement rule shifts, revisit all eight decisions.

Decision #2: audit implication — close event needs reason field
(applied|vetoed|expired|superseded); delayed-apply without it is a post-hoc
audit black hole.

Decision #3: forward-only promotion — retroactive region projection corrupts
authority trail with apparently-authored writes from before promotion.

Decision #4: fail-closed on extraction ambiguity — not silent LLM fallback;
ambiguity is an explicit ignored/blocked state.

Decision #5: RFC provenance pointer added.

Decision #6: Cave ProposalView labeled [DESIGNED, NOT SHIPPED] from the start.

Decision #7: label-variant round-trip is daemon wire contract (load-time reject
on drift); Cave has zero policy freedom over label strings.

Decision #8: proposal_window_opened paired with close event; window is a
first-class audit interval.

threads-uqx.3 design constraints (Sage): evidence_replay_hash on
ProposalClassification; VetoWindow.min_visible: Duration; label mapping as
daemon wire contract.

Co-authored-by: Sage <sage@opencoven.dev>
Co-authored-by: Echo <echo@opencoven.dev>
Co-authored-by: Nova <nova@opencoven.dev>

* feat(audit): add ApplyAudit event type, detail column, migration SQL (v0.1.4)

Closes coven-threads#5. Unblocks coven#414.

Changes:
- AuditEventType::ApplyAudit variant + apply_audit CHECK tag
- WardAuditRecord.detail: nullable JSON column for event-specific payload
- WardAuditRecord::for_apply() constructor
- apply_prev_sha256_hex() / apply_bytes_written() decode helpers
- WARD_AUDIT_MIGRATION_V014_SQL: transaction rebuild for v0.1.3 stores
- APPLY_AUDIT_DETAIL_KEY_PREV / _BYTES stable constants
- Exhaustiveness test extended; 3 new tests (shape, roundtrip, migration)
- Workspace version bumped 0.1.3 -> 0.1.4
- CHANGELOG.md created

Design: prev_sha256+bytes_written ride in detail JSON (Option A);
migration exports SQL constant, no rusqlite dep added (Option B-lite).

Bead: threads-3ym

* review(audit): add user_version ladder + explicit detail invariant doc

Addresses two Sage review notes (neither was blocking):

1. WARD_AUDIT_MIGRATION_V014_SQL now stamps PRAGMA user_version = 14
   inside the transaction. Future migrations gate on 'user_version < N'
   instead of substring-sniffing sqlite_master DDL. Migration doc-comment
   updated to name the gate rule and explain the intentional non-idempotence
   of the CREATE step (double-run fails loudly, not silently).

2. WardAuditRecord::detail doc-comment now names the full invariant for
   ApplyAudit rows (prev_sha256 hex 64 chars + bytes_written u64) and
   explicitly states other event types MAY leave it None or use their
   own documented shape. Turns the implicit decoder contract into a
   searchable spec.

CHANGELOG updated. Tests still green, clippy clean.

* feat(phase-5): core approval types — ApprovalPath, VetoWindow, ProposalClassification (threads-uqx.3)

Adds crates/coven-threads-core/src/approval.rs with the Phase-5 core type
sketch from specs/PHASE-5-APPROVAL-SEMANTICS.md §3, decisions 1/2/7/8.

Types introduced:
- ApprovalPath (AutoRegression | FamiliarCoherence | HumanApproval |
  HumanApprovalWithRationale) — promotion ceremony, separate from Channel
- ApprovalPathKind — variant without inner fields for label round-trip
- VetoWindow — delayed-apply only; min_visible + duration; deadline helpers
- ProposalClassification — channel, regions, tier floor, approval_path,
  evidence_replay_hash (WARD-C7 generalised)
- SurfaceRegionId — opaque region ref, full predicate in uqx.5
- WindowCloseReason — applied | vetoed | expired | superseded (decision 2+8)
- ProposalWindowAuditDetail / ProposalWindowCloseAuditDetail — detail payloads
  for proposal_window_opened and close events
- ApprovalPathWireEnvelope — daemon wire format {variant, label, veto_deadline}

Key constraints enforced:
- ApprovalPath != Channel (decision 1); highest ceremony wins
- VetoWindow is delayed-apply only; min_visible <= duration (decision 2)
- display_label() + from_display_label() round-trip is exhaustive; unknown
  labels return None so daemon rejects at load (decision 7)
- evidence_replay_hash on ProposalClassification (WARD-C7)

Tests: 15 passing (all approval tests green; full suite 90 passing)

Closes #8

* feat(phase-5): identity invariant predicates + advisory probes (threads-uqx.4)

Adds crates/coven-threads-core/src/identity_invariants.rs.

Types / implementations:
- FamiliarNameInvariant: PatternPredicate over Blake3 content hash of an
  identity surface (e.g. IDENTITY.md). Fails closed on mismatch, snapped
  thread, or missing thread (decision 4: ambiguity → reject, not fallback).
- ManifestAnchoredInvariant: PatternPredicate over manifest entry hash;
  holds under Forced channel (WARD-C1..C6: must survive compaction).
- CompositeIdentityInvariant: all-components-must-hold composite predicate;
  identity invariant is a unit — no partial pass.
- AdvisoryProbeResult: Gate-3 supplementary evidence shape; is_authoritative
  always false; confidence validated in [0.0, 1.0].
- AdvisoryProbes: the advisory_probes block (Q4 decision) — separate from
  deterministic probes block; empty means 'no advisory signals ran'.

Key constraints enforced:
- Predicate-first; no new strand kind (decision 3/4)
- Fail-closed on ambiguity (decision 4)
- advisory_probes structurally separate from probes; never sole authority
- descriptor is derived, never enforced on (§2.3)

Tests: 16 new (identity_invariants::tests), all passing.
Full suite: 106 tests, 0 failures.

Closes #9

* feat(phase-5): SurfaceRegionPredicate + Gate-4 replay (threads-uqx.5)

Adds crates/coven-threads-core/src/surface_regions.rs.

Types introduced:
- MaterializedDiff / SurfaceDiff: pure diff input; predicates are pure
  functions of this (Gate-4 replay constraint — no Cave state, no stale
  metadata, no agent self-report)
- RegionEvidence: predicate output; feeds ProposalClassification.affected_regions
  and evidence_replay_hash; includes replay_bytes for Gate-4 deadline check
- SurfaceRegionDescriptor: derived, Cave-renderable, never enforced on
- SurfaceRegionPredicate trait: materialize (authoritative) + describe (derived)
- ExecutionPromptRegion: SOUL.md / AGENTS.md, tier-0 floor, human review
- ToolDefaultsRegion: TOOLS.md, tier-1 floor, familiar review
- HeartbeatBehaviorRegion: HEARTBEAT.md, tier-1 floor
- SurfaceRegionRegistry: classify_all + descriptors + path_tier_floor helper

Key constraints enforced:
- Classify-first; threads stay source-bound (decision 3)
- materialize is pure over MaterializedDiff — daemon-replayable at Gate-4
- replay_bytes are deterministic (sorted surface order)
- path_tier_floor = min across evidence (most protective wins)
- SurfaceRegionDescriptor never enforced on

Tests: 12 new (surface_regions::tests), all passing.
Full suite: 118 tests, 0 failures.

Closes #10

* spec(phase-5): resolve coherence review findings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(phase-5): harden authority-boundary core

Bind semantic identity evidence into Gate-4 validation, commit replay to the complete materialized diff, validate wire contracts, and make proposal audit lifecycle persistence fail closed while preserving legacy history.

Bump the breaking public API to 0.2.0 and cover the retired invariant corpus, delayed approval semantics, schema migration paths, and append-only terminal behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document Phase 5 and harden approval contracts

Updates docs and status pages to reflect Phase 4 freeze and active Phase 5 approval semantics, including a new delayed-apply scheduler diagram and expanded architecture/FAQ/glossary guidance.

In `coven-threads-core`, tightens fail-closed behavior by requiring the `auto_regression.veto` wire key, preserving stronger veto windows across path promotion, validating `ApplyAudit` detail shape and event-type accessors, enforcing advisory probe validation at deserialization, adding `#[must_use]` on key APIs, and expanding tests around these contracts.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Nova <nova@opencoven.dev>
Co-authored-by: Sage <sage@opencoven.dev>
Co-authored-by: Echo <echo@opencoven.dev>
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.

ward_audit: add an apply-audit event type so the coven daemon can persist Gate-4 apply records

2 participants