Skip to content

Keep generated simulation seams current#103

Open
justin808 wants to merge 8 commits into
mainfrom
codex/sim-seam-generator-durability
Open

Keep generated simulation seams current#103
justin808 wants to merge 8 commits into
mainfrom
codex/sim-seam-generator-durability

Conversation

@justin808

@justin808 justin808 commented Jul 25, 2026

Copy link
Copy Markdown
Member

Update the simulation template so future reseeds preserve the canonical Agent Workflow Configuration pointer and checked config-only maintenance path. The config checker validates allowed paths, shell and Ruby syntax, validator dispatch, canonical pointer text, policy YAML, and the documented command contract. Task/config mixtures and unrelated paths remain rejected.\n\nValidation:\n- focused template tests: 10 runs, 28 assertions, 0 failures\n- generated template seam doctor against merged agent-workflows source: PASS\n- RuboCop: 36 files, 0 offenses\n- clean-environment repository test wrapper: all suites passed

Seam-guard bootstrap boundary

The generated pull_request_target guard is base-trusted and begins protecting a generated repository only after that repository merges its first adoption. That first adoption is an explicit bootstrap boundary; ordinary head-controlled CI is not sufficient proof for it.

Summary by CodeRabbit

  • New Features

    • Added automated checks to distinguish configuration-only changes from task changes and prevent unsupported combinations.
    • Added pull request safeguards to verify protected workflow and validation files.
    • Improved CI routing based on the type of changes detected.
  • Documentation

    • Expanded command documentation for validation and configuration checks.
    • Clarified supported workflow commands and configuration requirements.
  • Tests

    • Added comprehensive coverage for validation, CI safeguards, configuration changes, and stale branches.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The simulation template adds config-only and seam-guard validation, merge-base-aware CI routing, full-history checkout, expanded command documentation, and a comprehensive isolated-repository test suite.

Changes

Simulation Template Enforcement

Layer / File(s) Summary
Configuration contract validation
sim/template/.agents/bin/config-check, sim/template/.agents/bin/README.md
Adds config diff classification, allowlist enforcement, script and YAML validation, canonical documentation checks, and command documentation for config-check and seam-guard.
Trusted seam guard
sim/template/.agents/bin/seam-guard, sim/template/.github/workflows/seam-guard.yml, sim/template/.agents/bin/README.md
Adds pull-request base/head verification, guarded-path enforcement, canonical configuration checks, and the corresponding protected workflow.
CI classification and validation routing
sim/template/.agents/bin/ci, sim/template/.agents/bin/validate, sim/template/.github/workflows/ci.yml, sim/template/AGENTS.md
Routes CI by config-check status, verifies the pinned validator contract, uses merge-base file discovery, and enables full git history.
Template policy regression coverage
sim/test/template_test.rb, .agents/bin/test
Adds isolated repository tests for policy, guard, stale-branch, script-integrity, and validation behavior, and runs them from the standard test script.

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

Sequence Diagram(s)

sequenceDiagram
  participant CI
  participant config_check
  participant validate
  participant test
  CI->>config_check: classify repository changes
  config_check-->>CI: CONFIG_ONLY, TASK_ONLY, or NO_CHANGES
  CI->>validate: validate task changes
  CI->>test: run full test suite for no changes
  validate-->>CI: execute matching task test
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly related to the PR’s main goal of keeping the simulation template and guards up to date.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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/sim-seam-generator-durability

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.

Comment thread sim/template/.agents/bin/config-check Outdated
Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/config-check Outdated
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review

Overview

This PR keeps the simulation template's generated "seams" durable: it aligns the AGENTS.md Agent Workflow Configuration pointer with the real repo's canonical text (cisetup, validate, test, ...), adds a new .agents/bin/config-check script that validates config-only changes (allowed paths, shell/Ruby syntax, validator dispatch, canonical pointer text, policy YAML shape, and README documentation), wires validate to dispatch to config-check when a diff touches only those config paths, updates the README table, and adds a thorough sim/test/template_test.rb covering both the happy path and each rejection case.

Code quality / correctness

  • The new config-check and updated validate are careful: all system/Open3 calls use array-form arguments (no shell interpolation of base_ref or paths), so there's no command-injection risk even though base_ref is externally influenced (via GITHUB_BASE_REF/AGENT_SIM_BASE_REF).
  • The dispatch logic in validate (config-only regex vs. task-only regex) is mutually exclusive and well covered by tests, including the "task + config mixed" rejection case.
  • Left a few inline nits: ARGV.fetch(0) in config-check throws an unhandled IndexError instead of a clean error if invoked without an argument; the allowed-config-paths list is duplicated between the bash regex in validate and the Ruby array in config-check (drift-safe but a maintenance foot-gun); and the Ruby self-syntax-check only silences stdout, not stderr.
  • None of these are blocking — all are minor robustness/DRY suggestions.

Test coverage

sim/test/template_test.rb is thorough: canonical pointer text, valid/invalid policy YAML, non-canonical pointer rejection, validator-dispatch-removed rejection, validator syntax breakage, undocumented README contract, single-task gate still works, and unrelated/mixed-file rejection. It's also wired into .agents/bin/test. This looks like solid coverage for the new surface area.

Security

No injection or path-traversal concerns spotted — paths are hardcoded/joined via File.join, and all subprocess calls use argv-array form rather than shell strings.

Performance

Negligible — this only runs on config-only diffs during validate/CI gating, and does a handful of small file reads plus two system calls.

Overall: small, well-scoped, well-tested change. Nice work.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9aa3bf204e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sim/template/.agents/bin/config-check Outdated
Comment thread sim/template/.agents/bin/ci Outdated
Comment thread sim/template/.agents/bin/config-check Outdated
Comment thread sim/template/.agents/bin/ci
Comment thread sim/test/template_test.rb

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d1fb52d4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sim/template/.agents/bin/ci Outdated
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review: Keep generated simulation seams current (#103)

Overview
Adds a config-check gate to the simulation template's CI (sim/template/.agents/bin/ci) so that config-only changes (README, AGENTS.md, policy YAML, the command scripts themselves) can be validated and merged without requiring a matching lib/task_*.rb change, while still rejecting mixed task/config diffs and unrelated paths. New coverage is added in sim/test/template_test.rb (10 tests) and wired into .agents/bin/test.

What's solid

  • The TASK_ONLY / CONFIG_ONLY / NO_CHANGES dispatch in ci is a clean, explicit state machine, and delegating TASK_ONLY to the existing validate (via AGENT_SIM_BASE_REF) avoids re-resolving base_ref a second time - good, avoids a race between two separate git fetch calls.
  • config-check shells out via array-form system(...)/Open3.capture2 rather than string interpolation into a shell, so there's no shell-injection risk from path/ref values.
  • YAML.safe_load(..., aliases: false) for the policy file is the right call to avoid alias-based YAML bombs.
  • Test coverage is thoughtful: it exercises the 'malicious validator that exits early' and 'modified config-check' tampering scenarios directly, not just the happy path.
  • Verified independently: sha256sum of the checked-out config-check and validate match the hashes pinned in ci/config-check respectively.

Issues found (posted inline)

  1. Root-of-trust gap (sim/template/.agents/bin/ci): the hash-pinning chain is ci -> checks config-check's hash -> config-check checks validate's hash. Nothing checks ci itself, and ci is the actual CI entrypoint. Anyone (or any agent) can edit ci to drop or alter the check, bypassing the entire integrity mechanism without touching either pinned file. If there isn't an out-of-band protection on ci (branch protection/CODEOWNERS on that path, or verification from outside the repo), the 'checked config-only maintenance path' doesn't have an actual anchor.
  2. Two-dot diff for the integrity gate (sim/template/.agents/bin/config-check): git diff --name-only base_commit compares trees directly rather than from the merge-base, so on a branch that hasn't been rebased since main moved forward, unrelated upstream changes can appear in changed_paths and trip false 'unexpected config-only paths' / 'task mixed with config' rejections. This pattern already exists in validate, but this PR extends it into a stricter gate, so it's worth a git merge-base fix here (or at least a known caveat).
  3. Duplicated base_ref resolution logic between ci (new) and validate (pre-existing): same GITHUB_BASE_REF/local-main fallback logic copy-pasted in both. Since validate's bytes are now hash-pinned in config-check, a future fix to this logic has to be applied identically in two places or the scripts silently diverge from each other.
  4. Dead code: template_test.rb's private config_check helper is defined but never called.

Maintainability note (not inline)
The design intentionally hardcodes SHA256 constants for validate (in config-check) and for config-check (in ci). Any legitimate future edit to either script requires manually recomputing and updating the dependent constant, or CI breaks with a hash-mismatch error. That's presumably intended as a tamper-detection mechanism for this sim/testing context, but it's worth being explicit that it creates real ongoing maintenance coupling between three files that must be updated in lockstep.

Nit (no action needed)
sim/template/AGENTS.md's canonical pointer text references a setup command that doesn't exist in this template's .agents/bin/. I initially flagged this, but the root repo's own AGENTS.md has identical generic text and its .agents/bin/ also lacks setup/ci. The '...' and 'a missing script means n/a' wording make this intentional, non-exhaustive boilerplate from the shared seam generator, not a bug.

Comment thread sim/template/.agents/bin/ci Outdated
Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/ci
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review: Keep generated simulation seams current (#103)

Overview

Adds a config-check gate (Ruby) to the sim template that lets a PR touching only a fixed allowlist of config/doc paths (.agents/agent-workflow.yml, .agents/bin/README.md, .agents/bin/ci, .agents/bin/config-check, .agents/bin/validate, AGENTS.md) pass CI without running the full test suite, provided the canonical AGENTS.md pointer text, README rows, YAML policy shape, and pinned SHA-256 hashes of validate/config-check all check out. ci now dispatches to config-check -> TASK_ONLY/CONFIG_ONLY/NO_CHANGES, and a new sim/test/template_test.rb covers the new behavior end-to-end via a scratch git repo.

Critical: incomplete trust chain (see inline comments)

The hash-pinning is meant to stop a PR (in this simulation, an AI worker) from tampering with the enforcement scripts, but ci itself -- the actual GitHub Actions entrypoint (.github/workflows/ci.yml -> run: .agents/bin/ci) -- is never integrity-checked, and is explicitly included in config-check's allowed_paths. A PR can rewrite ci (e.g. to exit 0 unconditionally) and it will either be waved through as a "config-only" change or simply never invoke config-check at all, since the rewritten ci is what controls execution. sim/bin/verify-batch scores off the same GitHub Actions CI conclusion, so this fully defeats the tamper-resistance this PR is trying to add. Left inline comments with two possible fixes (drop ci from the allowlist, or verify it from something outside the PR's own diff).

Other findings

  • Minor duplication: ci's base-ref resolution block (new lines 14-24) is a near copy of validate's existing lines 6-16 -- worth factoring out so the two don't drift.
  • Triplicated canonical text: the "Agent Workflow Configuration" pointer string now lives in three places that must all be updated together -- AGENTS.md (source), config-check's expected_pointer heredoc, and template_test.rb's POINTER constant. Same for the two README rows checked in required_readme_rows. Not a bug, but any future rewording is easy to get out of sync across three files.
  • TOCTOU: config-check hashes validate, and ci hashes config-check, at check time, then later execs the checked file. In the single-process ephemeral CI runner this targets that's low risk, but worth noting since the whole point of this mechanism is tamper-resistance.

Positives

  • The hash I computed locally for both sim/template/.agents/bin/config-check and sim/template/.agents/bin/validate match the pinned values exactly -- the pins are currently correct.
  • sim/test/template_test.rb has solid coverage of the new gate: policy-only changes, invalid YAML, non-canonical pointer, tampered validator, tampered config-check, missing base_ref usage message, invalid script syntax, undocumented README contract, single-task gate preserved, unrelated/mixed-path rejection.
  • frozen_string_literal: true, Open3 usage instead of shelling out with string interpolation, and YAML.safe_load(..., aliases: false) are all good, safe defaults.
  • Doc updates (AGENTS.md, .agents/bin/README.md) are consistent with the new config-check behavior.

Test coverage

Good coverage of the intended success/failure paths for config-check/validate interaction. No test exercises the ci-tampering scenario above, which makes sense since there's currently no mechanism that would catch it -- that's the gap itself.

Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/seam-guard
Comment thread sim/test/template_test.rb
Comment thread sim/template/.agents/bin/config-check
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review summary

What this PR does: Adds a two-tier "seam guard" mechanism to the simulation template (sim/template/) so that config-only maintenance (policy YAML, AGENTS.md pointer, README docs) can be validated and merged without running the full task test suite, while preventing a simulated coding agent's PR from tampering with the CI/validation scripts themselves:

  • .agents/bin/config-check (new): runs as part of the PR's own head-branch ci, classifies a diff as TASK_ONLY / CONFIG_ONLY / NO_CHANGES, and validates the canonical AGENTS.md pointer, policy YAML shape, README contract rows, and script syntax. Pins a SHA-256 of .agents/bin/validate so it can't be silently edited.
  • .agents/bin/ci (updated): pins a SHA-256 of config-check itself before delegating to it, and dispatches to validate/test/exit based on the mode.
  • .agents/bin/seam-guard (new) + .github/workflows/seam-guard.yml (new): a pull_request_target workflow that checks out the base ref (trusted, unmodifiable by the PR) and independently re-validates the diff, rejecting any change to ci, validate, config-check, seam-guard, or the guard workflow itself.
  • sim/test/template_test.rb (new): 232 lines of Minitest coverage for the above.

Overall: This is a solid, thoughtful security design. The core idea — that a pull_request_target job checking out only the base ref is the real trust boundary, since anything running head-branch code can be rewritten by the PR itself — is exactly right, and the workflow correctly avoids executing any head-branch code (it only reads file contents via git show), pins action versions by SHA, uses minimal permissions: contents: read, and verifies the fetched PR head SHA against the event payload to guard against a TOCTOU race. I verified the hardcoded SHA-256 pins in ci and config-check do match the current tracked file contents.

Findings (posted inline)

  1. Allow-list mismatch between config-check and seam-guard (sim/template/.agents/bin/config-check:11-20 vs sim/template/.agents/bin/seam-guard:31-35) — config-check treats ci/config-check/seam-guard/the guard workflow file as legitimate "config-only" paths, but seam-guard (the actual trust boundary) rejects diffs to all of them. Not exploitable today since seam-guard overrides, but it's confusing and would be a real gap if the required-status-check enforcement for seam-guard is ever misconfigured.
  2. ~20 lines of validation logic duplicated between config-check and seam-guard (canonical pointer text, required README rows, policy-YAML-mapping check) — risk of the two drifting out of sync over time.
  3. Test coverage gapseam-guard's duplicated canonical-pointer / YAML / README checks and its TASK_ONLY path have no dedicated tests, unlike the equivalent config-check logic. Since seam-guard is the actual trusted enforcement point, it arguably deserves more coverage, not less.
  4. Minor: Digest::SHA256.file(validator) in config-check will raise an unhandled exception (rather than a clean abort) if .agents/bin/validate is deleted — fails closed either way, just a rougher error message.

Other notes (no action needed)

  • .agents/bin/ci's SHA pin on config-check, and config-check's SHA pin on validate, are not themselves a hard security boundary (an attacker's PR can simply rewrite ci) — the real boundary is seam-guard via required status checks in branch protection, which is outside this diff's scope. Worth confirming that's configured on repos generated from this template.
  • Nice touch: seam-guard never executes any head-branch code, only reads file contents via git show <sha>:<path>, which sidesteps the classic pull_request_target code-execution pitfall entirely.

Comment thread sim/template/.agents/bin/ci
Comment thread sim/template/.agents/bin/ci Outdated
Comment thread sim/template/.agents/bin/config-check Outdated
Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/ci
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review summary

This PR replaces the previous all-or-nothing gate (any non-task-file change made validate fail outright) with a three-way config-check classifier (TASK_ONLY / CONFIG_ONLY / NO_CHANGES) plus a new seam-guard workflow that runs on pull_request_target against the base ref, so a malicious/careless head PR can't tamper with ci, validate, config-check, or the workflows themselves to bypass the gate. I verified this is the actual trust boundary: sim/template/.github/workflows/ci.yml triggers on plain pull_request and runs the head-controlled .agents/bin/ci, so the SHA-256 pins embedded in ci/config-check can't stop a PR that simply edits ci itself — protection only holds because seam-guard.yml re-runs the equivalent check from the trusted base commit. That's a sound design, verified the pinned hashes (sha256sum of config-check and validate) match the current file contents.

Code quality

  • The base_ref resolution block in sim/template/.agents/bin/ci (lines 14–24) is copy-pasted verbatim from validate. Worth extracting to a shared helper to avoid drift (inline comment).
  • The two hardcoded SHA-256 pins (ci's expected_config_check_sha256, config-check's expected_validator_sha256) have no comment explaining their purpose or how to regenerate them — a legitimate future edit to either script will break CI with a cryptic message (inline comments).
  • config-check's allowed_paths intentionally allows edits to ci/config-check/seam-guard/the workflow file that seam-guard's config_paths (a strict subset) rejects. The asymmetry is correct but undocumented (inline comment).

Behavior / risk

  • On CONFIG_ONLY, ci exits 0 without running the minitest suite at all — likely intentional given config-check's deep content validation, but worth a conscious confirmation (inline comment).
  • This PR's guarantees depend on the "Seam Guard" check being configured as a required status check in branch protection — that's a repo setting, not something visible in the diff, so worth confirming separately.

Security

  • No injection issues found: all git/bash/ruby subprocess calls use array-form system/Open3.capture2 (no shell interpolation), YAML.safe_load(..., aliases: false) is used for the untrusted policy file, actions are pinned to full commit SHAs, and the new workflow's permissions: contents: read keeps blast radius minimal. The seam-guard.yml input validation (case "$BASE_SHA:$HEAD_SHA:$PR_NUMBER") correctly guards against malformed values before they reach git fetch.

Tests

  • sim/test/template_test.rb is thorough — covers canonical pointer text, policy YAML validation, tamper detection for validate/config-check, syntax checks, and both TASK_ONLY/CONFIG_ONLY paths for both config-check and seam-guard. I wasn't able to execute the suite in this sandbox (command execution was gated), but manual tracing through the logic and hash verification didn't turn up discrepancies with the PR description's reported results.

Overall: solid, well-tested change with a sensible security model. Findings above are mostly maintainability/documentation nits rather than correctness bugs.

Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/seam-guard
Comment thread sim/template/.agents/bin/seam-guard
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review summary

This PR adds a config-only maintenance path (config-check) for the simulation template's CI, plus a base-trusted pull_request_target guard (seam-guard + .github/workflows/seam-guard.yml) that prevents head-controlled PRs from silently rewriting CI/validator entrypoints. Overall the design direction (SHA256-pinning the validator contract, verifying the fetched PR head SHA matches the event payload, contents: read + persist-credentials: false on the guard workflow) is sound and shows real attention to the pull_request_target threat model.

I left 3 inline comments on concrete issues:

  1. Likely bug (sim/template/.agents/bin/config-check:28): config-check diffs with git diff <base_commit> (a two-dot diff against the working tree) instead of a merge-base diff, unlike seam-guard which correctly uses git merge-base(base, head) before diffing. Since .agents/bin/ci re-resolves base_ref to the current tip of the base branch on every run, this means once main advances past the point where a branch forked (a routine occurrence), config-check can pick up files that changed on main but were never touched by the PR — causing spurious "Unexpected config-only paths" / task-mixing rejections. The new template_test.rb suite always sets base_ref="HEAD", so this divergence case isn't exercised by the added tests.
  2. Duplication (config-check vs seam-guard): the canonical AGENTS.md pointer text, the README-row list, and the YAML/task-partition checks are duplicated near-verbatim between the two scripts, risking silent drift between the head-run and base-trusted validation paths.
  3. Possible maintenance dead-end: seam-guard's guarded path allowlist is a strict subset of config-check's — meaning once seam-guard is required, no PR can ever touch ci/config-check/seam-guard/validate/the workflow file again (e.g. to roll the pinned hashes), since seam-guard will always reject that as an "unexpected guarded path." Worth confirming this is the intended trade-off and documenting the escape hatch (admin bypass?).

Other notes:

  • Both pinned SHA256 hashes (expected_validator_sha256 in config-check, expected_config_check_sha256 in ci) were verified to match the actual current file contents — no bug there.
  • The seam-guard.yml workflow's input sanitization (case "$BASE_SHA:$HEAD_SHA:$PR_NUMBER" in *[!0-9a-f:]*...) plus explicit re-fetch-and-compare of the PR head SHA is a good defense against both injection and TOCTOU races on the ref.
  • Test coverage for the new scripts is solid overall (10 template tests covering config/task classification, tampering detection, syntax checks) — the one gap is the base-drift scenario noted above.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d46a257bd2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/ci
Comment thread sim/template/.agents/bin/seam-guard
Comment thread sim/template/.agents/bin/config-check
Comment thread sim/template/.agents/bin/ci Outdated
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review: Keep generated simulation seams current (#103)

Overview

This PR adds a two-layer guard to the sim/template simulation repo template: a head-controlled config-check (invoked from ci) that classifies a PR's diff as TASK_ONLY / CONFIG_ONLY / NO_CHANGES, and a new base-trusted seam-guard script wired up via a pull_request_target workflow (seam-guard.yml) that independently re-validates the same properties from the base branch, so a malicious head branch can't forge the classification. AGENTS.md's canonical pointer text and the .agents/bin/README.md command table are updated to match, and a new sim/test/template_test.rb covers the new scripts with 28 assertions.

Strengths

  • seam-guard.yml follows the secure pull_request_target pattern correctly: checks out base.sha (not head), never executes any code fetched from the PR head, and only reads file contents via git show <sha>:<path> for its checks.
  • The workflow validates BASE_SHA/HEAD_SHA/PR_NUMBER come through env: (not interpolated directly into the run: script) and re-verifies the fetched PR head SHA against the event payload before running the guard — solid protection against both the classic pull_request_target injection vector and a TOCTOU race if the PR is updated mid-run.
  • YAML.safe_load(..., aliases: false) for the policy file avoids YAML alias-expansion issues.
  • Good test coverage of the new classification logic itself (task/config mixing, unrelated paths, tampering detection, stale-branch handling).

Issues raised inline

  1. Possible permanent lockout of the protected files (sim/template/.agents/bin/seam-guard, config_paths) — the allowlist doesn't include .agents/bin/ci, config-check, seam-guard, validate, or the two workflow YAMLs. Once seam-guard.yml is a required check, no PR can ever modify those files again (confirmed by the new rejects_malicious_ci test, which fires for any change, not just malicious ones). Worth confirming this one-way-door is intentional.
  2. git merge-base fragility in config-check — depends on an implementation detail of GitHub's default pull_request shallow-checkout behavior that breaks for unmergeable PRs or if ci.yml's checkout is ever hardened to head.sha. The existing validate script avoids this by diffing directly against the fetched base ref instead of computing a merge-base. This code path also isn't exercised by the new tests (AGENT_SIM_BASE_REF=HEAD bypasses it).
  3. SHA256 pin maintenance (ciconfig-checkvalidate) — two hand-maintained hashes with no regeneration helper, and the ci-side pin has no real security value outside the seam-guard (base-trusted) execution path since an attacker who controls ci on head can just edit the check itself.

Minor

  • The canonical pointer markdown and required_readme_rows arrays are duplicated verbatim across config-check, seam-guard, and template_test.rb. Likely intentional (keeps seam-guard self-contained/independent of anything head-controlled), but it's a triple-maintenance point if the canonical text changes.
  • AGENTS.md's pointer text lists setup as an example command, but no setup script exists in the template — covered by the "missing script means n/a" caveat, so not wrong, just a little confusing on first read.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02d5e89aa5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


# Keep these duplicated checks aligned with config-check. Duplication is
# intentional: this base-trusted guard must never require candidate code.
agents = head_file(root, head_commit, "AGENTS.md")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate configuration against the would-be merge tree

When a configuration-only PR is based on an older revision of the guarded contract, the diff correctly ignores base-only changes, but these checks still read every required file from the stale head commit. For example, after the canonical AGENTS.md pointer is updated on the base branch, an older policy-only PR is rejected for retaining the previous pointer even though merging it would preserve the valid base version. Build or inspect the prospective merge tree, or read unchanged configuration files from the base commit, so stale configuration branches are evaluated as they would actually merge.

Useful? React with 👍 / 👎.

with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 0
persist-credentials: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

persist-credentials: false on this checkout means no auth token is left in the local git config. The run: step below then does git fetch --no-tags origin "+refs/pull/${PR_NUMBER}/head:..." against origin — on a private repository this fetch will fail (401/403, "repository not found") because there's no credential available for the anonymous HTTPS remote. This works fine for public repos, but since this template is meant to be reused broadly ("future reseeds"), it's worth confirming target repos are always public, or otherwise passing a token explicitly to the fetch (e.g. via git -c http.extraheader=... scoped to just this command) rather than relying on persisted checkout credentials.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review

Overview

This PR adds a two-tier CI security model to the simulation template used to seed generated agent-eval repos:

  • .agents/bin/ci now dispatches based on .agents/bin/config-check output (TASK_ONLY → run the matching task test, CONFIG_ONLY → pass, NO_CHANGES → run the full suite).
  • .agents/bin/config-check (new, head-controlled) is a "local drift" gate: it restricts config-only diffs to an explicit allowlist, hash-pins .agents/bin/validate's content, verifies script executability/syntax, and checks the canonical AGENTS.md pointer / policy YAML / README rows.
  • .agents/bin/seam-guard (new) + .github/workflows/seam-guard.yml (new) form the actual trust boundary: a pull_request_target job checked out at the base SHA, with contents: read only, that never executes PR-supplied code — it only reads file contents via git show and diffs paths. Its allowlist for non-task changes is intentionally narrower than config-check's (excludes ci, validate, config-check, seam-guard, and the workflow files), forcing changes to those entrypoints through an explicit "bootstrap PR" process documented in the new .agents/bin/bin/README.md section.
  • sim/test/template_test.rb (new, 24 test methods) exercises both scripts against a throwaway git repo cloned from the template, covering mixed task/config diffs, stale-branch merge-base handling, syntax/executable-bit tampering, hash-pin drift, and canonical-pointer/README drift.

Strengths

  • The design correctly identifies that the real security boundary can only be a pull_request_target job pinned to the base ref that never executes head-controlled code — seam-guard only ever reads files via git show, never execs anything from the PR. That's the right approach for this kind of self-hosted gate.
  • permissions: contents: read on the trusted job is good least-privilege practice.
  • The workflow re-fetches the PR's actual head ref by number and asserts it matches the event's head.sha before diffing, guarding against a stale/raced head.sha.
  • Good-faith duplication between config-check and seam-guard (canonical pointer text, README rows, policy-YAML check) is explained inline and is covered by tests on both sides, so drift between the two would be caught by CI rather than silently diverging.
  • validate/config-check/seam-guard all switched from git diff base_ref to git diff $(git merge-base base_ref HEAD), and ci.yml/ci fetch full history accordingly — this fixes a real correctness gap where a stale/diverged base ref would misreport the changed-file set.
  • I verified the two SHA-256 pins in ci (config-check) and config-check (validate) against the actual committed file contents — both match.

Issues

  1. Possible break on private repos (sim/template/.github/workflows/seam-guard.yml, inline comment posted) — persist-credentials: false on the checkout, combined with a later plain git fetch origin ... in the run: step, will fail authentication on a private repository since no token is persisted for that fetch. Works for public repos; worth confirming that's the only intended target, or fetching with an explicit token scoped to that command.

  2. Operational dependency not covered by this diff: the entire value of seam-guard depends on a generated repo's branch protection marking the "Seam Guard / guard" check as required. Nothing in this PR (understandably, since it's template-side) enforces or verifies that, and it's easy for an adopting repo to merge the seam-guard workflow without ever making it a required check, silently leaving the config-only allowlist in config-check (the wider, head-controlled one) as the only enforcement. Given the PR body already calls out the "bootstrap boundary" limitation, it'd be worth also documenting this required-check dependency explicitly (e.g., in the README addition or the bootstrap section) so it isn't missed during rollout.

  3. Minor/doc: .agents/bin/README.md's row for ci still reads "GitHub Actions entrypoint." — accurate but now understates what it does (mode dispatch to config-check/validate/test). Not a blocker, just slightly stale relative to the detail added for the other rows in the same table.

Test coverage

The new sim/test/template_test.rb suite is thorough — it covers both the untrusted (config-check) and trusted (seam-guard) paths independently, including negative cases (invalid YAML, non-canonical pointer, non-executable/invalid-syntax scripts, mixed task+config diffs, tampered hash-pinned files) and the merge-base-vs-stale-branch scenario for all three of validate/config-check/seam-guard. I wasn't able to execute the suite in this sandboxed review environment, but static inspection didn't turn up logic that would contradict the PR description's reported "10 runs, 28 assertions" (the file currently has 24 test_ methods, which suggests that count may be from an earlier revision — worth double-checking the PR description numbers are still current).

Security implications

No command injection or unsafe eval/system interpolation found — all git/ruby/bash invocations use Open3.capture2/system with array-form arguments (no shell interpolation of untrusted values), and the one place raw shell interpolation occurs (seam-guard.yml's case guard on BASE_SHA/HEAD_SHA/PR_NUMBER) is defense-in-depth on values GitHub already constrains to SHA/integer format.

@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 (2)
sim/test/template_test.rb (1)

335-341: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Helper naming: validate wraps ci, not .agents/bin/validate.

The validate private helper actually invokes .agents/bin/ci (line 338), while config_check/seam_guard helpers invoke their namesake binaries directly. This is intentional (testing the full ci-routing gate), but the name can mislead readers into thinking it calls .agents/bin/validate directly.

🤖 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 `@sim/test/template_test.rb` around lines 335 - 341, Rename the private helper
validate to a name that clearly indicates it invokes the full .agents/bin/ci
routing gate, and update all references to the helper accordingly. Leave the
Open3 invocation and the config_check/seam_guard helpers unchanged.
sim/template/.agents/bin/ci (1)

16-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated base_ref fallback logic between ci and validate, without a sync-comment. Both scripts implement the identical AGENT_SIM_BASE_REFGITHUB_BASE_REF fetch → main/origin/main fallback chain; unlike the other intentional duplications in this PR (e.g., config-check/seam-guard canonical-pointer checks), neither copy carries a "keep these aligned" comment, so a future edit to one could silently diverge from the other.

  • sim/template/.agents/bin/ci#L16-L26: extract the fallback chain into a small sourced helper shared with validate, or add a "keep aligned with validate" comment.
  • sim/template/.agents/bin/validate#L6-L16: extract into the same shared helper, or add a "keep aligned with ci" comment.
🤖 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 `@sim/template/.agents/bin/ci` around lines 16 - 26, Keep the duplicated
base_ref fallback logic in sim/template/.agents/bin/ci lines 16-26 and
sim/template/.agents/bin/validate lines 6-16 synchronized by either extracting
it into one shared sourced helper used by both scripts, or adding explicit “keep
aligned” comments to both copies; do not leave either site without
synchronization guidance.
🤖 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 `@sim/test/template_test.rb`:
- Around line 208-214: The test_validate_rejects_invalid_validator_syntax
currently triggers the validator hash-mismatch check rather than syntax
validation. Retarget it to a non-hash-pinned script, following
test_config_check_rejects_invalid_test_script_syntax, so the appended invalid
syntax reaches the bash -n branch; alternatively, rename the test and assert the
hash-mismatch message.

---

Nitpick comments:
In `@sim/template/.agents/bin/ci`:
- Around line 16-26: Keep the duplicated base_ref fallback logic in
sim/template/.agents/bin/ci lines 16-26 and sim/template/.agents/bin/validate
lines 6-16 synchronized by either extracting it into one shared sourced helper
used by both scripts, or adding explicit “keep aligned” comments to both copies;
do not leave either site without synchronization guidance.

In `@sim/test/template_test.rb`:
- Around line 335-341: Rename the private helper validate to a name that clearly
indicates it invokes the full .agents/bin/ci routing gate, and update all
references to the helper accordingly. Leave the Open3 invocation and the
config_check/seam_guard helpers 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ba33e5f-36c6-4180-a897-326ae568571e

📥 Commits

Reviewing files that changed from the base of the PR and between be503f0 and 02d5e89.

📒 Files selected for processing (10)
  • .agents/bin/test
  • sim/template/.agents/bin/README.md
  • sim/template/.agents/bin/ci
  • sim/template/.agents/bin/config-check
  • sim/template/.agents/bin/seam-guard
  • sim/template/.agents/bin/validate
  • sim/template/.github/workflows/ci.yml
  • sim/template/.github/workflows/seam-guard.yml
  • sim/template/AGENTS.md
  • sim/test/template_test.rb

Comment thread sim/test/template_test.rb
Comment on lines +208 to +214
def test_validate_rejects_invalid_validator_syntax
File.open(File.join(@repo, ".agents/bin/validate"), "a") { |file| file << "\nif\n" }

_out, _err, status = validate

refute status.success?
end

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

Test doesn't actually exercise syntax validation — it hits the validator hash-pin instead.

config-check's validator SHA256 check runs unconditionally and precedes the bash -n syntax check, so appending if\n to .agents/bin/validate will always fail via "Simulation validator does not match the checked contract." (already covered by test_ci_rejects_malicious_validator_that_exits_early), never reaching the syntax-check branch this test's name implies. The test still passes because it only asserts refute status.success?.

Either assert on the actual message produced (hash-mismatch), or retarget this test at a non-hash-pinned script (as test_config_check_rejects_invalid_test_script_syntax does for .agents/bin/test) to genuinely cover the syntax-check path.

🤖 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 `@sim/test/template_test.rb` around lines 208 - 214, The
test_validate_rejects_invalid_validator_syntax currently triggers the validator
hash-mismatch check rather than syntax validation. Retarget it to a
non-hash-pinned script, following
test_config_check_rejects_invalid_test_script_syntax, so the appended invalid
syntax reaches the bash -n branch; alternatively, rename the test and assert the
hash-mismatch message.

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