Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3e6ccd2
test(audit-review): define package quality contract
seonghobae Aug 23, 2026
8a46f1c
test(audit-review): require purpose-bound verified audit read
seonghobae Aug 23, 2026
592cd8f
test(audit-review): add exact-head quality workflow
seonghobae Aug 23, 2026
75bedd0
feat(audit-review): implement purpose-bound evidence review
seonghobae Aug 23, 2026
29408ee
feat(audit-review): export governed review contracts
seonghobae Aug 23, 2026
1e23586
test(audit-review): cover authorization and integrity edges
seonghobae Aug 23, 2026
6e60f5a
docs(audit-review): document governed review boundary
seonghobae Aug 23, 2026
b80eb57
docs(audit-review): record initial governed review contract
seonghobae Aug 23, 2026
b490ee2
docs(audit-review): record purpose-bound review decision
seonghobae Aug 23, 2026
a1c5da3
docs(audit-review): record primary-source evidence
seonghobae Aug 23, 2026
705536a
docs(audit-review): trace executable evidence boundary
seonghobae Aug 23, 2026
1aa81b2
test(audit-review): reject post-storage envelope widening
seonghobae Aug 23, 2026
fb668be
fix(audit-review): preserve PII-minimized envelope shape
seonghobae Aug 23, 2026
3d0fad6
docs(audit-review): record envelope-widening repair
seonghobae Aug 23, 2026
eaccec5
docs(audit-review): trace privacy-integrity repair
seonghobae Aug 23, 2026
b501d07
docs(audit-review): document exact envelope privacy check
seonghobae Aug 23, 2026
ce9d372
docs(audit-review): align audit-review authority and privacy controls
seonghobae Aug 23, 2026
27dae80
test(audit-review): cover exact audit envelope variants
seonghobae Aug 23, 2026
b9385db
test(audit-review): reject post-construction trust mutation
seonghobae Aug 23, 2026
5005c9f
fix(audit-review): revalidate live trust objects before use
seonghobae Aug 23, 2026
6471199
docs(audit-review): record live trust-object revalidation
seonghobae Aug 23, 2026
4009b45
docs(audit-review): trace runtime-integrity repair
seonghobae Aug 23, 2026
d1e8708
docs(audit-review): document live evidence revalidation
seonghobae Aug 23, 2026
df289f8
test(audit): require tenant-bound PostgreSQL evidence reader
seonghobae Aug 23, 2026
b7269c6
feat(audit): add tenant-bound PostgreSQL evidence reader
seonghobae Aug 23, 2026
c5daf3a
feat(audit): expose PostgreSQL evidence reader
seonghobae Aug 23, 2026
da0e676
docs(audit): document PostgreSQL evidence reader contract
seonghobae Aug 23, 2026
6133453
docs(audit): record PostgreSQL reader delivery
seonghobae Aug 23, 2026
8664395
docs(audit): trace PostgreSQL reader evidence
seonghobae Aug 23, 2026
d87cb05
docs(audit): accept least-privileged PostgreSQL reader boundary
seonghobae Aug 23, 2026
67b8446
fix(audit-review): validate persisted envelope value types
seonghobae Aug 28, 2026
5344d77
fix: isolate audit review callback queries
seonghobae Aug 29, 2026
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
74 changes: 74 additions & 0 deletions .github/workflows/audit-evidence-review-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Audit Evidence Review Quality

on:
pull_request:
branches:
- develop
paths:
- "packages/audit-evidence-review/**"
- ".github/requirements/foundation-test.txt"
- ".github/workflows/audit-evidence-review-quality.yml"
- "docs/adr/0102-governed-audit-evidence-review.md"
- "docs/doctoring/audit-evidence-review-references.md"
- "docs/traceability/audit-evidence-review.md"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: audit-evidence-review-quality-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
unit:
name: Audit evidence review contract and 100% coverage
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Prove exact candidate checkout
env:
ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA"
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
check-latest: false
- name: Install reviewed test and build toolchain
run: |
python -m pip install --require-hashes --no-deps --only-binary=:all: -r .github/requirements/foundation-test.txt
printf '%s\n' 'setuptools==84.0.0 --hash=sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670' > /tmp/orgmetra-audit-evidence-review-build.txt
python -m pip install --require-hashes --no-deps --only-binary=:all: -r /tmp/orgmetra-audit-evidence-review-build.txt
python -m pip check
- name: Compile audit evidence review package
run: python -m compileall -q packages/audit-evidence-review/src packages/audit-evidence-review/tests
Comment thread
seonghobae marked this conversation as resolved.
- name: Build and install exact package artifact
run: |
rm -rf /tmp/orgmetra-audit-evidence-review-build-tree /tmp/orgmetra-audit-evidence-review-dist /tmp/orgmetra-audit-evidence-review-venv
cp -a packages/audit-evidence-review /tmp/orgmetra-audit-evidence-review-build-tree
mkdir -p /tmp/orgmetra-audit-evidence-review-dist
python -m pip wheel --no-deps --no-build-isolation --wheel-dir /tmp/orgmetra-audit-evidence-review-dist /tmp/orgmetra-audit-evidence-review-build-tree
test "$(find /tmp/orgmetra-audit-evidence-review-dist -maxdepth 1 -type f -name '*.whl' | wc -l)" -eq 1
python -m venv /tmp/orgmetra-audit-evidence-review-venv
/tmp/orgmetra-audit-evidence-review-venv/bin/python -m pip install --require-hashes --no-deps --only-binary=:all: -r "$GITHUB_WORKSPACE/.github/requirements/foundation-test.txt"
wheel_path="$(find /tmp/orgmetra-audit-evidence-review-dist -maxdepth 1 -type f -name '*.whl' -print -quit)"
wheel_sha="$(sha256sum "$wheel_path" | awk '{print $1}')"
printf 'orgmetra-audit-evidence-review[test] @ file://%s --hash=sha256:%s\n' "$wheel_path" "$wheel_sha" > /tmp/orgmetra-audit-evidence-review-install.txt
/tmp/orgmetra-audit-evidence-review-venv/bin/python -m pip install --require-hashes --no-deps -r /tmp/orgmetra-audit-evidence-review-install.txt
/tmp/orgmetra-audit-evidence-review-venv/bin/python -m pip check
Comment thread
seonghobae marked this conversation as resolved.
- name: Test installed audit evidence review artifact with exact statement and branch coverage
env:
COVERAGE_FILE: /tmp/orgmetra-audit-evidence-review.coverage
run: |
cd /tmp
/tmp/orgmetra-audit-evidence-review-venv/bin/python -m pytest -c "$GITHUB_WORKSPACE/packages/audit-evidence-review/pyproject.toml" "$GITHUB_WORKSPACE/packages/audit-evidence-review/tests"
- name: Require clean checkout
run: |
git diff --exit-code
test -z "$(git status --porcelain)"
31 changes: 31 additions & 0 deletions docs/adr/0102-governed-audit-evidence-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ADR 0102: Governed audit evidence review boundary

Status: Proposed

## Context

Protected `develop` already persists a PII-minimized CloudEvents-compatible canonical audit envelope and SHA-256 digest in `audit_event_record` under forced tenant RLS. The repository did not yet define an executable read-side trust boundary for audit review. A caller could otherwise implement ad hoc reads that fetch evidence before authorization, fail to bind authorization to the exact tenant/request/purpose, return unbounded history, or trust persisted bytes without rechecking their digest, row identity, and PII-minimized envelope shape.

NIST SP 800-53 Rev. 5 AU-6 calls for review and analysis of system audit records; the current NIST control catalog update reviewed for this decision is 5.2.0. CloudEvents v1.0.2 defines the stable event context contract used by Orgmetra's existing audit envelope. These sources motivate reviewability and interoperability; they do not make this package a certification or a complete security-monitoring system.

## Decision

Introduce an Orgmetra-owned, transport-neutral `audit-evidence-review` package with four explicit boundaries:

1. `AuditEvidenceQuery` fixes tenant scope, pseudonymous request/requester correlations, the closed `audit_evidence_review` purpose, a maximum 90-day system-recorded interval, and a maximum 200-row page.
2. `AuditEvidenceReadAuthority` must be invoked before any audit-store read. Its authorization result must exactly match tenant, query reference, requester and purpose and must explicitly permit the read.
3. `AuditEvidenceRowReader` is the read-only storage protocol. The included `PostgresAuditEvidenceRowReader` reads only the existing Orgmetra `public.audit_event_record` relation. It enters a read-only transaction, proves `CURRENT_USER` is neither `SUPERUSER` nor `BYPASSRLS`, sets transaction-local `orgmetra.tenant_record_id`, and executes a static parameterized query over the authorized half-open system-recorded interval with deterministic ordering and the authorized limit. Deployment composition owns TLS, credentials, pooling and the least-privileged login role.
4. Every returned `PersistedAuditEvidenceRow` re-verifies valid bounded UTF-8, standard finite JSON values, exact canonical JSON bytes, SHA-256, the protected-main PII-minimized top-level/nested envelope key sets and value types, CloudEvents version/media type, persisted tenant/event identity, authorized time window, result count and deterministic ordering before evidence reaches the reviewer.

The package returns the already PII-minimized canonical audit envelope and does not query application-table HR values or another service database. A privileged storage rewrite cannot make extra top-level HR fields or nested rating-like fields acceptable merely by recomputing a matching digest. The package does not grant or imply employment-decision authority. An empty page means the authorized bounded reader returned no rows; an adapter failure must remain a failure rather than being converted to an empty page.

## Consequences

The buyer gains an executable, testable control boundary for audit review without turning audit evidence into a shadow HR system of record. The PostgreSQL adapter makes least-privilege role state, tenant RLS binding, read-only transaction semantics, bounded parameterized SQL and stable ordering executable rather than leaving them only as host documentation. Host composition still retains responsibility for identity/policy resolution, credentials/TLS/pooling, observability and auditing the review itself. A customer-facing audit UI/API may be added later only if it preserves authorization-before-read and exact evidence verification.

## Alternatives rejected

- **Expose `canonical_event_json` directly from arbitrary SQL.** Rejected because authorization ordering, bounds and integrity verification would be host-specific and easy to omit.
- **Let a privileged or RLS-bypass database role perform audit review.** Rejected because it would make tenant isolation depend on application predicates alone rather than the existing forced-RLS defense in depth.
- **Copy HR fields into an audit-search index.** Rejected because it creates a second sensitive system of record and weakens minimization/retention boundaries.
- **Treat a successful database read as proof of evidence integrity.** Rejected because read-time digest, exact-envelope-shape and row-identity verification provide defense in depth against corruption or privileged storage tampering.
15 changes: 15 additions & 0 deletions docs/doctoring/audit-evidence-review-references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Audit evidence review references

Reviewed 2026-08-24 against current authoritative sources.

## References (APA 7)

Cloud Native Computing Foundation. (2022). *CloudEvents specification (Version 1.0.2)*. https://github.com/cloudevents/spec/tree/v1.0.2

Joint Task Force. (2020). *Security and privacy controls for information systems and organizations (NIST Special Publication 800-53, Revision 5; updated release catalog 5.2.0)*. National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-53r5

## Design use

CloudEvents v1.0.2 remains the latest stable CloudEvents core release reviewed for the existing Orgmetra audit envelope. The package therefore rechecks `specversion = 1.0` and the existing JSON media-type contract rather than adopting a working draft.

NIST SP 800-53 Rev. 5 AU-6 identifies audit-record review, analysis and reporting as an organizational control capability. Orgmetra uses that as a design input for purpose-bound reviewability and accountable access. The reference does not imply NIST certification, FedRAMP authorization, SOC 2 attestation, or that this bounded package implements the entire AU family.
27 changes: 27 additions & 0 deletions docs/traceability/audit-evidence-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Audit evidence review traceability

## Protected-main truth

`develop@9e3e4847510e1e612b48474ba42b177b8ed824df` already owns immutable, PII-minimized audit/outbox persistence: `AuditOutboxEvent` produces deterministic CloudEvents-compatible canonical JSON and SHA-256, and `database/migrations/0003_audit_outbox_persistence.sql` stores it in forced-RLS `audit_event_record` with append-only mutation guards. Protected main does not yet contain this review package or its PostgreSQL reader.

## Active-PR truth

PR #102 adds the transport-neutral `orgmetra-audit-evidence-review` boundary and its Orgmetra-owned PostgreSQL reader. The package requires authorization before store access; exact authorization scope; bounded system-recorded interval and result count; exact runtime trust primitives; standard finite JSON value types; SHA-256 and canonical-JSON revalidation; row/event/tenant binding; in-window tenant-only rows; and strict `(recorded_at, audit_event_record_id)` order. It contains no HR application-table query and no employment-decision authority.

`PostgresAuditEvidenceRowReader` is the concrete read-only adapter for the existing `public.audit_event_record` relation. Before audit evidence is selected it opens `SET TRANSACTION READ ONLY`, verifies `CURRENT_USER` resolves to a PostgreSQL role with both `rolsuper=false` and `rolbypassrls=false`, and binds the exact tenant through transaction-local `orgmetra.tenant_record_id`. The evidence query is static and parameterized, applies the authorized half-open `recorded_at` interval and limit, and orders by `recorded_at, audit_event_record_id`. Deployment composition owns TLS, credentials, pooling and the actual least-privileged login; this adapter does not query another service database or an HR application table.

The first contract head `592cd8f097cb1d45cd901b85f542b590f9ebb546` intentionally had no production package and is the test-first predecessor. Its workflows were queued before follow-up commits, so it is not claimed as terminal hosted RED evidence and no predecessor status is transferred.

Fresh self-review then found a distinct privacy-integrity defect after the first implementation: canonical JSON plus a recomputed digest could carry additional top-level HR data or extra nested `data` fields because read-time verification checked the digest, CloudEvents version, event id and tenant but not the exact existing PII-minimized envelope shape. RED regression `1aa81b250669373ffd63c2b398925773c6cf967c` requires both widened shapes to fail closed and also requires malformed unencodable text to become a stable validation failure. Root repair `fb668be543d93c2bc7a1d0a930d5f889a916fd7b` binds read-time verification to the exact protected-main audit key sets and hashes a single verified UTF-8 byte sequence.

A second runtime-integrity self-review found that Python frozen dataclasses can still be rewritten with `object.__setattr__` after successful construction. Without boundary revalidation, a once-valid query could have its limit widened, a once-valid authorization could have `permitted` rewritten to a truthy non-boolean, and a once-valid persisted row could have both canonical JSON and digest replaced after construction. RED regression `b9385dbe9c9501dd1748cca6261c4d4c118ac1dd` fixes those attack paths as acceptance tests. Root repair `5005c9f58c3f35c6a08b8134362478418d011bcc` reconstructs live query, authorization, and row fields through their governed constructors and returns detached verified row snapshots, so authorization/store access and returned evidence never rely on a merely once-valid mutable Python object.

The PostgreSQL host-adapter slice began with regression head `df289f8506e170d290340c5d3dab32a31812e2ce`, which requires a public `PostgresAuditEvidenceRowReader`, read-only transaction entry, least-privileged role proof, transaction-local tenant binding, a static parameterized bounded audit query, query revalidation before opening a connection, and fail-closed unexpected row shape. Its initial hosted workflows queued before implementation, so no terminal RED is claimed. Root implementation began at `b7269c6b9fc091ab141c85e98c9c2c00580295bf`; exact-current-head evidence must be evaluated fresh after documentation and public-contract alignment.

## Planned host surfaces

A customer-facing audit UI/API may consume the verified review page through an application boundary. That surface is still planned and must not bypass `read_audit_evidence()` authorization ordering or the PostgreSQL reader's least-privilege/RLS contract.

## Out of scope

This PR does not create a second audit store, copy HR values into an index, query another service's application tables, mutate any dedicated-writer dependency repository, authorize employment decisions, provide SIEM correlation, or claim NIST/SOC 2 certification.
13 changes: 13 additions & 0 deletions packages/audit-evidence-review/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changelog

## Unreleased

- Add a purpose-bound audit evidence review request with a 90-day system-recorded window and 200-row maximum.
- Require authoritative exact-scope authorization before the audit-store reader is invoked.
- Add `PostgresAuditEvidenceRowReader` for the existing forced-RLS `public.audit_event_record`: it uses a read-only transaction, verifies the current PostgreSQL role is `NOSUPERUSER NOBYPASSRLS`, binds transaction-local tenant context, applies the authorized half-open recorded-time window and limit with parameterized SQL, and returns governed row snapshots only.
- Revalidate live query, authorization, and persisted-row fields into detached governed snapshots before authority/store use or evidence return, so post-construction `object.__setattr__` mutation cannot widen request bounds, forge permission, or bypass evidence checks.
- Re-verify persisted canonical JSON, SHA-256 digest, CloudEvents version/media type, tenant/event identity, review window, result count, and deterministic `(recorded_at, audit_event_record_id)` ordering before evidence is returned.
- Fail closed if stored canonical evidence is widened beyond the existing PII-minimized audit envelope/data key sets, even when a privileged rewrite recomputes a matching SHA-256 digest.
- Normalize unencodable canonical text to a stable validation failure before hashing or parsing.
- Keep HR application values, cross-service SQL, and employment-decision authority outside the review contract.
- Add exact statement and branch coverage over the installed wheel plus clean-checkout enforcement.
Loading
Loading