Skip to content

feat(agent-loop): audit signed-state drift read-only#204

Closed
Abiorh001 wants to merge 3 commits into
mainfrom
codex/ws-eng-008-02-signed-state-drift-audit
Closed

feat(agent-loop): audit signed-state drift read-only#204
Abiorh001 wants to merge 3 commits into
mainfrom
codex/ws-eng-008-02-signed-state-drift-audit

Conversation

@Abiorh001

@Abiorh001 Abiorh001 commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

PR Trust Bundle: WS-ENG-008-02

Chunk

WS-ENG-008-02 — Scheduled Signed-State Drift Audit

Merge intent: .agent-loop/merge-intents/WS-ENG-008-02.json

Goal

Detect later signed loop-memory custody or semantic drift independently, without
granting the audit repair, signing, dispatch, publication, or write authority.

Human-approved intent

The signed contract is
.agent-loop/initiatives/WS-ENG-008-repository-native-sdlc-assurance/chunks/WS-ENG-008-02-scheduled-signed-state-drift-audit.md.

What changed and why

  • Added a daily and default-branch-only manually requestable drift workflow.
  • Added a read-only audit entry point that reuses canonical signature, exact-tree,
    semantic, ledger, projection, ancestry, and active-contract validation.
  • Added bounded diagnostics that distinguish advancement from corruption.
  • Added fourteen focused failure fixtures and an Agent Gate workflow regression.
  • Documented operation and added one successor intent for WS-ENG-008-03.

Design chosen

The workflow captures immutable branch tips through the read-only GitHub API,
checks out exact main and state commits with pinned actions and non-persisted
credentials, runs canonical validators, then rechecks both tips. It never imports
or invokes reducer, signer, event, recovery, repair, or publication commands.

Alternatives rejected

  • workflow_dispatch: callers can select feature-ref workflow code.
  • Unauthenticated Git clones/tip reads: fail for private/internal repositories.
  • Scheduled repair: would create a second write and authority path.
  • Reimplementation of signed-state validators: would fork canonical security logic.

Scope control and product behavior

Eight authorized repository-assurance files changed. Backend, frontend, API,
database, authorization grants, payments, artifacts, product review decisions,
coverage thresholds, start/cancel authority, signing, and branch protection are
unchanged.

Acceptance criteria proof

  • Default-branch code, pinned actions, read-only permissions: workflow assertions pass.
  • Signature, manifest, closed tree, semantics, ledger/projections, ancestry and
    active contracts: canonical validators plus live audit pass.
  • Advancement/corruption distinction and bounded diagnostics: focused tests pass.
  • Private repository compatibility: transient read API and pinned state checkout.
  • Failure matrix: canonical mutation suites plus concrete audit-level binding and
    shallow-history fixtures pass.
  • Successor control: one schema-v2 intent names WS-ENG-008-03 with explicit start.

Tests/checks run

Fourteen focused tests and 105 Agent Gate regressions passed. Machine scope,
merge intent, Ruff, compilation, Markdown links, stale wording/authorization/
artifact scans, live signed-state audit, and diff checks passed.

Test delta and CI integrity

No test was removed, skipped, deselected, or weakened. No dependency, package
script, coverage floor, existing workflow, permission, or required check was
weakened. New actions are commit-pinned and both checkouts disable credential
persistence.

Reviewer results

Reviewed code SHA: 9c04beb154ef316307ef3f3896d006ccd87f6e8a

All nine required internal tracks passed: senior engineering, QA/test,
security/auth, product/ops, architecture, CI integrity, docs, reuse/dedup, and
test delta. Earlier High/Medium findings about feature-ref dispatch,
unauthenticated private-repository reads, preflight diagnostics, and concrete
contract fixtures were repaired and re-reviewed.

External review

CodeRabbit and hosted GitHub checks remain pending until publication. They
supplement, rather than replace, the completed internal review.

Remaining risks and follow-up work

Scheduled execution still depends on GitHub Actions and GitHub API availability.
Failures remain visible and non-mutating. WS-ENG-008-03 is only the declared
same-initiative successor and requires a separate explicit signed start.

Human review focus

  • repository_dispatch default-branch trust boundary.
  • Read-only token use and non-persisted credentials.
  • Advancement versus corruption classification.
  • Reuse of canonical validators and absence of mutable commands.

Human merge ownership

The user owns approval and merge of the exact PR. Merge automation will stop
ENG-008 and will not start WS-ENG-008-03 automatically.

Summary by CodeRabbit

  • New Features

    • Added a scheduled, read-only integrity audit for signed repository state.
    • Audits now verify signatures, ancestry, active contract binding, and immutable branch tips.
    • Reports branch advancement separately from corruption and preserves bounded diagnostic artifacts.
    • Added explicit successor-step tracking for the next implementation phase.
  • Documentation

    • Documented audit coverage, safety boundaries, manual triggering, and operational handling.
  • Tests

    • Added coverage for valid snapshots, corruption, unavailable history, branch advancement, and read-only workflow safeguards.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a scheduled, read-only signed-state drift audit with pinned branch validation, categorized diagnostics, regression tests, workflow safety checks, and WS-ENG-008-02 assurance metadata.

Changes

Signed-state audit

Layer / File(s) Summary
Audit engine
scripts/audit_loop_memory_drift.py
Adds categorized validation for signed-state custody, generated state, contract binding, ancestry, branch advancement, and diagnostic output.
Read-only workflow integration
.github/workflows/loop-memory-drift-audit.yml
Adds scheduled and repository_dispatch execution with default-branch checks, pinned checkouts, GitHub API tip reads, and diagnostic artifact upload.
Audit and gate regressions
scripts/test_audit_loop_memory_drift.py, scripts/test_agent_gates.py
Adds coverage for valid snapshots, environment failures, corruption, advancement classification, contract binding, workflow constraints, and gate registration.
Assurance tracking and operations
.agent-loop/initiatives/WS-ENG-008-repository-native-sdlc-assurance/*, .agent-loop/merge-intents/*, .agent-loop/policies/*, docs/operations_post_merge_memory.md
Records WS-ENG-008-02 provenance, policy boundaries, merge intent, review evidence, trust-bundle details, and post-merge audit procedures.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler as GitHub Actions
  participant API as GitHub API
  participant Checkout as Pinned state checkout
  participant Audit as Drift audit CLI
  Scheduler->>API: Read main and automation/loop-memory tips
  Scheduler->>Checkout: Materialize exact signed-state SHA
  Scheduler->>Audit: Run audit with expected repository and state SHAs
  Audit->>API: Re-check branch tips
  Audit->>Checkout: Validate signature, tree, semantics, contract, and ancestry
  Audit-->>Scheduler: Return pass or categorized diagnostics
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the core change, but it omits several required template sections like signed start provenance, evidence, and CI gate checks. Add the missing template sections: signed start provenance, allowed files/product behavior, evidence commands and results, CI integrity checks, test delta detail, and the reviewer/merge checklists.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: a read-only signed-state drift audit.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ws-eng-008-02-signed-state-drift-audit

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.

@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: 1

🧹 Nitpick comments (1)
.github/workflows/loop-memory-drift-audit.yml (1)

64-77: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Route step outputs through env: instead of inline template expansion.

${{ steps.tips.outputs.main_sha }} / state_sha are expanded directly into the shell script text. Static analysis (zizmor) flags this as template-injection risk. The upstream regex validation (^[0-9a-f]{40}$) at capture time mitigates it today, but passing these through env: and referencing "$MAIN_SHA"/"$STATE_SHA" removes the injection surface entirely as defense-in-depth, independent of that validation staying correct forever.

🔒 Proposed fix
       - name: Audit signed state without mutation authority
         shell: bash
         env:
           GH_TOKEN: ${{ github.token }}
+          MAIN_SHA: ${{ steps.tips.outputs.main_sha }}
+          STATE_SHA: ${{ steps.tips.outputs.state_sha }}
         run: |
           set -euo pipefail
           python3 scripts/audit_loop_memory_drift.py \
             --repository-root "${GITHUB_WORKSPACE}" \
             --state-root "${GITHUB_WORKSPACE}/loop-memory-state-audit" \
             --public-key .agent-loop/keys/loop-memory-signing-public.pem \
             --repository "${GITHUB_REPOSITORY}" \
-            --expected-main-sha "${{ steps.tips.outputs.main_sha }}" \
-            --expected-state-sha "${{ steps.tips.outputs.state_sha }}" \
+            --expected-main-sha "${MAIN_SHA}" \
+            --expected-state-sha "${STATE_SHA}" \
             --diagnostic-output "${RUNNER_TEMP}/loop-memory-drift-audit.json"
🤖 Prompt for 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.

In @.github/workflows/loop-memory-drift-audit.yml around lines 64 - 77, Update
the “Audit signed state without mutation authority” step to pass the tips step
outputs through its env block using dedicated MAIN_SHA and STATE_SHA variables,
then replace the inline template expressions in the Python command with quoted
shell references to those variables; leave the existing validation and audit
arguments unchanged.

Source: Linters/SAST tools

🤖 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 `@scripts/audit_loop_memory_drift.py`:
- Around line 63-72: Update _state_main_sha to validate that the parsed
STATE.json value is a dictionary before calling state.get or indexing its source
field. Treat any non-dictionary top-level JSON value as corruption by raising
AuditError with the existing bounded diagnostic, while preserving the current
main_sha validation for valid dictionary states.

---

Nitpick comments:
In @.github/workflows/loop-memory-drift-audit.yml:
- Around line 64-77: Update the “Audit signed state without mutation authority”
step to pass the tips step outputs through its env block using dedicated
MAIN_SHA and STATE_SHA variables, then replace the inline template expressions
in the Python command with quoted shell references to those variables; leave the
existing validation and audit arguments unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 500ff338-af32-46a5-bb3c-cd3ff77800fd

📥 Commits

Reviewing files that changed from the base of the PR and between 339248c and 87f95d0.

📒 Files selected for processing (10)
  • .agent-loop/initiatives/WS-ENG-008-repository-native-sdlc-assurance/STATUS.md
  • .agent-loop/initiatives/WS-ENG-008-repository-native-sdlc-assurance/reviews/WS-ENG-008-02-internal-review-evidence.md
  • .agent-loop/initiatives/WS-ENG-008-repository-native-sdlc-assurance/reviews/WS-ENG-008-02-pr-trust-bundle.md
  • .agent-loop/merge-intents/WS-ENG-008-02.json
  • .agent-loop/policies/repository-engineering-policy.md
  • .github/workflows/loop-memory-drift-audit.yml
  • docs/operations_post_merge_memory.md
  • scripts/audit_loop_memory_drift.py
  • scripts/test_agent_gates.py
  • scripts/test_audit_loop_memory_drift.py

Comment on lines +63 to +72
def _state_main_sha(state_root: Path) -> str:
try:
state = json.loads((state_root / ".agent-loop/STATE.json").read_text("utf-8"))
event = state.get("event")
value = event.get("main_sha") if isinstance(event, dict) else state["source"]["main_sha"]
except (OSError, UnicodeError, json.JSONDecodeError, KeyError, TypeError) as exc:
raise AuditError("corruption", "canonical state has no readable main identity") from exc
if not isinstance(value, str) or len(value) != 40:
raise AuditError("corruption", "canonical state has an invalid main identity")
return value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Uncaught AttributeError breaks the fail-closed contract for malformed STATE.json.

If the canonical state's top-level JSON isn't a dict (e.g. corrupted into a list, string, or null), state.get("event") raises AttributeError, which isn't in the caught exception tuple. This crashes the audit with an unhandled traceback instead of emitting the bounded "corruption" diagnostic the whole tool is designed to produce — precisely for the kind of state corruption this audit exists to detect. It also bypasses main()'s except AuditError handler, so no diagnostic JSON is ever written for this case.

🛡️ Proposed fix
 def _state_main_sha(state_root: Path) -> str:
     try:
         state = json.loads((state_root / ".agent-loop/STATE.json").read_text("utf-8"))
+        if not isinstance(state, dict):
+            raise TypeError("canonical state is not a JSON object")
         event = state.get("event")
         value = event.get("main_sha") if isinstance(event, dict) else state["source"]["main_sha"]
     except (OSError, UnicodeError, json.JSONDecodeError, KeyError, TypeError) as exc:
         raise AuditError("corruption", "canonical state has no readable main identity") from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _state_main_sha(state_root: Path) -> str:
try:
state = json.loads((state_root / ".agent-loop/STATE.json").read_text("utf-8"))
event = state.get("event")
value = event.get("main_sha") if isinstance(event, dict) else state["source"]["main_sha"]
except (OSError, UnicodeError, json.JSONDecodeError, KeyError, TypeError) as exc:
raise AuditError("corruption", "canonical state has no readable main identity") from exc
if not isinstance(value, str) or len(value) != 40:
raise AuditError("corruption", "canonical state has an invalid main identity")
return value
def _state_main_sha(state_root: Path) -> str:
try:
state = json.loads((state_root / ".agent-loop/STATE.json").read_text("utf-8"))
if not isinstance(state, dict):
raise TypeError("canonical state is not a JSON object")
event = state.get("event")
value = event.get("main_sha") if isinstance(event, dict) else state["source"]["main_sha"]
except (OSError, UnicodeError, json.JSONDecodeError, KeyError, TypeError) as exc:
raise AuditError("corruption", "canonical state has no readable main identity") from exc
if not isinstance(value, str) or len(value) != 40:
raise AuditError("corruption", "canonical state has an invalid main identity")
return value
🤖 Prompt for 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.

In `@scripts/audit_loop_memory_drift.py` around lines 63 - 72, Update
_state_main_sha to validate that the parsed STATE.json value is a dictionary
before calling state.get or indexing its source field. Treat any non-dictionary
top-level JSON value as corruption by raising AuditError with the existing
bounded diagnostic, while preserving the current main_sha validation for valid
dictionary states.

@Abiorh001

Copy link
Copy Markdown
Collaborator Author

Closed by owner-directed simplification. Signed-state drift auditing is paused while the engineering loop is reduced to a simple, rebuildable, non-blocking model.

@Abiorh001 Abiorh001 closed this Jul 26, 2026
@abiorh-claw
abiorh-claw deleted the codex/ws-eng-008-02-signed-state-drift-audit branch July 26, 2026 20:15
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