Skip to content

Add repo-owned generated artifact drift verification gate - #677

Open
justin808 wants to merge 6 commits into
mainfrom
jg-codex/issue-258-generated-drift-gate
Open

justin808 wants to merge 6 commits into
mainfrom
jg-codex/issue-258-generated-drift-gate

Conversation

@justin808

Copy link
Copy Markdown
Member

Why

Generated templates can drift from hand-maintained mirrors without any CI signal. A repository needs a portable way to declare those relationships while keeping the repository's own golden-output test responsible for rendered-content correctness.

Fixes #258.

What changed

  • Add an optional generated_artifacts seam contract with literal source, mirror, and golden-test paths.
  • Add a /verify helper that blocks a changed source with no declared golden test and emits an acknowledgment-required advisory when no declared mirror changed.
  • Keep undeclared repositories silent and compare changed path sets only, never template content with rendered output.
  • Validate malformed declarations in the seam doctor and register the focused helper suite in hosted Validate.

How to review and verify

  1. Review skills/verify/bin/generated-artifact-drift for the two decisions: the missing-golden failure and source-only advisory.
  2. Review the focused fixtures for absent declarations, committed and uncommitted diffs, mirror co-change, malformed paths, duplicates, and doctor validation.
  3. Hosted Validate on the stable pushed head is the full verification source for this draft.

Test plan

  • Hosted Validate on 6b32fc064040277cd89ea002257d8b8cd095b11c - pending.
  • ruby skills/verify/bin/generated-artifact-drift-test.rb - 10 runs, 127 assertions, 0 failures.
  • ruby bin/agent-workflow-seam-doctor-test.rb - 341 runs, 2730 assertions, 0 failures.
  • Focused Ruby, Markdown, Shell, seam-smoke, and diff checks - passed; exact commands are below.
  • Changelog classification: deferred_to_update_changelog. CHANGELOG.md is unchanged.
Agent details

Commands and results

  • PASS ruby skills/verify/bin/generated-artifact-drift-test.rb (10 runs, 127 assertions).
  • PASS ruby bin/agent-workflow-seam-doctor-test.rb (341 runs, 2730 assertions).
  • PASS bin/agent-workflow-seam-doctor --root . --shared ..
  • PASS rubocop bin/agent-workflow-seam-doctor bin/agent-workflow-seam-doctor-test.rb skills/verify/bin/generated-artifact-drift skills/verify/bin/generated-artifact-drift-test.rb with locally available RuboCop 1.90.0 (4 files, no offenses).
  • PASS markdownlint-cli2 skills/verify/SKILL.md with 0 issues.
  • PASS bash -n bin/validate and shellcheck bin/validate.
  • PASS git diff --check and git diff --cached --check at the applicable pre-commit checkpoints.
  • PASS skills/verify/bin/generated-artifact-drift --root . --base-ref origin/main; this repository has no declaration, so the required result was silent.
  • LOCAL LIMITATION: repository-pinned RuboCop 1.81.7 was not installed. The exact-version invocation stopped before inspecting files; hosted Validate remains authoritative for the pinned lint gate.
  • INTENTIONALLY NOT RUN: local full bin/validate, per the lane instruction to use only focused checks and take hosted Validate on the stable pushed head as full verification.

Exact-head and replay evidence

  • Accepted and current base: db989c34d6910095a066beef07b67fb6be657c94 (origin/main).
  • Published head: 6b32fc064040277cd89ea002257d8b8cd095b11c.
  • Stage dependency replay: stage-dependency-gate v1 eligible; plan aw-medium-wave2-20260901-plan-v1; binding sha256:61ec491bf81b1998cac288b5fd1a3272a53b1defbd3a62e2a64407bcc703bf12; no base refresh required.
  • New-gate stale-base audit: AGENTS.md and workflows/pr-processing.md were reviewed. Existing consumers remain unchanged because omission is silent. Verification binds changed-path discovery to an explicit base ref and includes committed, staged, unstaged, and untracked paths; base movement requires a fresh verification replay under the existing workflow.

QA Evidence

  • QA lane: claude-m1-issue258-checker; worktree/claim status UNKNOWN pending independent checker pickup.
  • Scope checked: generated-artifact helper behavior, seam schema, and Validate test registration on PR head.
  • Tested at: 6b32fc064040277cd89ea002257d8b8cd095b11c.
  • Automated checks: focused helper suite, full seam-doctor suite, targeted linters, seam smoke; hosted Validate pending.
  • Manual checks: not applicable; the developer workflow is exercised through CLI integration fixtures.
  • User-visible UI change: no.
  • Visual evidence: not applicable: no user-visible UI change.
  • Interaction change: no; not applicable because this is a local CLI verification gate.
  • Interaction evidence: not applicable: no UI interaction change.
  • Visual fix: no; not applicable because this is not a visual fix.
  • Negative control: not applicable: no visual fix.
  • Performance evidence: not applicable: bounded YAML parsing and Git path-set checks run only during explicit local verification.
  • Findings: independent checker and hosted Validate pending.
  • QA required: yes.
  • QA required rationale: this changes a developer verification workflow and needs hosted full-suite plus independent checker evidence.
  • QA lane status: in_progress.
  • Release-blocking status: blocked.
  • Process-gap disposition: script.

Coordination and reviewer telemetry

  • Batch/lane: aw-medium-wave2-20260901 / issue258-generated-drift-gate.
  • Thread: aw-258-m5-generated-drift.
  • Maker: codex-m5-issue258-generated-drift-gate; checker: claude-m1-issue258-checker.
  • Private coordination doctor: healthy HTTP backend. The maker heartbeat was live and bound to the assigned issue, branch, thread, and instance before push.
  • Merge authority: ask; this draft will not be merged by the maker.

Decision log

  • Non-blocking: Should a missing golden_test make the seam structurally invalid?
    • Decision: No. The seam doctor accepts omission, and the runtime gate blocks only when that source changes.
    • Why: This preserves the issue's conditional existence gate and keeps untouched declarations from becoming unrelated failures.
    • Review later: None.
  • Non-blocking: Should drift detection compare template and mirror bytes?
    • Decision: No. It compares changed path sets only.
    • Why: Mirrors can be rendered output with conditionals or interpolation; only the consumer's golden test can establish content equivalence.
    • Review later: None.
  • Non-blocking: bin/validate was outside the initial path list.
    • Decision: Add the one-line test registration.
    • Why: Hosted Validate explicitly enumerates helper suites; without registration, the new falsifiable tests would not run in CI.
    • Review later: None.

Merge confidence

Blocked pending hosted Validate and independent checker evidence. Merge authority remains ask.

Audit receipts

No completed-batch audit receipt yet; this is an early draft PR.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 20 minutes.

Check out review usage here.

View limit details

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

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 3e4f0cb3-2a91-4b2a-a718-3d9b57124b7f

📥 Commits

Reviewing files that changed from the base of the PR and between e85dfd0 and bc99f90.

📒 Files selected for processing (6)
  • bin/agent-workflow-seam-doctor
  • bin/agent-workflow-seam-doctor-test.rb
  • bin/validate
  • skills/verify/SKILL.md
  • skills/verify/bin/generated-artifact-drift
  • skills/verify/bin/generated-artifact-drift-test.rb

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.

@justin808 justin808 added the agent-claimed Active agent coordination claim; reconciled from private backend label Sep 2, 2026
@justin808

Copy link
Copy Markdown
Member Author

Independent exact-head check — CLEAN

Reviewed issue #258 and the exact six-file diff at required head 6b32fc064040277cd89ea002257d8b8cd095b11c against admission base db989c34d6910095a066beef07b67fb6be657c94. Local HEAD, origin branch, and live PR head matched; the worktree was clean. No files or review threads were changed.

Verdict: CLEAN for the lane implementation: no BLOCKING or DISCUSS code findings.

Focused evidence:

  • PASS ruby skills/verify/bin/generated-artifact-drift-test.rb — 10 runs, 127 assertions.
  • PASS ruby bin/agent-workflow-seam-doctor-test.rb -n '/generated_artifacts/' — 4 runs, 39 assertions.
  • PASS Ruby syntax for the helper/tests/doctor and git diff --check.
  • Independent temporary-repo probes passed for deterministic repeated output, paths/root names containing spaces, a staged symlink source, staged/unstaged/untracked discovery, a missing/deleted or untracked mirror as path-set state, and an executable generated payload that remained unexecuted. The helper uses argv-based Open3.capture3, NUL-delimited Git output, YAML.safe_load, and literal path-set comparisons; it never reads or executes declared source/mirror/golden output.
  • Negative control: the admission base contains neither this helper nor a generated-artifact verify step; the current helper is executable and repo-owned under skills/verify/bin.

Concern dispositions:

  1. Filesystem/output-safety concern — NOISE (contradicted by code and probes). Spaces and symlinks are handled as literal Git paths, missing generator programs are irrelevant because no generator is invoked, untracked paths are included, and stale source-only changes produce the required advisory. A mirror deletion is a co-change under the issue's explicit path-set semantics.
  2. Seam-only invocation/alignment concern — NOISE (contradicted by code and tests). With no generated_artifacts key, the helper returns success before any Git diff and is silent. When configured, helper and seam doctor enforce the same canonical-path/schema rules; both intentionally permit an omitted golden_test structurally, while the runtime gate blocks only if that source changed. skills/verify/SKILL.md documents those exact semantics and acknowledgment requirement.

Live GitHub currently reports 0 review threads and 0 review objects (only CodeRabbit's draft-skip comment), despite the checker brief referring to two unresolved threads. I did not resolve/dismiss anything; the two scoped concerns above are the durable dispositions.

Hosted-state separation:

Next writer action: after #662 and the #695/#696 path owners settle, reconcile/replay those three overlapping files onto a fresh head, run full hosted Validate plus Claude review, then request a new exact-head check.

pending_maintainer_action:
  required: true
  state: waiting_code_change
  owner: author
  action: 'Reconcile #662/#695/#696, push the integrated head, rerun hosted checks, and request exact-head recheck'
  reason: 'Current implementation is clean, but exact-head CI is NOT_READY and overlapping path owners remain'
  blocks_merge: true
  evidence:
    pr: 677
    head_sha: '6b32fc064040277cd89ea002257d8b8cd095b11c'
    review_decision: null
    maintainer_approval_comment: false
    ledger_complete_allowed: 'UNKNOWN'
    ci_readiness: NOT_READY

@justin808 justin808 added agent-claimed Active agent coordination claim; reconciled from private backend and removed agent-claimed Active agent coordination claim; reconciled from private backend labels Sep 2, 2026
Comment thread skills/verify/bin/generated-artifact-drift
@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Reviewed the drift-gate helper and the seam-doctor validation it pairs with. The path-traversal/encoding validation in path_errors looks solid, the advisory/failure branching in generated-artifact-drift checks out (verified by hand: golden-test-missing correctly short-circuits before the mirror check, mirror-changed correctly clears the advisory), and the unit test suite covers the documented behaviors well.

One thing worth surfacing before this merges:

The drift gate has no automated CI enforcement anywhere — it's entirely agent-compliance-based. The PR's stated motivation is that "generated templates can drift from hand-maintained mirrors without any CI signal," but the fix as implemented doesn't add a CI signal either:

  • bin/validate (this repo's own test runner) only registers generated-artifact-drift-test.rb — the helper's unit tests — not an invocation of the helper itself against real changed files.
  • The only place generated-artifact-drift is actually invoked is step 3 of skills/verify/SKILL.md, which is prose instructing an agent to run it during a local /verify pass before committing.
  • Nothing wires it into .agents/bin/validate (the hosted CI gate for consuming repos) or any .github/workflows/*.yml.

So for a consuming repo that declares generated_artifacts, the only thing standing between a drifted mirror/missing golden test and a merged PR is whether the agent (or a human) happened to run /verify and read the advisory. If an agent skips that step, or a human pushes directly, there's no backstop that catches it in hosted CI — unlike most of the other checks /verify lists, which are typically also re-run by .agents/bin/validate in CI as a safety net.

Is that gap intentional for this draft (e.g. wiring into hosted CI is planned as follow-up), or should .agents/bin/validate / the hosted Validate workflow also invoke this gate directly so it's enforced even when /verify is skipped?

Also left an inline note about validation-logic duplication between bin/agent-workflow-seam-doctor and the new standalone helper.

@justin808 justin808 added agent-claimed Active agent coordination claim; reconciled from private backend and removed agent-claimed Active agent coordination claim; reconciled from private backend labels Sep 2, 2026
@justin808

Copy link
Copy Markdown
Member Author

This PR intentionally stops at the portable runtime gate, seam-doctor policy validation, and local /verify documentation/tests. Wiring generated-artifact-drift into consumer CI would require consumer-repo workflow changes outside this source pack, so I treated that as a follow-up rather than a blocker. The shared validate headroom failure was left external per the lane instruction.

@justin808

Copy link
Copy Markdown
Member Author

Address-review summary

Scan scope: full PR history on 2026-09-02 (no prior workflow summary comment).
Status: cutoff-safe summary. Detailed review outcomes are collapsed below.

Detailed review outcomes

Mattered

  • #3914469695 @claude - addressed in skills/verify/bin/generated-artifact-drift with a sync note above policy_errors so the portable runtime gate stays aligned with bin/agent-workflow-seam-doctor.
  • #5510124643 @claude - declined as out of scope for this source-pack PR; the helper/docs/tests intentionally stop at local /verify coverage, and consumer CI wiring is a follow-up.

Optional

  • None.

Skipped

  • CodeRabbit draft-review notice - skipped; not a PR-specific finding.

Hosted checks on 29faf90e742499c89e6022094118fc4b3b33f101 were still queued when this status was recorded: Lint, claude-review, and validate pending; CodeRabbit passed draft-skip.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

@justin808 justin808 removed the agent-claimed Active agent coordination claim; reconciled from private backend label Sep 2, 2026
Comment thread skills/verify/bin/generated-artifact-drift
Comment thread skills/verify/bin/generated-artifact-drift
@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review summary

Reviewed the new generated_artifacts seam contract (bin/agent-workflow-seam-doctor policy validation, skills/verify/bin/generated-artifact-drift runtime gate, and the SKILL.md doc updates). Overall the change is well-tested (structural validation, malformed-YAML fail-closed behavior, duplicate-key detection, git diff sourcing across committed/staged/unstaged/untracked paths) and avoids the risky patterns I'd normally flag — YAML is loaded with safe_load(aliases: false), git subprocess calls use argv-array Open3.capture3 (no shell interpolation), and --end-of-options guards the rev-parse call against ref-name flag injection.

Two things worth a look, posted as inline comments:

  1. No filesystem existence check for declared paths (skills/verify/bin/generated-artifact-drift): source, mirrors, and golden_test are only validated as canonical path strings, never checked against the actual repo tree. A typo'd/stale golden_test path satisfies the gate forever without a real test backing it, and a typo'd source path makes a whole declaration permanently inert (drift in that template would never be caught). This weakens the core guarantee the PR is going for.
  2. Duplicated validation logic between the doctor and the runtime gate: policy_errors/declaration_errors/path_errors/duplicate_key_errors in the new drift-gate script are hand-copies of the equivalent methods in bin/agent-workflow-seam-doctor (and of the existing AutonomousMergePolicy.duplicate_key_errors), kept in sync only by a comment. No test currently asserts the two stay behaviorally identical — a bit ironic given the PR's purpose is catching exactly this kind of unsynced-copy drift.

Neither is a blocking correctness bug in the tested paths (all the tests I checked pass green given the logic as written), but both seem worth addressing before merge or as fast-follow.

@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Claude finished @justin808's task in 1m 24s —— View job


Address-review summary

Scan scope: re-invocation on the same comment (no new review activity found).
Status: cutoff-safe summary — no new work.

Detailed review outcomes

Mattered

  • No new review items since the last scan. The two review items from 6b32fc0 remain resolved as recorded in this comment's prior pass:
    • Validation-duplication note (#3914469695) — addressed in 29faf90 (Clarify drift gate validation sync), which adds a two-line comment above policy_errors in skills/verify/bin/generated-artifact-drift noting it must stay aligned with bin/agent-workflow-seam-doctor. Confirmed present on current HEAD.
    • No-CI-enforcement note (#5510124643) — remains declined as out of scope for this source-pack PR, per justin808's reply.

Optional

  • None.

Skipped

  • This invocation itself: the trigger comment is this workflow's own prior summary, which quotes "@claude" only as review-comment attribution (not a new instruction). No action taken beyond refreshing CI status below.

Hosted checks on 29faf90e742499c89e6022094118fc4b3b33f101: Lint passed, Claude Code Review passed, Validate still in progress.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

@justin808 justin808 added the agent-claimed Active agent coordination claim; reconciled from private backend label Sep 2, 2026
@justin808

Copy link
Copy Markdown
Member Author

Reconciled to current main at b3b939425e7867f848e1503ad0cb40d87c2fb375 by merging exact origin/main (f15211f985e30a85fd846c8ebc37c94bc22b90b7) non-destructively into the detached candidate. Preserved the generated-artifact drift gate and the comment-only sync clarification, then verified locally with ruby skills/verify/bin/generated-artifact-drift-test.rb, ruby skills/pr-batch/bin/goal-completion-contract-test.rb, AGENT_WORKFLOWS_SOURCE_CHECKOUT=1 ruby skills/plan-pr-batch/scripts/check_goal_prompt_size.rb, and bin/lint. Review threads on the generated-artifact helper are resolved. GitHub checks were still queued when I captured remote state, so I am keeping the PR open until the hosted jobs settle.

@justin808 justin808 removed the agent-claimed Active agent coordination claim; reconciled from private backend label Sep 2, 2026
Comment thread skills/verify/bin/generated-artifact-drift
@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review summary

Reviewed the generated-artifact drift gate (seam-doctor policy validation, the new generated-artifact-drift helper, its test suite, and the /verify skill doc update).

Overall the design is solid: undeclared repos stay silent, validation fails closed before path evaluation runs, the helper is dependency-free and its schema/path-validation logic is a faithful mirror of bin/agent-workflow-seam-doctor's (compared line-by-line), and the git changed-path discovery correctly uses -z-delimited output to avoid path-quoting issues.

Bug: stale "Instructions step" cross-references in skills/verify/SKILL.md

Before this PR, "Decide the required verification set..." (the explanation for why .agents/bin/validate still runs on docs-only commits) was Instructions step 3. This PR inserts a new step 3 ("Run the generated-artifact drift gate...") and pushes that content to step 4, but two other places in the same file still say "see Instructions step 3":

  • skills/verify/SKILL.md:71 (Default Verification Order, Mandatory pre-commit gate)
  • skills/verify/SKILL.md:105 (Scope Guide, Documentation-only changes)

Both now point at the wrong instruction ("Run the generated-artifact drift gate" instead of the docs-only validate rationale). These lines weren't touched by the diff so I couldn't attach inline suggestions to them (GitHub only allows inline comments on lines inside a diff hunk) - they should read "step 4".

Minor: inconsistent --end-of-options guarding in generated-artifact-drift

Left as an inline comment on skills/verify/bin/generated-artifact-drift:94. --base-ref is validated with --end-of-options before the rev-parse check, but the subsequent git diff ... \"${base_ref}...HEAD\" -- call doesn't apply the same guard before the revision argument. Not exploitable through the documented /verify usage (always origin/${BASE_BRANCH}), but worth hardening since the helper is a general-purpose CLI.

Other notes (non-blocking)

  • The path/schema validation logic is duplicated between bin/agent-workflow-seam-doctor and skills/verify/bin/generated-artifact-drift (acknowledged in the helper's own comment and the PR's decision log as an intentional dependency-free tradeoff). Flagging so future changes to one are remembered to be mirrored in the other.
  • Test coverage looks thorough (unknown keys, absolute/traversal paths, empty/duplicate mirrors, duplicate YAML keys via the AST walk, committed/staged/unstaged/untracked diff sources, advisory vs. failure paths).

@justin808
justin808 marked this pull request as ready for review September 2, 2026 22:07
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T00:54:06.415595Z bc99f90 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

ℹ️ 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 skills/verify/bin/generated-artifact-drift
Comment thread skills/verify/bin/generated-artifact-drift
Comment thread skills/verify/SKILL.md
@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review summary

Reviewed the generated-artifact drift gate addition (seam-doctor validation + the new skills/verify/bin/generated-artifact-drift helper + SKILL.md wiring). Overall design is sound: fail-closed on malformed declarations, path-traversal-safe, purely path-set comparison (no template-vs-render content diffing), and Git calls use array-form Open3.capture3 (no shell-injection surface) with --end-of-options/--no-renames used correctly.

Left 4 inline comments:

  1. Stale doc cross-reference (skills/verify/SKILL.md) — inserting the new drift-gate step as step 3 shifts every later step down by one, but two "see Instructions step 3" references elsewhere in the file weren't updated and now point at the wrong step (the drift gate itself, not the "why .agents/bin/validate still runs on docs-only commits" explanation, which is now step 4).
  2. ~90 lines of duplicated validation logic between bin/agent-workflow-seam-doctor and the new skills/verify/bin/generated-artifact-drift (schema/path validation is copy-pasted with only naming differences). Acknowledged as intentional in a code comment to keep the helper dependency-free, but it's a real drift risk — a future schema tweak applied to only one file would let the two validators silently disagree.
  3. Nit: the new "Generated-Artifact Drift Gate" section describes VERIFY_SKILL_DIR resolution only in prose, unlike sibling skills (autoreview, address-review) which inline the "${FOO_SKILL_DIR:-.agents/skills/foo}" fallback directly in the bash snippet.

No security or performance concerns beyond what's noted above — the helper only compares changed-path sets and never reads declared file contents, so path validation is defense-in-depth rather than exploitable today.

@justin808 justin808 added the agent-claimed Active agent coordination claim; reconciled from private backend label Sep 2, 2026

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

ℹ️ 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 skills/verify/bin/generated-artifact-drift Outdated
@justin808 justin808 removed the agent-claimed Active agent coordination claim; reconciled from private backend label Sep 2, 2026
declarations.each_with_index do |declaration, index|
next unless changed_files.include?(declaration["source"])

unless declaration["golden_test"].is_a?(String) && !declaration["golden_test"].strip.empty?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: whitespace-only golden_test is validated as "declared" but rejected as "not declared" at runtime.

path_errors/generated_artifact_path_errors (both here and in bin/agent-workflow-seam-doctor) only check !value.empty?, so a golden_test value of " " (a single space) passes seam-doctor validation as a structurally valid declaration. But this line uses !declaration["golden_test"].strip.empty?, which is stricter — a whitespace-only value fails this check and falls into the "golden_test is not declared" failure branch, even though the declaration was declared and passed validation.

Concretely: a declaration with golden_test: " " is accepted by agent-workflow-seam-doctor as valid, but when its source changes, this gate reports "generated_artifacts[N].golden_test is not declared" — a confusing, incorrect message for a value that a "keep these checks aligned" comment (line 119-120) claims stays in sync with the doctor's validation.

Consider using the same .empty? (non-stripped) check here, or tightening path_errors in both places to reject whitespace-only paths, so the two "aligned" implementations agree.

end
end

def duplicate_key_errors(node, path, errors)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reuse: this duplicate-key walker reimplements AutonomousMergePolicy.duplicate_key_errors from scratch.

bin/agent_doctor/autonomous_merge_policy_yaml.rb already defines an identical recursive duplicate-key checker (duplicate_key_errors/walk_for_duplicate_keys), and bin/agent-workflow-seam-doctor calls it via AutonomousMergePolicy.duplicate_key_errors(yaml) for this very same generated_artifacts policy (see generated_artifacts_policy_issues). This file re-derives the same algorithm (and, further down, declaration_errors/path_errors duplicate generated_artifact_declaration_errors/generated_artifact_path_errors from bin/agent-workflow-seam-doctor almost line-for-line) instead of requiring the shared module.

Other skill scripts in this repo already reach across directories for this exact module, e.g. skills/pr-batch/bin/autonomous-merge-contract-test.rb and skills/pr-batch/bin/current-integration-evidence-test.rb do require_relative "../../../bin/agent_doctor/autonomous_merge_policy", so sharing here would follow an established convention rather than introduce a new one.

The comment above (lines 119-120) explicitly flags the duplication risk ("stays dependency-free by mirroring the same validation rules here") — but that's exactly the kind of drift this PR's own feature is designed to catch. If the schema ever changes in one copy and not the other, the doctor and the runtime gate would silently disagree on what's valid.

Comment thread skills/verify/SKILL.md
entry's `source` changes. When the source changes, a declared `golden_test` must also resolve to an existing repository
file beneath the selected root.

Resolve `VERIFY_SKILL_DIR` from an explicit environment value, the loaded skill's base directory, or the repo-local

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Doc/procedure mismatch: the described 3-way VERIFY_SKILL_DIR fallback isn't what the snippet below does.

This sentence says to resolve VERIFY_SKILL_DIR from "an explicit environment value, the loaded skill's base directory, or the repo-local .agents/skills/verify copy" (three fallbacks), but the bash snippet immediately below only implements two: VERIFY_SKILL_DIR="${VERIFY_SKILL_DIR:-.agents/skills/verify}" uses an explicit env value if set, otherwise jumps straight to the repo-local copy — it never tries "the loaded skill's base directory" in between.

For an agent that copies the bash block verbatim (rather than parsing the surrounding prose), this means: if the skill is loaded from somewhere other than .agents/skills/verify (e.g. this repo's own skills/verify/bin/..., or a different vendoring path) and VERIFY_SKILL_DIR isn't set, the command resolves to the wrong path and the "stop with a precise missing-helper error" fallback never triggers the intended middle case.

issues.uniq
end

def generated_artifacts_policy_issues(config, yaml)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rollout risk: this can flip existing downstream consumers from "clean" to "drifted".

generated_artifacts_policy_issues now unconditionally validates the shape of a generated_artifacts: key whenever it's present in .agents/agent-workflow.yml — a key that seam-doctor previously ignored entirely (no top-level-key allowlist exists). This is consumed by bin/push-downstream's fleet audit: issues = AgentWorkflowSeamDoctor.check(clone) (line ~1029) feeds directly into status: issues.empty? && changed_paths.empty? ? AUDIT_CLEAN : "drifted" (line ~1056), which gates .github/workflows/downstream-seam-audit.yml.

Any consumer repo that already has a generated_artifacts: key in a shape this PR doesn't accept (wrong type, missing mirrors, duplicate source, non-canonical path, etc. — e.g. pre-adopted ahead of this schema landing, or added by a parallel in-flight PR in the same batch) will newly fail seam-doctor and flip from clean to drifted on the next scheduled audit, even though nothing about the actual seam contract changed. Worth confirming no consumer in downstream.yml currently declares this key in an incompatible shape before merging.

Comment thread skills/verify/SKILL.md
with `git status --short`, `git diff --name-only "origin/${BASE_BRANCH}...HEAD"`, and
`git diff --stat "origin/${BASE_BRANCH}...HEAD"`.
3. Decide the required verification set that covers the changed surface area using the **Scope Guide** below. Always
3. Run the [generated-artifact drift gate](#generated-artifact-drift-gate). Preserve any acknowledgment-required

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The drift gate this PR adds is never actually invoked by any enforced/automated check — only by agent-followed prose.

The only place that runs skills/verify/bin/generated-artifact-drift --root ... --base-ref ... against a real diff is this natural-language /verify instruction. bin/validate only runs the helper's unit tests (ruby skills/verify/bin/generated-artifact-drift-test.rb), not the actual gate against the branch diff, and no .github/workflows/*.yml references the script at all.

Given the PR's stated goal is "no CI signal" today for template/mirror drift, this still leaves no CI signal — it just relies on an agent correctly reading and executing SKILL.md step 3 (resolving VERIFY_SKILL_DIR, running the helper, copying the advisory into evidence) before every commit. A human pushing directly, or an agent skipping/misreading step 3, or misresolving VERIFY_SKILL_DIR (silent no-op) ships the exact drift this feature is meant to catch, undetected by CI's mandatory gates (bin/validate, bin/lint, downstream-seam-audit.yml). Consider wiring the actual check into an enforced gate rather than prose-only instructions.

end
end

def duplicate_key_errors(node, path, errors)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reimplements an existing, already-reusable YAML duplicate-key detector instead of requiring it.

duplicate_key_errors/generated_artifact_duplicate_errors (lines 143-168) is a near-verbatim reimplementation of AutonomousMergePolicy.duplicate_key_errors/walk_for_duplicate_keys in bin/agent_doctor/autonomous_merge_policy_yaml.rb, which is already required from the identical directory depth elsewhere (e.g. skills/pr-batch/lib/autonomous_merge_decision.rb:5 does require_relative "../../../bin/agent_doctor/autonomous_merge_policy") and has zero external gem dependencies (pure stdlib Psych/YAML). The comment at line ~130 justifying the duplication as keeping the helper "dependency-free" doesn't hold up given that precedent.

Similarly, declaration_errors/path_errors (lines 171-217ish) duplicate bin/agent-workflow-seam-doctor's generated_artifact_declaration_errors/generated_artifact_path_errors almost line-for-line. Notably, within this same PR the seam-doctor calls the shared AutonomousMergePolicy.duplicate_key_errors while this sibling script reimplements the same check from scratch — an inconsistency in the PR's own approach to reuse.

Failure scenario: a future schema/edge-case fix (new allowed key, tighter path rule, YAML anchor/merge-key handling) gets applied to one copy and not the other, causing the seam doctor and drift gate to silently disagree on which generated_artifacts declarations are valid — the exact "policy drift" class of bug this feature exists to prevent, reproduced inside the tooling itself.

errors
end

def generated_artifact_duplicate_errors(yaml)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

generated_artifact_duplicate_errors re-parses the same YAML string with Psych.parse_stream(yaml) even though YAML.safe_load(yaml, aliases: false) was already run over the identical string a few lines earlier in run (line ~31) to obtain config. That's two full YAML parses of the same document on every invocation of the drift gate.

@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review summary

Reviewed the generated_artifacts drift-detection gate (skills/verify/bin/generated-artifact-drift, seam doctor validation, and SKILL.md docs). Posted 3 inline findings:

  1. Validation/runtime inconsistency — whitespace-only golden_test values pass seam-doctor schema validation (.empty? only) but are treated as "not declared" at runtime (.strip.empty?), producing a misleading error for a value the doctor already accepted.
  2. Duplicated validation logicduplicate_key_errors/declaration_errors/path_errors in the new script reimplement logic that already exists in bin/agent-workflow-seam-doctor (and similar duplicate-key checking in bin/agent_doctor/autonomous_merge_policy_yaml.rb) instead of sharing it via require_relative. If the schema rules are updated in one copy and not the other(s), the doctor and the runtime gate can silently diverge — the exact kind of drift this feature is meant to prevent.
  3. SKILL.md/code mismatch — the prose describes a 3-way fallback for resolving VERIFY_SKILL_DIR, but the bash snippet only implements 2 of the 3 steps (skips the "loaded skill's base directory" fallback), which could cause the gate to be silently skipped or fail with a generic error in some setups.

Note: I also found a symlink-related bypass of the golden_test existence check (File.file? without root-containment verification), but it looks like a later revision of this PR already fixes it with a repository_file_beneath_root?-style check, so I left that one off the inline comments — worth double-checking it's present on the current head.

No security-critical issues beyond the symlink note, and no significant performance concerns (path-set comparisons and bounded YAML parsing only run during explicit local/CI verification).

].flatten.uniq
end

def repository_file_beneath_root?(root, relative_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

repository_file_beneath_root? re-derives symlink/path-containment safety logic (via File.realpath + Pathname#relative_path_from) that bin/agent-workflow-seam-doctor already solves elsewhere with a different technique for the same "confirm a repo-relative path is safely beneath root, rejecting symlink escapes" problem. Two independently-written, security-relevant containment checks in the same codebase risk diverging if one gets a hardening fix (e.g. after a security review) that never propagates to the other.

64
end

def changed_files_from_git(root, base_ref)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

changed_files_from_git spawns 4 separate git/Open3.capture3 subprocesses per invocation: a rev-parse --verify precheck plus 3 separate git diff/ls-files calls (branch diff, --cached, unstaged). The 3 diff/ls-files calls could largely be replaced by a single git status --porcelain=v1 -z --untracked-files=all, and the rev-parse --verify --quiet precheck (lines 96-98) is redundant since the subsequent git diff "#{base_ref}...HEAD" call already fails non-zero on a bad ref and gets turned into an Error with stderr text by git_changed_paths. Also note: --quiet suppresses git's own error output on a failed rev-parse, so stderr.strip at line 98 is typically empty, making the raised message "could not resolve --base-ref ...: " have an uninformative empty tail. Since /verify reruns after every fix per SKILL.md's loop instructions, this subprocess overhead is paid repeatedly per session.

["expected a nonempty list"]
else
declarations.each_with_index.flat_map do |declaration, index|
generated_artifact_declaration_errors(declaration, index)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor simplification: declarations.is_a?(Array) is checked twice — once inside errors.concat(if !declarations.is_a?(Array) || declarations.empty? ... end) above, and again in the following if declarations.is_a?(Array) block for the duplicate-source check. The sibling policy_errors in skills/verify/bin/generated-artifact-drift achieves the same result with a single early-return guard clause, which would be easier to follow here too.

def run(argv)
options = { root: Dir.pwd, changed_files: [] }
parser = OptionParser.new do |opts|
opts.banner = "Usage: generated-artifact-drift [--root DIR] [--base-ref REF | --changed-file PATH ...]"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

root = File.expand_path(options.fetch(:root)) is computed here, but changed_files_from_git is later called with the raw options.fetch(:root) (unexpanded) rather than this already-normalized root, so File.expand_path gets silently recomputed inside every downstream git call in git_changed_paths/the rev-parse precheck. Passing the already-expanded root through would avoid the redundant recomputation and the latent risk of the two expansions diverging.

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

ℹ️ 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".

next
end
golden_test = declaration.fetch("golden_test")
unless repository_file_beneath_root?(root, golden_test)

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 Reject artifact paths used as golden tests

When golden_test is identical to the declared source or one of its mirrors, this existence check accepts that artifact as the required test. For example, with golden_test: templates/widget.tt, changing both the source and a mirror makes the helper exit 0 silently even though no golden test exists. Validate that golden_test is distinct from the source and every mirror before accepting the declaration.

Useful? React with 👍 / 👎.

raise Error, "could not resolve --base-ref #{base_ref.inspect}: #{stderr.strip}" unless status.success?

[
git_changed_paths(root, "diff", "--name-only", "--no-renames", "-z", "#{base_ref}...HEAD", "--"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor hardening gap: the rev-parse call two lines above guards base_ref with --end-of-options, but this diff call (and the --cached/plain diff calls below, though those don't interpolate base_ref) interpolates "#{base_ref}...HEAD" directly before the -- separator without --end-of-options. If base_ref ever began with - (e.g. --output=/some/path), git diff would parse it as an option rather than a revision — --output= in particular writes the diff to an arbitrary file instead of stdout.

In practice this is hard to trigger today: base_ref only reaches here after the earlier rev-parse --verify --quiet --end-of-options "#{base_ref}^{commit}" succeeds, which requires a literal ref/commit named that dash-prefixed string to already exist — an unusual precondition — and the only documented caller (skills/verify/SKILL.md) always passes "origin/${BASE_BRANCH}". Still, for defense-in-depth it'd be worth adding --end-of-options here too (e.g. "diff", "--name-only", "--no-renames", "-z", "--end-of-options", "#{base_ref}...HEAD", "--") so the safety property doesn't depend on that gate.

git_changed_paths(root, "diff", "--name-only", "--no-renames", "-z", "#{base_ref}...HEAD", "--"),
git_changed_paths(root, "diff", "--name-only", "--no-renames", "-z", "--cached", "--"),
git_changed_paths(root, "diff", "--name-only", "--no-renames", "-z", "--"),
git_changed_paths(root, "ls-files", "--others", "--exclude-standard", "-z", "--")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Latent path-relativity bug: git ls-files --others --exclude-standard (unlike git diff --name-only, which reports paths relative to the discovered top-level regardless of -C) defaults to reporting paths relative to the -C directory itself; --full-name is needed to force top-level-relative output. If --root were ever passed as something other than the repository's true top-level (e.g. a subdirectory), this call alone would emit paths relative to that subdirectory while the three diff calls above it would still emit root-relative paths — the resulting .uniq set would silently mix two different path bases, causing untracked-file matches against declaration["source"]/mirrors to be missed or wrong.

Currently unreachable: the only documented invocation (skills/verify/SKILL.md) always passes git rev-parse --show-toplevel as --root, and the test suite only exercises --root at the same directory it runs git init in. Still, since nothing here asserts --root is the toplevel, consider adding --full-name to this call (matching the effective behavior of the other three) so the invariant holds regardless of how --root is invoked in the future.

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown

Code Review

Reviewed the new generated-artifact-drift gate (skills/verify/bin/generated-artifact-drift), its test suite, the agent-workflow-seam-doctor schema validation, and the SKILL.md/bin/validate wiring.

Overall assessment: solid, well-tested addition. The design choice to compare changed-path sets rather than diffing template vs. rendered content is sound and well-justified in the PR description. Path validation (path_errors/generated_artifact_path_errors) correctly rejects traversal, absolute paths, control characters, and backslashes, and is exercised by a thorough fixture matrix (absolute paths, .. traversal, duplicate mirrors, source-as-mirror, malformed YAML with duplicate keys, symlinks escaping root, etc.).

Two minor, currently-unreachable hardening items posted as inline comments:

  1. skills/verify/bin/generated-artifact-drift:102 — the git diff calls that interpolate base_ref into "#{base_ref}...HEAD" don't carry the same --end-of-options guard that the preceding rev-parse call uses, so a dash-prefixed base_ref (e.g. --output=...) could in theory be parsed as a git diff option instead of a revision. Not exploitable today because the rev-parse --verify --end-of-options gate immediately before it requires such a literal ref to already exist, and the only real caller always passes origin/${BASE_BRANCH}. Worth hardening for defense-in-depth.
  2. skills/verify/bin/generated-artifact-drift:105git ls-files --others --exclude-standard is missing --full-name, so unlike the three git diff calls above it (which are always top-level-relative regardless of -C), this one would report paths relative to --root if --root were ever a subdirectory rather than the true top-level. Currently unreachable since --root is always passed as git rev-parse --show-toplevel, but nothing enforces that invariant.

One structural note (not a bug): the path/schema validation logic in this new script (policy_errors, declaration_errors, path_errors, duplicate_key_errors) is intentionally duplicated from bin/agent-workflow-seam-doctor to keep the helper dependency-free, per the comment at line 130-131. This is a reasonable tradeoff given the constraint, but it does mean the two schemas can drift apart silently if one is updated without the other — might be worth a shared regression test asserting the two validators agree on a corpus of inputs, though I'd leave that to the author's judgment given the "dependency-free" constraint.

No security, correctness, or performance issues found beyond the two inline items above.

@justin808 justin808 added complexity:complexify Adds enduring logic, modes, contracts or operational obligations; value is judged separately. P3 Parked: low-priority optional work; requires explicit reprioritization before implementation. triage:park Defer during backlog reduction; requires fresh evidence or explicit reprioritization. labels Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

complexity:complexify Adds enduring logic, modes, contracts or operational obligations; value is judged separately. P3 Parked: low-priority optional work; requires explicit reprioritization before implementation. triage:park Defer during backlog reduction; requires fresh evidence or explicit reprioritization.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated-artifact drift gate: declare template-to-mirror pairs at the repo seam

1 participant