Skip to content

feat: add governed audit evidence review boundary - #102

Open
seonghobae wants to merge 30 commits into
developfrom
feat/audit-evidence-review
Open

feat: add governed audit evidence review boundary#102
seonghobae wants to merge 30 commits into
developfrom
feat/audit-evidence-review

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Buyer-visible gap

Protected develop@9e3e4847510e1e612b48474ba42b177b8ed824df already writes PII-minimized immutable audit/outbox evidence, but it has no executable purpose-bound read boundary that proves authorization occurs before evidence access and re-verifies persisted canonical evidence before returning it to an auditor or HR governance workflow.

This PR adds that bounded Orgmetra-owned review boundary and now includes the concrete least-privileged PostgreSQL reader for the existing audit store. It does not add a second system of record, does not query another CWL repository or cross-service HR application tables, and does not grant employment-decision authority.

Test-first implementation and repairs

Initial contract head 592cd8f097cb1d45cd901b85f542b590f9ebb546 added the installed-wheel quality lane and an import-before-production test. Its workflows materialized but remained queued before follow-up commits, so it is test-first predecessor evidence, not claimed as terminal hosted RED, and no predecessor status is transferred.

The implementation adds a bounded AuditEvidenceQuery; exact-scope AuditEvidenceReadAuthority invoked before storage; a read-only adapter protocol over the existing forced-RLS Orgmetra audit store; and defense-in-depth verification of exact UTF-8 canonical bytes, lower-case SHA-256, deterministic JSON, the existing PII-minimized envelope/data shape, CloudEvents 1.0 media/version, event/tenant identity, system-recorded window, row count, and strict (recorded_at, audit_event_record_id) order.

Fresh self-review repaired two earlier Orgmetra-owned integrity defects test-first:

  1. Post-storage envelope widening. RED 1aa81b250669373ffd63c2b398925773c6cf967c proves that adding a top-level HR field or nested data field and recomputing a matching digest must still fail closed; malformed unencodable text must also become a stable validation failure. Root repair fb668be543d93c2bc7a1d0a930d5f889a916fd7b binds verification to the exact protected-main PII-minimized envelope/data key sets and hashes one verified UTF-8 byte sequence.
  2. Post-construction trust mutation. RED b9385dbe9c9501dd1748cca6261c4d4c118ac1dd proves a once-valid query cannot widen its limit, a once-valid authorization cannot replace boolean permitted with a truthy non-boolean, and a once-valid persisted row cannot replace canonical bytes plus digest after construction. Root repair 5005c9f58c3f35c6a08b8134362478418d011bcc reconstructs live query, authorization, and row fields through governed constructors and returns detached verified row snapshots.

PostgreSQL host adapter slice

Regression head df289f8506e170d290340c5d3dab32a31812e2ce requires a public PostgresAuditEvidenceRowReader, SET TRANSACTION READ ONLY, a fail-closed CURRENT_USER proof for NOSUPERUSER NOBYPASSRLS, transaction-local orgmetra.tenant_record_id binding, a static parameterized query over public.audit_event_record, the authorized half-open recorded_at window and limit, deterministic ordering, direct-query revalidation before connection open, and fail-closed unexpected row shape. Its hosted workflows queued before implementation, so no terminal RED is claimed.

Root implementation starts at b7269c6b9fc091ab141c85e98c9c2c00580295bf. The adapter reads only the existing Orgmetra audit relation; deployment composition retains TLS, credentials, pooling and least-privileged role selection. README, CHANGELOG, ADR 0102 and traceability distinguish this implemented active-PR adapter from the still-planned customer-facing audit UI/API.

Fresh source verification on the current head confirms read_audit_evidence(...) snapshots and authorizes the exact query before invoking reader.read_rows(...). PostgresAuditEvidenceRowReader is intentionally a storage adapter rather than a second authorization authority; direct use outside the governed orchestration function is not an authorized review path. The remaining review observations about compile caches, test-tool installation, non-autocommit deployment convention and DB-write-vs-read defense depth are informational and do not identify a current authorization/privacy/data-integrity bypass.

CloudEvents v1.0.2 and NIST SP 800-53 Rev. 5 AU-6/current reviewed control catalog update 5.2.0 remain design evidence, not certification claims.

Exact-current-head evidence

Current exact head: d87cb05f723f106c653f2ea07680872fd9c62ada.
Fresh live base: develop@9e3e4847510e1e612b48474ba42b177b8ed824df.
GitHub reports the PR open, ready-for-review, and mergeable.

Every applicable exact-current-head hosted workflow is terminal GREEN:

  • Audit Evidence Review Quality 32663401703 — success.
  • Foundation CI 32663401728 — success.
  • Recovery Rehearsal Quality 32663401678 — success.
  • SAST Semgrep 32663401822 — success.
  • Security Scan 32663401746 — success.

Fresh submitted reviews are COMMENTED/analysis only; there is no qualifying independent non-author APPROVE or CHANGES_REQUESTED. The earlier datacontenttype concern is resolved after verifying the protected audit writer/database contract. Current unresolved threads are informational observations, not verified defects.

Merge governance

Ready-for-review is not merge authorization. Keep unmerged until a qualifying independent non-author approval exists on this unchanged head and issue #89's enforceable develop protection defect is actually repaired. Immediately before any future merge, refetch exact head, live base, reviews, unresolved threads, effective rules/protection and exact-current-head checks and use expected-head protection only if every live gate is satisfied. Do not self-approve, weaken gates, simulate branch protection in workflow code, transfer predecessor evidence, or manufacture passing evidence.

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 58 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f8a7980c-8007-4820-917d-a3ae51769d96

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3e484 and d87cb05.

📒 Files selected for processing (15)
  • .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
  • packages/audit-evidence-review/CHANGELOG.md
  • packages/audit-evidence-review/README.md
  • packages/audit-evidence-review/pyproject.toml
  • packages/audit-evidence-review/src/orgmetra_audit_evidence_review/__init__.py
  • packages/audit-evidence-review/src/orgmetra_audit_evidence_review/postgres.py
  • packages/audit-evidence-review/src/orgmetra_audit_evidence_review/review.py
  • packages/audit-evidence-review/tests/test_postgres_reader.py
  • packages/audit-evidence-review/tests/test_privacy_integrity.py
  • packages/audit-evidence-review/tests/test_review.py
  • packages/audit-evidence-review/tests/test_runtime_integrity.py
  • packages/audit-evidence-review/tests/test_validation.py
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/audit-evidence-review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@seonghobae
seonghobae marked this pull request as ready for review August 23, 2026 19:04

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 potential issues.

Open in Devin Review

Comment on lines +49 to +50
- name: Compile audit evidence review package
run: python -m compileall -q packages/audit-evidence-review/src packages/audit-evidence-review/tests

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: compileall artifacts covered by gitignore

The quality workflow compiles the package in-tree then requires a clean checkout. The __pycache__ directories this creates do not break the check because .gitignore already ignores __pycache__/ and *.py[cod]. Confirmed, not a defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +59 to +64
/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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Test extra deps come from foundation-test, not the wheel

The venv installs the wheel with the [test] extra under --no-deps, so pytest and pytest-cov are not pulled from the extra. They are supplied by foundation-test.txt installed at line 59, which pins both. The test runner is present; not a defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae
seonghobae marked this pull request as draft August 23, 2026 20:06

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 new potential issues.

Open in Devin Review

Comment on lines +62 to +94
def read_rows(self, query: AuditEvidenceQuery) -> tuple[PersistedAuditEvidenceRow, ...]:
"""Return one ordered, tenant-bound page of revalidated immutable audit rows."""
if type(query) is not AuditEvidenceQuery:
raise TypeError("query must be an exact AuditEvidenceQuery.")
verified_query = AuditEvidenceQuery(
tenant_record_id=query.tenant_record_id,
query_reference=query.query_reference,
requester_reference=query.requester_reference,
purpose_code=query.purpose_code,
recorded_from=query.recorded_from,
recorded_before=query.recorded_before,
limit=query.limit,
)

with self.connection_factory() as connection:
with connection.cursor() as cursor:
cursor.execute(_READ_ONLY_SQL)
cursor.execute(_ROLE_GUARD_SQL)
if cursor.fetchone() is None:
raise PermissionError(
"audit evidence reads require a NOSUPERUSER NOBYPASSRLS database role."
)
cursor.execute(_TENANT_CONTEXT_SQL, (str(verified_query.tenant_record_id),))
cursor.execute(
_AUDIT_EVIDENCE_SQL,
(
verified_query.tenant_record_id,
verified_query.recorded_from,
verified_query.recorded_before,
verified_query.limit,
),
)
rows = cursor.fetchmany(verified_query.limit)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Reader read path has no authorization gate

PostgresAuditEvidenceRowReader.read_rows performs the read-only role guard and tenant RLS binding but no authorization; that lives only in read_audit_evidence. By design the reader is meant to be composed under read_audit_evidence, but any future host that calls the reader directly loses the authorization-before-read guarantee.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +76 to +84
with self.connection_factory() as connection:
with connection.cursor() as cursor:
cursor.execute(_READ_ONLY_SQL)
cursor.execute(_ROLE_GUARD_SQL)
if cursor.fetchone() is None:
raise PermissionError(
"audit evidence reads require a NOSUPERUSER NOBYPASSRLS database role."
)
cursor.execute(_TENANT_CONTEXT_SQL, (str(verified_query.tenant_record_id),))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Read-only/RLS depends on non-autocommit connection

SET TRANSACTION READ ONLY and the set_config(..., true) tenant binding only take effect if the injected connection is not in autocommit and no statement has yet run in the transaction. This deployment assumption matches the existing services/job-analysis-api/.../postgres.py reader, so it is a consistent convention rather than a new defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +168 to +179
event_keys = frozenset(document)
if event_keys not in (_BASE_EVENT_KEYS, _BASE_EVENT_KEYS | {_CONFIRMATION_KEY}):
raise ValueError("canonical_event_json does not match the governed audit envelope shape.")
event_data = document.get("data")
if type(event_data) is not dict or frozenset(event_data) != _DATA_KEYS:
raise ValueError("canonical_event_json does not match the governed audit data shape.")
if document.get("specversion") != "1.0" or document.get("datacontenttype") != "application/json":
raise ValueError("audit event must use the governed CloudEvents 1.0 JSON contract.")
if document.get("id") != str(self.audit_event_record_id):
raise ValueError("audit event id does not match the persisted row identity.")
if document.get("orgmetratenant") != str(self.tenant_record_id):
raise ValueError("audit event tenant does not match the persisted row scope.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Read-time checks are a subset of DB write validation

PersistedAuditEvidenceRow re-verifies key sets, data shape, canonical form, digest, specversion, datacontenttype, id and tenant, but not the source/type/subject/actor patterns that validate_audit_event_envelope enforces on write. Acceptable because the rows already passed that DB CHECK, so this is narrower defense-in-depth, not a hole.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae
seonghobae marked this pull request as ready for review August 23, 2026 20:59
@seonghobae
seonghobae marked this pull request as draft August 24, 2026 15:40
@seonghobae
seonghobae marked this pull request as ready for review August 24, 2026 15:40
@seonghobae seonghobae closed this Aug 24, 2026
@seonghobae seonghobae reopened this Aug 24, 2026
@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please review the current unchanged head against protected develop. Local exact-head verification: all owned package suites pass at 100% statement/branch coverage.

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.

1 participant