Skip to content

feat: herdr session protection + version audit + dry-run mode (0.5.0) - #11

Merged
buihongduc132 merged 1 commit into
mainfrom
feat/herdr-protection-version-audit-dryrun
Jul 31, 2026
Merged

feat: herdr session protection + version audit + dry-run mode (0.5.0)#11
buihongduc132 merged 1 commit into
mainfrom
feat/herdr-protection-version-audit-dryrun

Conversation

@buihongduc132

@buihongduc132 buihongduc132 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Three user-requested features for pi-opa-net 0.5.0:

1. Herdr session protection rules

Blocks destructive herdr commands (terminal workspace manager for AI agents):

  • block-herdr-server-stopherdr server stop
  • block-herdr-session-stopherdr session stop <name>
  • block-herdr-session-deleteherdr session delete <name>
  • block-herdr-workspace-closeherdr workspace close <name>

Also extends session_kill_targets to include herdr and bermuda (herdr plugin daemon), so pkill herdr / killall bermuda are blocked via existing tmux/pkill/killall rules.

2. pi_opa_net_version in ALL audit traces

Every decision metadata + audit log entry now carries the exact package version:

  • DecisionBuilder metadata → metadata.pi_opa_net_version
  • Filesystem JSONL (src/pi/audit.ts) → pi_opa_net_version field
  • OTLP export (OtlpAuditSink) → pi_opa_net_version in kvlistValue body

New src/version.ts reads version from package.json once at module load.

3. PIOPANET_DRY_RUN safety mode

Setting PIOPANET_DRY_RUN=1 adds dry_run: true to decision metadata. E2E tests now run with this flag enabled — guarantees CLI is in evaluation-only mode (no accidental command execution if bugs exist).

Test results

  • bun run typecheck ✅ clean
  • bun run lint ✅ clean (87 files, no fixes)
  • bun test443 pass, 2 fail (pre-existing pi-session smoke failures due to stale deployed v0.3.0 — will resolve after deploy of 0.5.0)

Pre-existing failures

2 Layer A1 — pi-session E2E smoke tests fail because the deployed pi-opa-net (v0.3.0) doesn't block commands correctly in spawned pi sessions. These are not caused by this PR — they fail on origin/main too. Deploying 0.5.0 will fix them. Also includes a fix: the smoke test now git inits the temp cwd so pi can actually start.

Schema change

Additive — pi_opa_net_version (string) and dry_run (boolean) added to DecisionMetadata. herdr added to family enum. No breaking changes.

Checklist

  • Herdr rules in rego + catalog
  • Version in all audit surfaces
  • Dry-run mode + test coverage
  • CHANGELOG updated
  • Version bumped to 0.5.0

Summary by cubic

Adds herdr session protection to block destructive commands, records the pi-opa-net version in all audit traces, and adds a PIOPANET_DRY_RUN safety mode for evaluation-only runs. Released as pi-opa-net 0.5.0 with no breaking changes.

  • New Features

    • Herdr session protection: adds herdr rule family and blocks server stop, session stop/delete, and workspace close; extends session_kill_targets to include herdr and bermuda.
    • Version in audit: adds pi_opa_net_version to decision metadata, filesystem JSONL, and OTLP export.
    • Dry-run mode: PIOPANET_DRY_RUN=1 marks decisions with dry_run: true and keeps the CLI in evaluation-only mode.
  • Migration

    • Schema is additive: pi_opa_net_version and dry_run added to DecisionMetadata; audit consumers should accept these fields.
    • Optional: set PIOPANET_DRY_RUN=1 to run in safe, non-executing mode.

Written for commit b41abb6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added safety protections for Herdr server, session, and workspace shutdown or deletion commands.
    • Added protections for terminating Herdr-related processes.
    • Added dry-run mode metadata and package-version details to decision and audit records.
  • Bug Fixes

    • Improved Pi session smoke tests by initializing a temporary Git repository.
  • Documentation

    • Added release notes and design documentation for session-protection and rule-unlock features.

Add 4 herdr rules (server stop, session stop/delete, workspace close)
to protect herdr terminal workspace manager from accidental kills.
Extend session_kill_targets to include herdr/bermuda daemon.

Add pi_opa_net_version to all audit traces (decision metadata,
filesystem JSONL, OTLP export) for version correlation.

Add PIOPANET_DRY_RUN=1 safety marker — tests run with this flag to
guarantee evaluation-only mode.

Bump to 0.5.0. 443 tests pass (2 pi-session smoke tests remain
pre-existing failures due to stale deployed v0.3.0).
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds four Herdr session-protection rules, package-version and dry-run metadata, audit propagation, release documentation, and E2E coverage. It also records the complete rule-unlock-keys design and implementation decisions.

Changes

Herdr protection and release metadata

Layer / File(s) Summary
Herdr rule contracts and policy
src/rules/RuleRegistry.ts, src/rules/catalog.ts, policy/safety.rego, schemas/decision-output.v1.json
Adds the herdr rule family, four Herdr catalog rules, process-target protection, and schema support.
Version, dry-run, and audit metadata
src/version.ts, src/output/DecisionBuilder.ts, src/pi/audit.ts, src/audit/OtlpAuditSink.ts, schemas/decision-output.v1.json, tests/unit/audit/*, tests/e2e/e2e.test.ts
Adds package-version metadata to decisions and audits. Adds dry_run: true when PIOPANET_DRY_RUN=1.
Herdr validation and release support
tests/e2e/e2e.test.ts, tests/e2e/pi-session-smoke.test.ts, tests/unit/rules/catalog-parity.test.ts, package.json, CHANGELOG.md, .gitignore
Adds Herdr allow and deny coverage, Git setup for Pi sessions, updated rule counts, version 0.5.0, and runtime-artifact exclusions.

Rule-unlock-keys exploration

Layer / File(s) Summary
Unlock-key design exploration
flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn1-safetynet-gap-assessment.md, flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn2-explore-unlock-design-forks.md, flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn2a-gotcha-unlock-design-batch-a.md
Documents the proposed unlock flow, design choices, failure modes, and cross-decision findings.
Unlock-key decisions and open threads
flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-locked-decisions.yaml, flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-open-threads.yaml, flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn3-lock-decisions-ttl.md
Records key formats, delivery channels, filtering, failure handling, caching, revocation, TTL behavior, and schema decisions.
Unlock-key seams and completion records
flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn4-yagni-finalize-seams.md, flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn4a-gotcha-yagni-seams-batch-b.md, flow/findings/2026-07-20-rule-unlock-keys/README.md, flow/findings/2026-07-20-rule-unlock-keys/references.md
Defines salt and audit extension seams, processing stages, acceptance constraints, gotcha coverage, and supporting references.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 clearly summarizes the three main changes: Herdr session protection, version audit metadata, and dry-run mode.
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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/herdr-protection-version-audit-dryrun

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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

feat: herdr session protection, version audit trail, dry-run mode (0.5.0)

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds 4 new rego/catalog rules blocking destructive herdr commands (server stop, session
 stop/delete, workspace close) and extends session_kill_targets to cover herdr/bermuda for
 pkill/killall protection.
• Embeds pi_opa_net_version in every audit surface (decision metadata, filesystem JSONL, OTLP
 export) via a new src/version.ts module for version correlation.
• Introduces PIOPANET_DRY_RUN=1 safety flag that stamps dry_run: true on decision metadata; E2E
 tests now run with it enabled.
• Extends DecisionMetadata schema additively (pi_opa_net_version, dry_run, herdr family
 enum) with no breaking changes.
• Bumps package version to 0.5.0 and updates CHANGELOG; fixes pi-session smoke test to git init
 the temp cwd so pi can start.
• Adds extensive design/exploration documentation under flow/findings/ for a prior related feature
 (rule-unlock-keys).
Diagram

graph TD
  A["CLI run.ts"] --> B["policy/safety.rego"] --> C["Rule Catalog"] --> D["DecisionBuilder"]
  E["src/version.ts"] --> D
  D --> F["Filesystem JSONL audit.ts"]
  D --> G["OtlpAuditSink"]
  D --> H["decision-output.v1 schema"]
  I["PIOPANET_DRY_RUN env"] --> D
  subgraph Legend
    direction LR
    _svc([Module]) ~~~ _ext{{External Config}}
  end
Loading
High-Level Assessment

The PR follows existing repository conventions closely: new rego deny rules mirror the pattern of prior rule groups (e.g. tmux/pkill/killall), version tracking is a minimal single-read module rather than a heavier config/dependency-injection approach, and dry-run is a simple env-gated flag consistent with existing config patterns (e.g. PIOPANET_UNLOCK_KEYS). These are the simplest viable approaches for the stated goals; no meaningfully better alternative architecture stands out.

Files changed (25) +1763 / -9

Enhancement (8) +114 / -3
safety.regoAdd herdr session protection rego rules +39/-1

Add herdr session protection rego rules

• Extends session_kill_targets with herdr/bermuda and adds 4 new deny rules blocking herdr server/session/workspace destructive commands.

policy/safety.rego

decision-output.v1.jsonAdd pi_opa_net_version, dry_run, herdr enum to schema +9/-1

Add pi_opa_net_version, dry_run, herdr enum to schema

• Additively extends decision-output.v1 schema with pi_opa_net_version and dry_run metadata fields and adds herdr to the family enum.

schemas/decision-output.v1.json

OtlpAuditSink.tsInclude pi_opa_net_version in OTLP audit export +2/-0

Include pi_opa_net_version in OTLP audit export

• Adds pi_opa_net_version field to the AuditEntry type and includes it in the exported kvlistValue body.

src/audit/OtlpAuditSink.ts

DecisionBuilder.tsAdd version and dry-run fields to decision metadata +9/-0

Add version and dry-run fields to decision metadata

• Imports PI_OPA_NET_VERSION and stamps it on every decision's metadata; adds dry_run:true when PIOPANET_DRY_RUN=1 is set.

src/output/DecisionBuilder.ts

audit.tsInclude pi_opa_net_version in filesystem JSONL audit +3/-0

Include pi_opa_net_version in filesystem JSONL audit

• Adds pi_opa_net_version field to the AuditEntry interface and populates it when writing audit entries.

src/pi/audit.ts

RuleRegistry.tsAdd herdr to RuleFamily type +2/-1

Add herdr to RuleFamily type

• Extends the RuleFamily union type to include 'herdr'.

src/rules/RuleRegistry.ts

catalog.tsAdd herdr rule catalog entries and family inference +27/-0

Add herdr rule catalog entries and family inference

• Adds 4 new RuleMeta entries for herdr rules and maps 'herdr' program to the herdr family in inferFamilyFromProgram.

src/rules/catalog.ts

version.tsNew module reading package version at load time +23/-0

New module reading package version at load time

• Reads package.json once at module load and exports PI_OPA_NET_VERSION for use across audit surfaces.

src/version.ts

Bug fix (1) +15 / -0
pi-session-smoke.test.tsFix smoke test by git-initializing temp cwd +15/-0

Fix smoke test by git-initializing temp cwd

• Runs git init and sets user config in the temp directory so pi can start during the smoke test.

tests/e2e/pi-session-smoke.test.ts

Tests (3) +82 / -5
e2e.test.tsAdd herdr E2E cases and dry-run mode test +54/-4

Add herdr E2E cases and dry-run mode test

• Runs CLI with PIOPANET_DRY_RUN=1, adds deny/allow test cases for herdr commands and pkill/killall targets, adds a dry-run metadata assertion test, and updates rule-count threshold.

tests/e2e/e2e.test.ts

OtlpAuditSink.test.tsUpdate OTLP sink tests for pi_opa_net_version +3/-1

Update OTLP sink tests for pi_opa_net_version

• Adds pi_opa_net_version to sample audit entries and asserts its presence in the exported kvlistValue keys.

tests/unit/audit/OtlpAuditSink.test.ts

catalog-parity.test.tsAdd parity tests for herdr rule catalog entries +25/-0

Add parity tests for herdr rule catalog entries

• New test suite verifying all herdr rule IDs exist in the catalog and map to the herdr family.

tests/unit/rules/catalog-parity.test.ts

Documentation (11) +1547 / -0
CHANGELOG.mdDocument 0.5.0 release notes +12/-0

Document 0.5.0 release notes

• Adds changelog entries for herdr protection, version audit, dry-run mode, and the smoke test fix.

CHANGELOG.md

2026-07-20-locked-decisions.yamlAdd locked design decisions for rule-unlock-keys explore +183/-0

Add locked design decisions for rule-unlock-keys explore

• New YAML artifact recording locked design decisions from a prior exploratory session on rule unlock keys.

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-locked-decisions.yaml

2026-07-20-open-threads.yamlAdd open threads tracking for rule-unlock-keys explore +219/-0

Add open threads tracking for rule-unlock-keys explore

• New YAML file tracking open/resolved discussion threads from the unlock-keys design exploration.

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-open-threads.yaml

2026-07-20-turn1-safetynet-gap-assessment.mdAdd turn 1 safety-net gap assessment notes +91/-0

Add turn 1 safety-net gap assessment notes

• New markdown recording exploration notes about safety-net rule coverage gaps.

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn1-safetynet-gap-assessment.md

2026-07-20-turn2-explore-unlock-design-forks.mdAdd turn 2 unlock design forks exploration notes +218/-0

Add turn 2 unlock design forks exploration notes

• New markdown documenting design forks considered for the unlock-key feature.

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn2-explore-unlock-design-forks.md

2026-07-20-turn2a-gotcha-unlock-design-batch-a.mdAdd gotcha review batch A for unlock design +228/-0

Add gotcha review batch A for unlock design

• New markdown listing ranked risk findings for the unlock-key design (batch A).

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn2a-gotcha-unlock-design-batch-a.md

2026-07-20-turn3-lock-decisions-ttl.mdAdd turn 3 TTL/long-lived key lock decisions +152/-0

Add turn 3 TTL/long-lived key lock decisions

• New markdown recording locked decisions for TTL and long-lived unlock key design.

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn3-lock-decisions-ttl.md

2026-07-20-turn4-yagni-finalize-seams.mdAdd turn 4 YAGNI finalization notes +171/-0

Add turn 4 YAGNI finalization notes

• New markdown finalizing YAGNI seams for salt and audit sink in the unlock-key design.

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn4-yagni-finalize-seams.md

2026-07-20-turn4a-gotcha-yagni-seams-batch-b.mdAdd gotcha review batch B for YAGNI seams +216/-0

Add gotcha review batch B for YAGNI seams

• New markdown listing ranked risk findings for YAGNI seams and ship surface (batch B).

flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn4a-gotcha-yagni-seams-batch-b.md

README.mdAdd README summary for rule-unlock-keys exploration +26/-0

Add README summary for rule-unlock-keys exploration

• New README summarizing the rule-unlock-keys explore/implementation/deploy outcome.

flow/findings/2026-07-20-rule-unlock-keys/README.md

references.mdAdd references list for rule-unlock-keys explore +31/-0

Add references list for rule-unlock-keys explore

• New markdown listing source files and documents consulted during the exploration.

flow/findings/2026-07-20-rule-unlock-keys/references.md

Other (2) +5 / -1
.gitignoreIgnore runtime artifact directories +4/-0

Ignore runtime artifact directories

• Adds .pi-opa-net/, .pi-subagents/, and .pi/ to gitignore for runtime artifacts.

.gitignore

package.jsonBump version to 0.5.0 +1/-1

Bump version to 0.5.0

• Updates package version from 0.4.2 to 0.5.0.

package.json

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

🤖 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 `@flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-open-threads.yaml`:
- Line 128: Quote the resolution scalar containing the embedded “: ” so the YAML
parser treats the entire value as a string. Update the resolution field in the
open-thread record while preserving its existing text exactly.

In
`@flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn3-lock-decisions-ttl.md`:
- Around line 127-130: Update the piped-key example under the git stash pop
usage to include the --unlock-stdin flag, ensuring the CLI reads the unlock key
from stdin while preserving the existing command structure.

In `@flow/findings/2026-07-20-rule-unlock-keys/README.md`:
- Around line 8-9: Update the rule-unlock-keys design summary in the README to
state that 15 open threads are resolved, with OT2 remaining open; keep the
existing locked-decision count and other design details unchanged.

In `@schemas/decision-output.v1.json`:
- Around line 98-99: Update the description associated with the rule-family enum
to reflect that it contains 14 families, or remove the outdated numeric count
while preserving the existing grouping/filtering meaning.

In `@tests/e2e/e2e.test.ts`:
- Around line 125-160: Add the missing `killall bermuda` case alongside the
existing `pkill bermuda` entry in the GROUP H test cases, using the appropriate
killall rule identifier and preserving the `killall` family so both command
variants are covered.
- Around line 229-238: Update the dry-run metadata test around the
`pi_opa_net_version` assertions to compare the emitted version against the
authoritative `PI_OPA_NET_VERSION` or package.json version, rather than only
checking that it is a string. Preserve the existing definedness and dry_run
assertions.
🪄 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: 19b75e8c-ddac-4f18-8f9e-4f2a5131a8d4

📥 Commits

Reviewing files that changed from the base of the PR and between 8831403 and b41abb6.

📒 Files selected for processing (25)
  • .gitignore
  • CHANGELOG.md
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-locked-decisions.yaml
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-open-threads.yaml
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn1-safetynet-gap-assessment.md
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn2-explore-unlock-design-forks.md
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn2a-gotcha-unlock-design-batch-a.md
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn3-lock-decisions-ttl.md
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn4-yagni-finalize-seams.md
  • flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn4a-gotcha-yagni-seams-batch-b.md
  • flow/findings/2026-07-20-rule-unlock-keys/README.md
  • flow/findings/2026-07-20-rule-unlock-keys/references.md
  • package.json
  • policy/safety.rego
  • schemas/decision-output.v1.json
  • src/audit/OtlpAuditSink.ts
  • src/output/DecisionBuilder.ts
  • src/pi/audit.ts
  • src/rules/RuleRegistry.ts
  • src/rules/catalog.ts
  • src/version.ts
  • tests/e2e/e2e.test.ts
  • tests/e2e/pi-session-smoke.test.ts
  • tests/unit/audit/OtlpAuditSink.test.ts
  • tests/unit/rules/catalog-parity.test.ts

topic: "IMPORTANT: TTL replayable-within-window (not single-use)"
raised_in: turn2a
status: resolved
resolution: Documentation task (not a design decision). Already decided in turn2a: "document clearly: TTL = time-bounded unlimited use; no single-use mode."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the YAML scalar valid.

Line 128 contains an unquoted : inside a plain scalar. YAML parsing fails, so consumers cannot load the open-thread records.

Proposed fix
-    resolution: Documentation task (not a design decision). Already decided in turn2a: "document clearly: TTL = time-bounded unlimited use; no single-use mode."
+    resolution: >-
+      Documentation task (not a design decision). Already decided in turn2a:
+      "document clearly: TTL = time-bounded unlimited use; no single-use mode."
📝 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
resolution: Documentation task (not a design decision). Already decided in turn2a: "document clearly: TTL = time-bounded unlimited use; no single-use mode."
resolution: >-
Documentation task (not a design decision). Already decided in turn2a:
"document clearly: TTL = time-bounded unlimited use; no single-use mode."
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 128-128: syntax error: mapping values are not allowed here

(syntax)

🤖 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 `@flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-open-threads.yaml` at
line 128, Quote the resolution scalar containing the embedded “: ” so the YAML
parser treats the entire value as a string. Update the resolution field in the
open-thread record while preserving its existing text exactly.

Source: Linters/SAST tools

Comment on lines +127 to +130
```
pi-opa-net eval "git stash pop"
pi-opa-net eval "git stash pop" --unlock ll_a3f9c2b8e1d4
echo "ttl.1753127056.7c2f..." | pi-opa-net eval "git stash pop" # --unlock-stdin

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass the --unlock-stdin flag in the example.

Line 130 pipes a key but does not provide --unlock-stdin. The CLI will not treat stdin as an unlock key, so the documented command remains blocked.

Proposed fix
-echo "ttl.1753127056.7c2f..." | pi-opa-net eval "git stash pop"  # --unlock-stdin
+echo "ttl.1753127056.7c2f..." | pi-opa-net eval "git stash pop" --unlock-stdin
📝 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
```
pi-opa-net eval "git stash pop"
pi-opa-net eval "git stash pop" --unlock ll_a3f9c2b8e1d4
echo "ttl.1753127056.7c2f..." | pi-opa-net eval "git stash pop" # --unlock-stdin
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 127-127: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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
`@flow/findings/2026-07-20-rule-unlock-keys/2026-07-20-turn3-lock-decisions-ttl.md`
around lines 127 - 130, Update the piped-key example under the git stash pop
usage to include the --unlock-stdin flag, ensuring the CLI reads the unlock key
from stdin while preserving the existing command structure.

Comment on lines +8 to +9
### rule-unlock-keys design explore (2026-07-20)
Explored hidden-bypass/unlock-key feature for pi-opa-net. 4 turns → 16 locked decisions (LD-L1..L6, LD-Y1, LD-Y2, LD-G1..G8) + 16 open threads (all resolved). YAGNI applied to salt + audit. Ship surface: src/unlock/* (6 files), src/audit/AuditSink.ts, src/cli/unlock-key.ts, schema additive, policy unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the open-thread status.

Line 9 says that all 16 threads are resolved. Line 21 states that OT2 is open. State that 15 threads are resolved and OT2 remains open.

🤖 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 `@flow/findings/2026-07-20-rule-unlock-keys/README.md` around lines 8 - 9,
Update the rule-unlock-keys design summary in the README to state that 15 open
threads are resolved, with OT2 remaining open; keep the existing locked-decision
count and other design details unchanged.

Comment on lines +98 to 99
"enum": ["git", "docker", "rm", "gcloud", "bq", "gh", "glab", "bd", "builtin", "custom", "tmux", "pkill", "killall", "herdr"],
"description": "Rule family for grouping/filtering. Matches the 6 families in the rego translation (turn6)."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the family-count description.

The enum now contains 14 families, but the description still states that it matches six families. Update the count or remove the count from the description.

🤖 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 `@schemas/decision-output.v1.json` around lines 98 - 99, Update the description
associated with the rule-family enum to reflect that it contains 14 families, or
remove the outdated numeric count while preserving the existing
grouping/filtering meaning.

Comment thread tests/e2e/e2e.test.ts
Comment on lines +125 to +160
// GROUP H — herdr session protection.
{
command: 'herdr server stop',
ruleId: 'block-herdr-server-stop',
family: 'herdr',
},
{
command: 'herdr session stop foo',
ruleId: 'block-herdr-session-stop',
family: 'herdr',
},
{
command: 'herdr session delete bar',
ruleId: 'block-herdr-session-delete',
family: 'herdr',
},
{
command: 'herdr workspace close baz',
ruleId: 'block-herdr-workspace-close',
family: 'herdr',
},
{
command: 'pkill herdr',
ruleId: 'block-pkill-tmux-wezterm',
family: 'pkill',
},
{
command: 'killall herdr',
ruleId: 'block-killall-tmux-wezterm',
family: 'killall',
},
{
command: 'pkill bermuda',
ruleId: 'block-pkill-tmux-wezterm',
family: 'pkill',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the missing killall bermuda deny case.

The new cases cover pkill herdr, killall herdr, and pkill bermuda, but not killall bermuda. Add both command variants for each new target. This prevents a policy change from covering only one command path.

Proposed test case
   {
     command: 'pkill bermuda',
     ruleId: 'block-pkill-tmux-wezterm',
     family: 'pkill',
   },
+  {
+    command: 'killall bermuda',
+    ruleId: 'block-killall-tmux-wezterm',
+    family: 'killall',
+  },
📝 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
// GROUP H — herdr session protection.
{
command: 'herdr server stop',
ruleId: 'block-herdr-server-stop',
family: 'herdr',
},
{
command: 'herdr session stop foo',
ruleId: 'block-herdr-session-stop',
family: 'herdr',
},
{
command: 'herdr session delete bar',
ruleId: 'block-herdr-session-delete',
family: 'herdr',
},
{
command: 'herdr workspace close baz',
ruleId: 'block-herdr-workspace-close',
family: 'herdr',
},
{
command: 'pkill herdr',
ruleId: 'block-pkill-tmux-wezterm',
family: 'pkill',
},
{
command: 'killall herdr',
ruleId: 'block-killall-tmux-wezterm',
family: 'killall',
},
{
command: 'pkill bermuda',
ruleId: 'block-pkill-tmux-wezterm',
family: 'pkill',
},
// GROUP H — herdr session protection.
{
command: 'herdr server stop',
ruleId: 'block-herdr-server-stop',
family: 'herdr',
},
{
command: 'herdr session stop foo',
ruleId: 'block-herdr-session-stop',
family: 'herdr',
},
{
command: 'herdr session delete bar',
ruleId: 'block-herdr-session-delete',
family: 'herdr',
},
{
command: 'herdr workspace close baz',
ruleId: 'block-herdr-workspace-close',
family: 'herdr',
},
{
command: 'pkill herdr',
ruleId: 'block-pkill-tmux-wezterm',
family: 'pkill',
},
{
command: 'killall herdr',
ruleId: 'block-killall-tmux-wezterm',
family: 'killall',
},
{
command: 'pkill bermuda',
ruleId: 'block-pkill-tmux-wezterm',
family: 'pkill',
},
{
command: 'killall bermuda',
ruleId: 'block-killall-tmux-wezterm',
family: 'killall',
},
🤖 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 `@tests/e2e/e2e.test.ts` around lines 125 - 160, Add the missing `killall
bermuda` case alongside the existing `pkill bermuda` entry in the GROUP H test
cases, using the appropriate killall rule identifier and preserving the
`killall` family so both command variants are covered.

Comment thread tests/e2e/e2e.test.ts
Comment on lines +229 to +238
it('dry-run mode: PIOPANET_DRY_RUN=1 adds dry_run flag to metadata', () => {
const r = runCli('git stash list', 'json');
expect(r.exitCode).toBe(0);
const rec = r.record!;
const metadata = rec.metadata as Record<string, unknown>;
expect(metadata.dry_run).toBe(true);
expect(metadata.pi_opa_net_version).toBeDefined();
expect(typeof metadata.pi_opa_net_version).toBe('string');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the exact package version.

The test only checks that metadata.pi_opa_net_version is a string. It will pass if the CLI emits a stale or hard-coded value. Compare the value with PI_OPA_NET_VERSION or the package.json version.

[details]
[summary]Proposed assertion[/summary]

-    expect(metadata.pi_opa_net_version).toBeDefined();
-    expect(typeof metadata.pi_opa_net_version).toBe('string');
+    expect(metadata.pi_opa_net_version).toBe(PI_OPA_NET_VERSION);

[/details]

🤖 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 `@tests/e2e/e2e.test.ts` around lines 229 - 238, Update the dry-run metadata
test around the `pi_opa_net_version` assertions to compare the emitted version
against the authoritative `PI_OPA_NET_VERSION` or package.json version, rather
than only checking that it is a string. Preserve the existing definedness and
dry_run assertions.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 25 rules
✅ Skills: pi-opa-net

Grey Divider


Remediation recommended

1. Blank version in audits 🐞 Bug ◔ Observability
Description
OtlpAuditSink always emits the pi_opa_net_version key, but the Hermes/Zcode audit entry writers
don’t populate this field, so their OTLP audit records will contain an empty version string instead
of the package version. This breaks the PR’s stated goal of having the exact version in every audit
trace and makes cross-version correlation unreliable for those integrations.
Code

src/audit/OtlpAuditSink.ts[R90-94]

        },
      },
      { key: 'evaluated_at', value: stringValue(entry.evaluated_at) },
+      { key: 'pi_opa_net_version', value: stringValue(entry.pi_opa_net_version) },
    ];
Evidence
The OTLP sink unconditionally appends a pi_opa_net_version kv; when producers omit it,
stringValue() converts undefined to an empty string. The Pi audit writer now sets the version,
but the Hermes/Zcode audit writers do not, so those paths will export blank versions.

src/audit/OtlpAuditSink.ts[36-40]
src/audit/OtlpAuditSink.ts[73-95]
src/pi/audit.ts[39-63]
src/hermes/audit.ts[38-60]
src/zcode/audit.ts[38-60]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`src/audit/OtlpAuditSink.ts` now always exports a `pi_opa_net_version` field in the OTLP kvlist body. However, not all audit-entry producers (notably `src/hermes/audit.ts` and `src/zcode/audit.ts`) include/populate that field, so the sink serializes it as an empty string.

### Issue Context
- The PR adds `pi_opa_net_version` to the OTLP sink output.
- `stringValue()` turns `undefined` into `""`, so missing producer fields become blank version values.
- `src/pi/audit.ts` was updated to include `pi_opa_net_version`, but Hermes/Zcode writers were not.

### Fix Focus Areas
- src/hermes/audit.ts[38-60]
- src/zcode/audit.ts[38-60]
- src/audit/OtlpAuditSink.ts[73-95]

### Suggested fix
1. Update `src/hermes/audit.ts` and `src/zcode/audit.ts` to include `pi_opa_net_version` in their `AuditEntry` interface and populate it when building the entry. Prefer using the already-produced decision metadata when available:
  - `pi_opa_net_version: (input.decision.metadata as any)?.pi_opa_net_version ?? PI_OPA_NET_VERSION`
  - (or just `PI_OPA_NET_VERSION` if you want a single source of truth)
2. (Optional hardening) In `OtlpAuditSink`, consider omitting the kv entirely when the version is missing, but only if you **also** ensure all producers populate it to satisfy the “all audit traces” requirement.
3. Add/extend unit tests around Hermes/Zcode audit writers or OTLP sink inputs to assert the exported kv contains a non-empty `pi_opa_net_version`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines 90 to 94
},
},
{ key: 'evaluated_at', value: stringValue(entry.evaluated_at) },
{ key: 'pi_opa_net_version', value: stringValue(entry.pi_opa_net_version) },
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Blank version in audits 🐞 Bug ◔ Observability

OtlpAuditSink always emits the pi_opa_net_version key, but the Hermes/Zcode audit entry writers
don’t populate this field, so their OTLP audit records will contain an empty version string instead
of the package version. This breaks the PR’s stated goal of having the exact version in every audit
trace and makes cross-version correlation unreliable for those integrations.
Agent Prompt
### Issue description
`src/audit/OtlpAuditSink.ts` now always exports a `pi_opa_net_version` field in the OTLP kvlist body. However, not all audit-entry producers (notably `src/hermes/audit.ts` and `src/zcode/audit.ts`) include/populate that field, so the sink serializes it as an empty string.

### Issue Context
- The PR adds `pi_opa_net_version` to the OTLP sink output.
- `stringValue()` turns `undefined` into `""`, so missing producer fields become blank version values.
- `src/pi/audit.ts` was updated to include `pi_opa_net_version`, but Hermes/Zcode writers were not.

### Fix Focus Areas
- src/hermes/audit.ts[38-60]
- src/zcode/audit.ts[38-60]
- src/audit/OtlpAuditSink.ts[73-95]

### Suggested fix
1. Update `src/hermes/audit.ts` and `src/zcode/audit.ts` to include `pi_opa_net_version` in their `AuditEntry` interface and populate it when building the entry. Prefer using the already-produced decision metadata when available:
   - `pi_opa_net_version: (input.decision.metadata as any)?.pi_opa_net_version ?? PI_OPA_NET_VERSION`
   - (or just `PI_OPA_NET_VERSION` if you want a single source of truth)
2. (Optional hardening) In `OtlpAuditSink`, consider omitting the kv entirely when the version is missing, but only if you **also** ensure all producers populate it to satisfy the “all audit traces” requirement.
3. Add/extend unit tests around Hermes/Zcode audit writers or OTLP sink inputs to assert the exported kv contains a non-empty `pi_opa_net_version`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@buihongduc132
buihongduc132 merged commit befef4c into main Jul 31, 2026
4 of 6 checks passed
@buihongduc132
buihongduc132 deleted the feat/herdr-protection-version-audit-dryrun branch July 31, 2026 05:06
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