Skip to content

55: normalize reproducibility hash recipe across audit writers - #84

Merged
wjduenow merged 2 commits into
devfrom
feat/55-normalize-hash-recipe
May 12, 2026
Merged

wjduenow merged 2 commits into
devfrom
feat/55-normalize-hash-recipe

Conversation

@wjduenow

@wjduenow wjduenow commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Migrate prune.audit._compute_config_hash and safety.policy._compute_policy_hash from SHA-256[:16] to blake2b(digest_size=8) so every reproducibility hash in the audit / sidecar corpus reads one recipe.
  • Bump PruneEvent.audit_schema_version 1 → 2 and safety AuditEvent.audit_schema_version 2 → 3 in lockstep; fields stay int so older JSONLs still round-trip.
  • Refresh drift detectors, committed fixtures, ops docs (docs/safety-ops.md, docs/prune-ops.md), and rule files (.claude/rules/safety-layer.md, .claude/rules/prune-engine.md) + CLAUDE.md public-surface notes in lockstep.

Closes #55.

Why

Pre-#55 the audit corpus used two hash families: blake2b-8 (draft, grade, diff, prune's compiled_sql_hash) and SHA-256[:16] (prune config_hash, safety policy_hash). A reviewer correlating across safety.jsonl / llm_responses.jsonl / prune.jsonl / grade.jsonl / diff.json needed to know which family applied where. One recipe is the explainability surface Architectural Commitment #5 asks for.

Test plan

  • ruff check . && ruff format --check . && pyright clean
  • pytest — 1805 pass, 6 pre-existing WSL2 symlink-loop failures (pass on GHA), 96% coverage maintained
  • Drift detectors refreshed and re-validate against updated fixtures
  • Final grep -rn "sha256\|SHA-256" sweep — remaining references are historical-context notes in docstrings/rules + orthogonal dbt manifest fixtures
  • CI green on PR

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Updated audit event schemas: prune audit version bumped to 2, safety audit version bumped to 3
    • Standardized hash computation across audit systems to use blake2b (digest size 8) for reproducibility and normalization
    • Added migration guidance for consumers to gate on appropriate schema versions when processing legacy audit records
  • Chores

    • Updated test fixtures and test assertions to align with new audit schema versions

Review Change Stack

`prune.audit._compute_config_hash` and `safety.policy._compute_policy_hash`
were the last two outliers using `SHA-256[:16]` while every other
reproducibility hash in the audit / sidecar corpus (`draft`, `grade`,
`diff`, plus prune's `compiled_sql_hash`) already used
`blake2b(digest_size=8)` over canonical JSON. Migrating both to the same
recipe so a reviewer correlating `safety.jsonl` / `llm_responses.jsonl`
/ `prune.jsonl` / `grade.jsonl` / `diff.json` reads one family of digests.

`PruneEvent.audit_schema_version` bumped 1 → 2; safety `AuditEvent`
bumped 2 → 3. Fixtures + drift detectors + ops docs + rule files
refreshed in lockstep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (4)
  • feature/.*
  • bug/.*
  • hotfix/.*
  • feat/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd0c9259-8179-41e5-8eea-0d8abec68d7f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Standardizes reproducibility hash computation across prune and safety audit writers by migrating from SHA-256 truncation to unified blake2b(digest_size=8) over canonical JSON; bumps prune audit schema version 1→2 and safety audit schema version 2→3; updates implementation, documentation, tests, and fixtures to enforce the normalized recipe.

Changes

Audit hash recipe normalization (issue #55)

Layer / File(s) Summary
Prune config_hash migration and schema bump
src/signalforge/prune/audit.py, src/signalforge/prune/engine.py, CLAUDE.md, docs/prune-ops.md, .claude/rules/prune-engine.md
_compute_config_hash switches from sha256(...).hexdigest()[:16] to blake2b(..., digest_size=8).hexdigest(). _PRUNE_AUDIT_SCHEMA_VERSION bumped 1→2. PruneEvent.audit_schema_version field updated to Literal[2]. Public API and operational docs updated to reflect the new hash recipe and schema version.
Safety policy_hash migration and schema bump
src/signalforge/safety/policy.py, src/signalforge/safety/models.py, src/signalforge/safety/request.py, docs/safety-ops.md, .claude/rules/safety-layer.md
_compute_policy_hash switches from SHA-256 truncation to blake2b(..., digest_size=8).hexdigest(). _AUDIT_SCHEMA_VERSION bumped 2→3 in request module. AuditEvent.audit_schema_version default updated to 3 in models. Operational and rules documentation updated to describe the new hash computation and schema bump history.
Prune audit test and fixture updates
tests/prune/test_audit.py, tests/prune/test_drift_detector.py, tests/prune/test_engine.py, tests/fixtures/prune/prune_event_v1.jsonl, tests/fixtures/e2e_helpers/happy/.signalforge/prune.jsonl
Writer contract tests updated to assert audit_schema_version == 2. StrictPruneEvent drift detector mirror updated to Literal[2]. Snapshot and fixture assertions refreshed. JSONL fixtures bumped from v1 to v2 and include new signalforge_version field.
Safety audit test and fixture updates
tests/safety/test_audit.py, tests/safety/test_drift_detector.py, tests/safety/test_models.py, tests/safety/test_request.py, tests/fixtures/safety/audit_events_sample.jsonl, tests/fixtures/safety/regenerate.sh
Writer contract tests updated to assert audit_schema_version == 3. Forward-compatibility test added to verify legacy v2 events parse correctly. Test docstrings and fixture references updated for new schema version. JSONL fixtures bumped from v2 to v3 and regenerate script updated to emit v3 records.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • wjduenow/SignalForge#83: Adds draft-skip redaction reasons and bumps AuditEvent.audit_schema_version in safety models; this PR completes the safety schema versioning in the same codebase region.
  • wjduenow/SignalForge#20: Introduced the initial prune audit infrastructure and constants (_PRUNE_AUDIT_SCHEMA_VERSION, PruneEvent, _compute_config_hash); this PR migrates the hash recipe for those same entities.

Poem

🐰 A hash harmonization hop:
Blake2b dances cross the audit floor,
Where sha256 whispers fade to folklore,
Sixteen hex chars, digest_size divine—
Now prune and safety schemas align!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating hash recipes to blake2b across audit writers and bumping schema versions.
Linked Issues check ✅ Passed All requirements from issue #55 are met: blake2b migration in prune/audit.py, audit_schema_version bumps (prune 1→2, safety 2→3), drift detectors and fixtures refreshed, and hash consistency across writers achieved.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #55: hash recipe migrations, schema version bumps, test updates, and documentation refreshes. No unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@wjduenow

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 standardizes reproducibility hashes across the audit/sidecar corpus by migrating remaining SHA-256[:16] usages to blake2b(digest_size=8) (16 hex chars), and bumps audit schema versions in prune and safety to reflect the hash-recipe change while keeping JSONL round-trippable across historical versions.

Changes:

  • Migrate policy_hash (safety) and config_hash (prune) hashing to blake2b(digest_size=8) and update schema-version constants/defaults accordingly.
  • Refresh drift detectors, unit tests, and committed JSONL fixtures for the new audit_schema_version values (safety: 3, prune: 2).
  • Update ops/docs and rule files to describe the new hash recipe and version gating guidance.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/safety/test_request.py Updates safety request audit-schema-version expectation to 3.
tests/safety/test_models.py Updates AuditEvent default version to 3 and adds legacy v2 parsing coverage.
tests/safety/test_drift_detector.py Updates drift-detector fixture pinning docs for the v3 bump.
tests/safety/test_audit.py Updates audit writer tests to assert audit_schema_version == 3.
tests/prune/test_engine.py Updates pinned fixture assertions to expect prune audit_schema_version == 2.
tests/prune/test_drift_detector.py Updates strict prune event schema literal to v2.
tests/prune/test_audit.py Updates prune audit writer tests to assert audit_schema_version == 2.
tests/fixtures/safety/regenerate.sh Updates fixture regeneration notes and bumps fixture records to v3.
tests/fixtures/safety/audit_events_sample.jsonl Updates committed safety JSONL fixture lines to audit_schema_version: 3.
tests/fixtures/prune/prune_event_v1.jsonl Updates committed prune JSONL fixture lines to audit_schema_version: 2.
tests/fixtures/e2e_helpers/happy/.signalforge/prune.jsonl Updates e2e prune fixture JSONL to audit_schema_version: 2.
src/signalforge/safety/request.py Bumps safety writer _AUDIT_SCHEMA_VERSION to 3 and documents the reason.
src/signalforge/safety/policy.py Migrates _compute_policy_hash to blake2b(digest_size=8) and updates docstrings.
src/signalforge/safety/models.py Bumps AuditEvent.audit_schema_version default to 3 and updates schema history docs.
src/signalforge/prune/engine.py Updates prune pipeline documentation to the new hash recipe.
src/signalforge/prune/audit.py Migrates _compute_config_hash to blake2b(digest_size=8) and bumps prune schema version to 2.
docs/safety-ops.md Updates ops docs for safety policy_hash recipe and current schema version (3).
docs/prune-ops.md Updates ops docs for prune config_hash recipe and current schema version (2).
CLAUDE.md Updates public-surface notes to reflect the new safety/prune schema versions and hash recipe.
.claude/rules/safety-layer.md Updates safety layer rules describing the reproducibility fields and versioning.
.claude/rules/prune-engine.md Adds/updates guidance documenting the hash recipe normalization and schema bump rationale.

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

Comment thread src/signalforge/prune/engine.py
Comment thread .claude/rules/safety-layer.md Outdated
Comment thread tests/prune/test_drift_detector.py

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/rules/safety-layer.md:
- Line 34: Update the audit_schema_version signature so it matches production:
change the declaration audit_schema_version: int = 1 to audit_schema_version:
int = 3 (or alternatively split into two explicit items such as
audit_schema_version (schema default) vs current_production_audit_schema_version
= 3) so the signature no longer contradicts the explanatory text; edit the
single symbol audit_schema_version in the doc and adjust the explanatory
parenthetical to remove confusion.

In `@src/signalforge/prune/audit.py`:
- Line 106: The field audit_schema_version is currently typed as Literal[2]
which prevents reading historical prune.jsonl entries with value 1; change the
annotation on audit_schema_version (in the model where it is declared) from
Literal[2] to int (keeping the default value 2) so older rows validate; keep the
existing runtime gate that checks audit_schema_version >= 2 where applicable and
move the strict assertion of "we emit 2" into writer tests (update the
writer/unit tests to assert the emitted JSONL contains audit_schema_version ==
2).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c0ae7bf-f2a4-4102-9ba6-8cfec5edc659

📥 Commits

Reviewing files that changed from the base of the PR and between 61973c3 and 7cac3fc.

📒 Files selected for processing (21)
  • .claude/rules/prune-engine.md
  • .claude/rules/safety-layer.md
  • CLAUDE.md
  • docs/prune-ops.md
  • docs/safety-ops.md
  • src/signalforge/prune/audit.py
  • src/signalforge/prune/engine.py
  • src/signalforge/safety/models.py
  • src/signalforge/safety/policy.py
  • src/signalforge/safety/request.py
  • tests/fixtures/e2e_helpers/happy/.signalforge/prune.jsonl
  • tests/fixtures/prune/prune_event_v1.jsonl
  • tests/fixtures/safety/audit_events_sample.jsonl
  • tests/fixtures/safety/regenerate.sh
  • tests/prune/test_audit.py
  • tests/prune/test_drift_detector.py
  • tests/prune/test_engine.py
  • tests/safety/test_audit.py
  • tests/safety/test_drift_detector.py
  • tests/safety/test_models.py
  • tests/safety/test_request.py

Comment thread .claude/rules/safety-layer.md Outdated
Comment thread src/signalforge/prune/audit.py Outdated
Three review fixes from PR #84:

1. `_compute_config_hash` now canonicalises internally
   (`json.dumps(..., sort_keys=True, separators=(",", ":"))`) so callers
   can't accidentally feed non-canonical input. Pydantic's
   `model_dump_json` does not contractually guarantee sorted keys across
   point releases; the call site in `prune.engine` was passing the bare
   dump. Mirrors `safety.policy._compute_policy_hash` verbatim.

2. `PruneEvent.audit_schema_version` typed back to `int` (default
   `_PRUNE_AUDIT_SCHEMA_VERSION`) rather than `Literal[2] = 2`. The
   `Literal` form rejected pre-#55 records with `audit_schema_version: 1`
   and broke the audit-replay round-trip contract that safety's
   `AuditEvent` already honours. Drift-detector strict mirror updated to
   `int` to match; added two new pin tests — current-version fixture pin
   plus legacy-v1 round-trip — so the contract is exercised both ways.

3. `safety-layer.md` AuditEvent bullet now reads `audit_schema_version:
   int` (was stale `int = 1`) with the round-trip rationale called out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wjduenow

Copy link
Copy Markdown
Owner Author

PR Review Summary

Fixed (3 items)

File Line Issue Commit
src/signalforge/prune/audit.py 106 audit_schema_version: Literal[2] = 2 broke v1 round-trip (CodeRabbit, Major). Typed back to int = _PRUNE_AUDIT_SCHEMA_VERSION to mirror safety's AuditEvent pattern. 0c737b0
src/signalforge/prune/engine.py 692 _compute_config_hash relied on the caller for canonicalisation but the call site passed bare resolved_config.model_dump_json() (Copilot). Canonicalisation hoisted inside the helper (json.dumps(json.loads(...), sort_keys=True, separators=(",", ":"))) — mirrors _compute_policy_hash verbatim so callers can't misuse it. 0c737b0
.claude/rules/safety-layer.md 34 Bullet still read audit_schema_version: int = 1 after the v3 bump (Copilot + CodeRabbit, both flagged). Updated to int with the round-trip rationale called out. 0c737b0

Added two new pin tests in tests/prune/test_drift_detector.py:

  • test_prune_event_fixture_audit_schema_version_is_current — mirrors safety's _AUDIT_SCHEMA_VERSION pin
  • test_prune_event_round_trips_legacy_schema_version_1 — exercises the audit-replay contract that motivated typing the field int

False Positives (1 item)

File Line Issue Reason
tests/prune/test_drift_detector.py 106 Suggested renaming prune_event_v1.jsonlprune_event_v2.jsonl to match audit_schema_version: 2 (Copilot). Fixture-file version (_v1) is a separate scheme from the audit JSONL's audit_schema_version. Convention is uniform across the project: diff_report_v1.json carries audit_schema_version: 2; grade_event_v1.jsonl carries audit_schema_version: 1. The fixture file name versions the fixture's own revision; the in-record field versions the audit shape. Renaming would break the convention without adding signal.

🤖 Generated with Claude Code

@wjduenow
wjduenow merged commit 21bab5a into dev May 12, 2026
2 checks passed
@wjduenow
wjduenow deleted the feat/55-normalize-hash-recipe branch May 12, 2026 13:56
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.

3 participants