Skip to content

Report unsupported signed launch readiness - #306

Closed
justin808 wants to merge 16 commits into
mainfrom
jg-codex/issue-299-signed-launch-readiness
Closed

Report unsupported signed launch readiness#306
justin808 wants to merge 16 commits into
mainfrom
jg-codex/issue-299-signed-launch-readiness

Conversation

@justin808

@justin808 justin808 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closes #299.

Summary

  • report signed-launch readiness independently from install freshness as supported, clean-host unsupported, or fail-closed UNKNOWN
  • create or validate the host-owned .agents directory during install without creating keys, trust anchors, capability claims, signatures, or waivers
  • accept an explicit durable human waiver only for typed unsupported Codex hosts and exact batch/lane/dispatcher/model/effort bindings
  • persist waived launches as waived-active, keep them distinct from signed confirmed-active, and enforce lifecycle/replay constraints
  • document clean installs, pre-v2 upgrades, host-owned trust material, and the unsupported-host remediation path

Trust boundary

Private signing keys remain host-owned. Caller or environment input cannot select trust anchors, and partial, malformed, unsafe, or ambiguous host evidence remains UNKNOWN and cannot be waived. The waiver path preserves all security, dependency, validation, review, CI, merge-eligibility, exact-head, and completed-batch-audit gates.

Validation

  • bin/validate — PASS on the exact staged tree (exit 0; 115 files inspected by RuboCop, no offenses)
  • status: 15 runs / 70 assertions
  • dispatcher capability: 75 runs / 827 assertions
  • batch-plan preflight: 51 runs / 453 assertions
  • architecture: 6 runs / 110 assertions
  • model routing: 18 runs / 1081 assertions
  • installer, public doctor, seam, migration/recovery, replay, and remaining helper suites passed in the full gate

The authoritative local full gate used macOS /bin/bash with BASH_ENV and ENV unset because the ambient Homebrew Bash 5.3 environment reproducibly deadlocked existing heredoc fixtures. No source workaround for that environment issue is included.

Batch state

Implementation lane is complete at 6f39f4225793646071aff2e25f10c3829c20b103. Independent final-head QA and review are pending and remain required before merge.

Summary by CodeRabbit

  • New Features

    • Added signed-launch readiness reporting with supported, unsupported, and unknown states.
    • Installations now validate host security and report readiness after setup.
    • Added controlled human waivers for eligible unsupported hosts, with distinct waived-launch status and replay handling.
    • Status output now includes readiness details, including for failures.
    • Added versioned, identity-bound launch confirmations and signed host observations.
  • Bug Fixes

    • Unsafe, incomplete, or unverifiable installation and trust configurations are now blocked.
  • Documentation

    • Documented readiness classifications, waiver requirements, lifecycle behavior, and replay safeguards.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@justin808, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

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

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2137694a-4dc2-4ef7-94c2-720dcf9ab50e

📥 Commits

Reviewing files that changed from the base of the PR and between f55faa8 and 70f8a21.

📒 Files selected for processing (15)
  • .gitignore
  • bin/agent_doctor/signed_launch_installation_test.rb
  • docs/agent-workflows-model-routing.md
  • docs/installation-and-upgrades.md
  • docs/pr-batch-skills.md
  • skills/plan-pr-batch/SKILL.md
  • skills/plan-pr-batch/bin/batch-plan-preflight
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb
  • skills/plan-pr-batch/scripts/check_goal_prompt_size.rb
  • skills/pr-batch/SKILL.md
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight-test.rb
  • skills/pr-batch/bin/model-routing-contract-test.rb
  • skills/triage/SKILL.md
  • workflows/pr-processing.md

Walkthrough

The PR adds signed-launch readiness inspection, secure host-owned provisioning, unsupported Codex waivers, waived dispatcher replay, and lifecycle waiver validation. Partial or unsafe host state remains UNKNOWN and blocked.

Changes

Signed-launch readiness and waiver handling

Layer / File(s) Summary
Installation validation and readiness inspection
bin/agent_doctor/*, bin/agent-workflows-status*, bin/install-agent-workflows*
Installations validate roots and .agents directories. Readiness validates capability records and RSA trust anchors. Status reports supported, unsupported, or UNKNOWN states.
Waiver records and evidence validation
bin/agent_doctor/signed_launch_waiver*.rb
Waiver records enforce secure access, exact schemas, direct-human authorization, durable references, assignment bindings, chronology, evidence constraints, and canonical digests.
Waived dispatcher activation and replay
skills/pr-batch/bin/dispatcher-capability-preflight*
Dispatcher assignments support waived-active with validated waiver identifiers, digest bindings, installation bindings, and distinct replay output.
Workflow-control lifecycle waivers
skills/plan-pr-batch/bin/batch-plan-preflight*
Batch preflight accepts lifecycle waivers, validates lane and plan bindings, merges waived lanes into completion tracking, and rejects duplicate, reused, unsafe, or weakly signed records.
Policy and documentation foundation
CHANGELOG.md, CONTEXT.md, docs/..., workflows/pr-processing.md, skills/.../SKILL.md
Documentation and contract tests define readiness states, installation provenance, waiver constraints, replay behavior, lifecycle records, and migration rules.

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

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant AgentWorkflowsStatus
  participant SignedLaunchReadiness
  participant DispatcherPreflight
  participant BatchPlanPreflight
  Operator->>AgentWorkflowsStatus: request readiness
  AgentWorkflowsStatus->>SignedLaunchReadiness: assess host and target
  SignedLaunchReadiness-->>AgentWorkflowsStatus: return readiness state
  Operator->>DispatcherPreflight: provide unsupported-host waiver
  DispatcherPreflight-->>Operator: persist waived-active assignment
  Operator->>BatchPlanPreflight: provide lifecycle waiver
  BatchPlanPreflight-->>Operator: include waived lane in completion
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: reporting unsupported signed-launch readiness.
Linked Issues check ✅ Passed The changes satisfy issue #299 by adding readiness reporting, typed unsupported handling, durable waivers, fail-closed validation, replay coverage, and migration documentation.
Out of Scope Changes check ✅ Passed The changes support the linked issue objectives and do not show unrelated code changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg-codex/issue-299-signed-launch-readiness

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 bin/install-agent-workflows-test.bash Outdated
Comment thread bin/agent_doctor/signed_launch_readiness.rb Outdated
@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review summary

This PR adds a "signed-launch readiness" capability that is deliberately fail-closed: it reports supported / unsupported / UNKNOWN for Codex host trust material independent of install freshness, and adds a narrowly-scoped, durable, direct-human waiver path for unsupported hosts. The security design is careful and consistent with the rest of the repo's style (owner/permission/symlink checks before trusting any file, strict exact-key schema validation, RSA public-key-only anchors, fail-closed on any ambiguity).

What the PR does

  • bin/agent_doctor/signed_launch_readiness.rb: inspects <target>/.agents/{signed-launch-capability,dispatcher-launch-trust,workflow-control-lifecycle-trust}.json, requiring exact ownership/permissions/schema, and classifies supported / unsupported (clean host, all three files absent) / UNKNOWN (anything partial, malformed, symlinked, or writable).
  • bin/agent_doctor/signed_launch_waiver{,_record}.rb: validates a durable, human-issued bootstrap waiver plus a live dispatcher observation / lifecycle waiver, tightly bound to batch/lane/dispatcher/model/effort with no fallbacks.
  • bin/install-agent-workflows: creates/validates the host-owned .agents directory (0700, owner-checked, non-symlink) without ever writing keys/trust/capability files itself; surfaces signed_launch_readiness.capability in the install summary.
  • bin/agent-workflows-status, skills/pr-batch/bin/dispatcher-capability-preflight, skills/plan-pr-batch/bin/batch-plan-preflight: wire the readiness/waiver checks into status reporting and the dispatcher/lifecycle gates, adding a distinct waived-active lifecycle that's kept separate from signed confirmed-active and deduplicated against signed receipts.
  • Docs updated in several places to describe the new contract.

Issues found (posted inline)

  1. Bug — will break CI: bin/install-agent-workflows-test.bash:246 uses stat -f '%Lp' ..., which is BSD/macOS-only syntax. CI runs bin/validate (which runs this test script) on ubuntu-latest, where GNU stat -f means "show filesystem status" and does not accept %Lp; the command errors instead of returning a mode string. This is the only stat -f usage anywhere in the repo. The PR description even notes the author validated locally on macOS due to a Bash env issue — this Linux/macOS stat divergence looks like a related blind spot.
  2. Design smell: AgentDoctor::SignedLaunchReadiness.inspect(host:, target:) shadows Object#inspect with an incompatible (required-keyword) signature. All current call sites pass the keywords correctly so nothing fails today, but any future zero-arg .inspect on this module (pp, debuggers, error formatting) will raise ArgumentError instead of behaving like a normal Ruby object. Recommend renaming (e.g. evaluate/assess).

Other observations (not filed as separate comments)

  • The safety checks (safe_owned_directory?, read_record, ensure_signed_launch_directory) do a check-then-read/check-then-use sequence rather than opening a file descriptor once and fstat-ing it, so there's a small TOCTOU window on hostile-symlink-swap races. Given the existing codebase already accepts this pattern elsewhere (e.g. the pre-existing dispatcher trust anchor loading) and the threat model is a local, single-owner filesystem, this is consistent with existing risk acceptance rather than a new regression — flagging only for awareness.
  • The large "Signed-launch readiness is separate from install freshness…" paragraph is duplicated verbatim across CONTEXT.md, docs/agent-workflows-model-routing.md, docs/pr-batch-skills.md, skills/pr-batch/SKILL.md, and workflows/pr-processing.md. This matches an existing repo convention (the adjacent launch-confirmation paragraph is duplicated the same way), so it's not a new problem, just worth noting for future maintenance cost.
  • Test coverage for the new logic (readiness classification, installer directory hardening, waiver binding/replay/cross-batch rejection, duplicate signed/waived detection) is thorough and specifically targets the fail-closed paths.

Overall: solid, well-tested security design with one concrete CI-breaking bug and one latent naming footgun to fix before merge.

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

ℹ️ 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 bin/agent_doctor/signed_launch_waiver.rb Outdated

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

🧹 Nitpick comments (2)
bin/agent_doctor/signed_launch_waiver_record.rb (1)

78-82: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider restricting durable_ref? schemes and merge_authority values.

durable_ref? accepts any absolute URI. file:///tmp/anything and http://… both pass, so a waiver can point its evidence at a local or unauthenticated location while still satisfying validation. constraints? accepts any nonempty merge_authority string, so a typo such as auto_merge_when_gate_pass validates and later comparisons silently miss.

Add a scheme allowlist for evidence references and an explicit value allowlist for merge_authority. Both checks are local to this module.

Also applies to: 117-121

🤖 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 `@bin/agent_doctor/signed_launch_waiver_record.rb` around lines 78 - 82,
Restrict durable_ref? to an explicit allowlist of approved URI schemes instead
of accepting every absolute URI, while continuing to return false for invalid or
disallowed references. In constraints?, validate merge_authority against its
supported values and reject nonempty unknown strings so later comparisons cannot
silently miss; keep both allowlists local to this module.
skills/plan-pr-batch/bin/batch-plan-preflight-test.rb (1)

667-680: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the lane rejection variant to exercise the waiver lane binding.

The lane variant sets lane_id to "lane-z", which no planned lane declares. validate_lane_lifecycle_records rejects it at the lanes_by_id[waiver["lane_id"]] lookup, before validate_lifecycle compares the lane against authorized_lanes. The variant therefore proves the lane-lookup guard, not the waiver lane binding.

Add a second planned lane that is absent from authorized_lanes to cover the binding itself.

♻️ Suggested additional coverage
def test_lifecycle_waiver_rejects_a_planned_lane_outside_authorized_lanes
  helper, root = installed_unsupported_workflow_control_helper
  route = { "model" => "gpt-5.6-sol", "effort" => "xhigh" }
  waiver_path, waiver_record = bootstrap_waiver(
    root, batch_id: "batch-plan-1", lane_id: "lane-a", route:
  )
  waiver = lane_lifecycle_waiver(
    path: waiver_path, record: waiver_record, route:, lane_id: "lane-b"
  )
  input = input_for(
    lanes: [lane("lane-a"), lane("lane-b")], backend: "codex", lifecycle_waivers: [waiver]
  )

  result, _stderr, status = evaluate(input, helper: helper)

  refute status.success?
  assert_includes result.fetch("violations").map { |item| item.fetch("code") },
                  "lane-lifecycle-waiver-invalid"
  assert_empty result.dig("launch", "completed_lane_ids")
end
🤖 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 `@skills/plan-pr-batch/bin/batch-plan-preflight-test.rb` around lines 667 -
680, Strengthen the lane rejection coverage in the lifecycle waiver tests by
adding a case where the waiver targets a second planned lane that is not
included in authorized_lanes. Update the relevant test setup around
lane_lifecycle_waiver and input_for to include both planned lanes, then assert
the existing lane-lifecycle-waiver-invalid violation and no completed lanes,
ensuring rejection occurs during lifecycle authorization rather than lane
lookup.
🤖 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 `@bin/agent_doctor/signed_launch_readiness.rb`:
- Around line 82-88: Update clean_unsupported_host? so clean unsupported hosts
such as Claude are handled explicitly: either extend the clean-host eligibility
branch beyond "codex" so a safe, state-free Claude home is waivable, or clearly
identify Claude as out of scope in the readiness reason and installation
documentation. Ensure clean Claude hosts do not fall through to unknown(host)
with an unsatisfiable remediation requirement.
- Line 14: Rename AgentDoctor::SignedLaunchReadiness.inspect to
inspect_readiness (or readiness) so it no longer shadows Module#inspect, then
update all three call sites to use the new method name while preserving the
existing host and target arguments.

In `@bin/agent_doctor/signed_launch_waiver_record.rb`:
- Around line 31-44: Update signed_launch_waiver_record’s read method to
validate every directory ancestor from the waiver path through the filesystem
root, ensuring each is owned by helper_uid and has no group/world write bits;
also eliminate the lstat-to-read replacement window by opening the waiver once,
validating the opened file descriptor’s stat, and reading from that descriptor.

In `@bin/agent_doctor/signed_launch_waiver.rb`:
- Around line 98-103: Update lifecycle_receipt_ref to prevent slash-delimited
identifier collisions by validating or percent-encoding batch_plan_id,
stage_dependency_plan_id, wave, lane_id, and state before interpolation.
Preserve the existing receipt_ref structure while ensuring each component maps
unambiguously to one lifecycle record.
- Around line 105-124: Update dispatcher_observation_valid? so instance_id and
launch_token must each be nonempty on both the observation and assignment before
or alongside their equality checks. Preserve the existing matching behavior
while rejecting nil and empty-string identities, using the same nonempty
validation convention already applied to dispatcher/bootstrap identity fields.

In `@bin/agent-workflows-status`:
- Around line 176-179: Update the signed_launch_readiness call in status_payload
to guard against a nil target, returning the UNKNOWN readiness record directly
when target is missing. Only invoke SignedLaunchReadiness.inspect for a resolved
target, including in the rescue path that passes options[:target], so readiness
is never evaluated against the current working directory.

In `@bin/agent-workflows-status-test.rb`:
- Around line 60-61: Add a minimum RSA modulus-size validation to public_anchor?
in signed_launch_readiness.rb, requiring RSA keys to have at least 2048 bits
before reporting them as supported. Update the RSA fixture generation in
agent-workflows-status-test.rb, including dispatcher_key and workflow_key, to
use a compliant size or a shared per-run key while preserving test behavior.

In `@bin/install-agent-workflows`:
- Around line 763-768: Make the signed_launch_capability probe around
AgentDoctor::SignedLaunchReadiness.inspect non-fatal under set -e by handling a
failed ruby command substitution and assigning UNKNOWN as the fallback value.
Preserve the existing capability output when the probe succeeds so the
completion message, PATH instruction, and successful install status are always
emitted.
- Around line 282-303: Update ensure_signed_launch_directory to validate both
$target and $target/.agents as non-symlink directories with matching helper
ownership and no group/world-writable permissions, emitting a diagnostic that
identifies $target when validation fails. Align the installer’s ownership
reference with signed_launch_readiness.rb’s __FILE__-based check so
shared-checkout installs validate ownership against the readiness script and
installed runner consistently.

In `@bin/install-agent-workflows-test.bash`:
- Line 246: Replace the BSD-specific stat invocation in the .agents permission
assertion with the existing Ruby File.lstat-based permission check used at line
199, preserving the expected 777 comparison and failure message.

---

Nitpick comments:
In `@bin/agent_doctor/signed_launch_waiver_record.rb`:
- Around line 78-82: Restrict durable_ref? to an explicit allowlist of approved
URI schemes instead of accepting every absolute URI, while continuing to return
false for invalid or disallowed references. In constraints?, validate
merge_authority against its supported values and reject nonempty unknown strings
so later comparisons cannot silently miss; keep both allowlists local to this
module.

In `@skills/plan-pr-batch/bin/batch-plan-preflight-test.rb`:
- Around line 667-680: Strengthen the lane rejection coverage in the lifecycle
waiver tests by adding a case where the waiver targets a second planned lane
that is not included in authorized_lanes. Update the relevant test setup around
lane_lifecycle_waiver and input_for to include both planned lanes, then assert
the existing lane-lifecycle-waiver-invalid violation and no completed lanes,
ensuring rejection occurs during lifecycle authorization rather than lane
lookup.
🪄 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: 0aa35604-5057-4a6d-95fe-0ce5c69ad680

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed056b and 6f39f42.

📒 Files selected for processing (19)
  • CHANGELOG.md
  • CONTEXT.md
  • bin/agent-workflows-status
  • bin/agent-workflows-status-test.rb
  • bin/agent_doctor/signed_launch_readiness.rb
  • bin/agent_doctor/signed_launch_waiver.rb
  • bin/agent_doctor/signed_launch_waiver_record.rb
  • bin/install-agent-workflows
  • bin/install-agent-workflows-test.bash
  • docs/agent-workflows-model-routing.md
  • docs/installation-and-upgrades.md
  • docs/pr-batch-skills.md
  • skills/plan-pr-batch/SKILL.md
  • skills/plan-pr-batch/bin/batch-plan-preflight
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb
  • skills/pr-batch/SKILL.md
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight-test.rb
  • workflows/pr-processing.md

Comment thread bin/agent_doctor/signed_launch_readiness.rb Outdated
Comment thread bin/agent_doctor/signed_launch_readiness.rb Outdated
Comment thread bin/agent_doctor/signed_launch_waiver_record.rb Outdated
Comment thread bin/agent_doctor/signed_launch_waiver.rb
Comment thread bin/agent_doctor/signed_launch_waiver.rb
Comment thread bin/agent-workflows-status
Comment thread bin/agent-workflows-status-test.rb Outdated
Comment thread bin/install-agent-workflows Outdated
Comment thread bin/install-agent-workflows Outdated
Comment thread bin/install-agent-workflows-test.bash Outdated

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

ℹ️ 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 bin/agent_doctor/signed_launch_waiver.rb
Comment thread bin/agent_doctor/signed_launch_waiver_record.rb Outdated
@justin808

Copy link
Copy Markdown
Member Author

Review feedback addressed at ccc37d59

Full-history review inventory was scanned after the repair push. All 13 inline threads are replied to and resolved; no unresolved review threads remain at this checkpoint.

Mattered

  • Replaced BSD-only stat coverage with portable File.lstat validation.
  • Bound waived dispatcher and lifecycle replay to a canonical waiver digest and fresh, post-grant host observation.
  • Hardened waiver reads across the full ancestor chain with a single NOFOLLOW descriptor read.
  • Rejected ambiguous lifecycle receipt components and empty instance/launch identities.
  • Returned typed UNKNOWN for unavailable targets, required RSA anchors of at least 2048 bits, aligned installer root/owner checks, and made a failed post-install readiness probe nonfatal.
  • Clarified the Codex-only v1 scope and renamed SignedLaunchReadiness.inspect to assess.

Optional feedback completed

  • Added durable-reference scheme and merge-authority allowlists.
  • Added planned-but-unauthorized second-lane lifecycle coverage.

Skipped as non-actionable status

  • Codex review boilerplate and the duplicate Claude summary required no separate code change after their concrete inline findings were fixed.
  • CodeRabbit's current-head rate-limit notice and docstring-coverage warning are service/status-only; current-head reviewer completion remains a separate merge gate and is not claimed by this checkpoint.

Validation: bin/validate passed; focused status 17/76, dispatcher 83/904, and lifecycle 55/501 suites passed. The one-time issue #299 bootstrap waiver did not waive CI, QA, review, thread, or merge gates.

@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review: Report unsupported signed launch readiness (#306)

This PR adds a new signed_launch_readiness capability report plus a durable, human-waiver escape hatch for unsupported (Codex) hosts, threaded through agent-workflows-status, install-agent-workflows, dispatcher-capability-preflight, and batch-plan-preflight. The overall design is careful: fail-closed UNKNOWN for anything partial/malformed/writable, exact-match schema checks everywhere, digest-bound waiver replay, and bounded-freshness live observations for the unsigned waiver path. Test coverage for the new logic is extensive.

Overview

  • New bin/agent_doctor/signed_launch_readiness.rb assesses supported / Codex-only unsupported / fail-closed UNKNOWN from host-owned .agents JSON files (ownership, permission, and RSA-2048 checks).
  • New bin/agent_doctor/signed_launch_waiver.rb and bin/agent_doctor/signed_launch_waiver_record.rb implement the durable human-waiver bootstrap record and its binding into dispatcher/lifecycle wrappers (canonical digest, bounded-freshness observation, absolute-path + O_NOFOLLOW file read).
  • install-agent-workflows now provisions/validates the target root and its .agents directory without creating any trust material, and reports readiness post-install (non-fatal on probe failure).
  • dispatcher-capability-preflight and batch-plan-preflight gain a waived-active lifecycle parallel to confirmed-active, gated on the new waiver library.

Correctness issue (likely blocks real installs)
ensure_signed_launch_directories in bin/install-agent-workflows validates that the target root and its .agents directory are owned by the same uid as $repo_root/bin/agent_doctor/signed_launch_readiness.rb - i.e. the source checkout, not the identity actually running the installer. Whenever the invoking user differs from the owner of the cloned/mounted repo (shared/system-wide checkout plus per-user target, sudo installs, CI/service accounts, Docker images where the repo is copied in by one UID and the entrypoint runs as another), this uid comparison will always fail and the installer will exit 1 on an otherwise legitimate, first-time install - see inline comment. Every other place in this codebase that does this ownership check (dispatcher_trust_anchor, workflow_control_trust_anchor) compares against the currently executing helper files own owner, which is self-consistent because that file has already been copied under the target. The installer-time check compares against the pre-install source tree instead, which is a different identity in common multi-user deployments.

Security consistency nit
bin/agent_doctor/signed_launch_readiness.rb enforces an RSA 2048-bit-or-larger floor on the dispatcher/workflow-control public trust anchors before reporting supported (matching the CHANGELOGs "2048-bit anchor floors" claim). However, the actual signature-verification code paths that consume those same anchor files - dispatcher_trust_anchor/launch_observation_signature_verified? in dispatcher-capability-preflight and workflow_control_trust_anchor/workflow_control_receipt_signature_verified? in batch-plan-preflight - only check that the key is an RSA public key, with no bit-length check. A sub-2048-bit anchor would therefore report readiness as UNKNOWN (informational) while still being accepted for real launch-confirmation/lifecycle-receipt signature verification. This predates this PR but the new readiness features claims about key strength do not actually hold end-to-end; worth tightening for consistency now that the floor is a documented property.

Other observations (not blocking)

  • AgentDoctor::SignedLaunchReadiness#read_record validates file safety via File.lstat plus a separate File.read, whereas the new SignedLaunchWaiverRecord.read uses File::NOFOLLOW on a single open. Both are guarded by non-writable-directory checks so the TOCTOU window requires an attacker who already has write access, but the inconsistency in hardening technique within the same PR is worth a look.
  • The bootstrap-waiver / dispatcher-observation design is explicit that file ownership/permissions are "record integrity, not proof a human spoke" (documented in installation-and-upgrades.md). Worth calling out explicitly in review: a compromised or prompt-injected coordinator process running as the same uid as the installed toolkit can author its own bootstrap-waiver file (matching ownership/permissions trivially, since it is the same user) and self-authorize a waived launch. This is a known/documented limitation of the design, not a regression, but reviewers evaluating the security boundary should be aware the waiver path guarantee is procedural/replay-integrity only.

Nice test coverage overall (symlink/ownership/permission negative tests, digest-mutation rejection, cross-batch/lane/route reuse rejection, chronology bounds, URI-scheme allowlist). The main actionable item before merge is the installer ownership check.

Comment thread bin/install-agent-workflows
Comment thread bin/agent_doctor/signed_launch_readiness.rb
@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review

Overview

This PR adds a signed_launch_readiness capability check that is reported independently from install freshness (bin/agent_doctor/signed_launch_readiness.rb), wires it into agent-workflows-status and the installer's post-install summary, and adds a durable, direct-human "waiver" path (signed_launch_waiver.rb / signed_launch_waiver_record.rb) that lets a Codex host with a clean/absent (unsupported) signing setup activate waived-active dispatcher and workflow-control lifecycle states instead of the normal signed confirmed-active path. The installer now also owns creating/validating .agents as a safe, non-symlinked, owner-matched directory without ever writing key material into it.

The scope is large (19 files, ~2.2k additions) but almost entirely additive, and the new adversarial test coverage is unusually thorough — mutation tests on the waiver record, cross-batch/lane/route/dispatcher reuse rejection, symlink/ancestor-chain attacks, RSA key-length floors, chronology bounds, etc.

Code quality / style

  • The new Ruby modules (AgentDoctor::SignedLaunchReadiness, SignedLaunchWaiver, SignedLaunchWaiverRecord) are small, single-purpose, and follow the existing repo convention of exact key-set validation (record.keys.sort == expected) rather than permissive hash access — good, since it prevents smuggling extra fields through a "valid-looking" record.
  • Fail-closed defaults are consistent: anything ambiguous, partial, or malformed resolves to UNKNOWN/rejection rather than a default-allow.
  • Test additions mirror the style of existing tests in the same files (shared fixture helpers, secure_mktmpdir, etc.), so this fits the codebase well.

Findings

I left two inline comments on concrete issues:

  1. bin/install-agent-workflows — the post-install readiness probe loads agent_doctor/signed_launch_readiness from the source checkout ($repo_root/bin) rather than the just-installed copy under $target/bin/. Since the module's trust check compares directory ownership against File.stat(__FILE__).uid (the loaded file's owner), this makes the printed "Signed launch readiness: …" line depend on who owns the source checkout, not who owns $target/$target/.agents (which the installer deliberately pins to Process.euid). Whenever those differ (shared checkout, sudo-run installs, etc.), the install-time message will spuriously say UNKNOWN even though agent-workflows-status run immediately afterward (which loads the installed copy) would correctly report unsupported/supported. It fails closed so it's not a security issue, but it undermines the accuracy of the very signal this PR is introducing, and the new test_install_uses_effective_uid_instead_of_source_owner_for_safe_target test — which sets up exactly this owner-mismatch scenario — never asserts on the printed readiness value, so this slipped past it.

  2. bin/agent_doctor/signed_launch_readiness.rbread_record validates the capability/trust-anchor files with a File.lstat check followed by a separate File.read(path, ...) call, leaving a TOCTOU window where the file could be swapped for a symlink between the two calls. The sibling file added in this same PR, signed_launch_waiver_record.rb#read, solves the identical problem correctly by opening with File::NOFOLLOW and validating via the file descriptor's fstat before reading. Given how much of this PR's test suite is dedicated to exactly this class of race/symlink attack, this looks like an inconsistency rather than an intentional relaxation, and the read path for signed-launch-capability.json / dispatcher-launch-trust.json / workflow-control-lifecycle-trust.json is arguably higher-value (it directly gates supported vs unsupported) than the waiver file it was applied to.

Other observations (not blocking)

  • docs/installation-and-upgrades.md and the CHANGELOG.md entry are thorough and match the implemented behavior well.
  • The RSA key-length floor (>= 2048 bits) was correctly added in all three signature/anchor verification sites touched by this PR (dispatcher-capability-preflight, batch-plan-preflight, signed_launch_readiness.rb), with matching weak-key regression tests in each.
  • The launch_waiver/launch_confirmation mutual-exclusion and "exactly one persisted assignment" guards in dispatcher-capability-preflight look correct and are exercised by tests.

Risk

Low-to-moderate. The core waiver trust boundary (filesystem ownership of the waiver record, replay binding via canonical SHA-256 digest, exact batch/lane/dispatcher/route matching) is well thought out and heavily tested, and the two issues above are edge cases rather than exploitable bypasses of the readiness/waiver gates themselves.

Comment thread bin/install-agent-workflows Outdated
Comment thread bin/agent_doctor/signed_launch_readiness.rb Outdated

@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: 181f10e8e2

ℹ️ 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 bin/agent_doctor/signed_launch_waiver.rb

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
bin/agent-workflows-status-test.rb (1)

227-231: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use a compliant-size private-key fixture.

Line [230] uses a 1024-bit private key. The test can pass because the validator rejects the key size, even if it accepts private-key material. Use STRONG_RSA_KEY.to_pem for this scenario and keep the separate 1024-bit test for key-size rejection.

Proposed fix
-            record["agent_workflow_dispatcher_trusted_public_key_pem"] = OpenSSL::PKey::RSA.generate(1024).to_pem
+            record["agent_workflow_dispatcher_trusted_public_key_pem"] = STRONG_RSA_KEY.to_pem
🤖 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 `@bin/agent-workflows-status-test.rb` around lines 227 - 231, Update the
“private-key” scenario in the fixture setup to assign STRONG_RSA_KEY.to_pem
instead of generating a 1024-bit RSA key, while preserving the separate 1024-bit
fixture for testing key-size rejection.
skills/plan-pr-batch/bin/batch-plan-preflight-test.rb (1)

297-316: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add contract coverage for lane_lifecycle_waivers.

input_for always emits lane_lifecycle_waivers, while the helper treats a missing key as [] and only validates it if present. Add a test where lane_lifecycle_waivers is omitted so the pre-v2/backward-compatibility contract is documented explicitly.

🤖 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 `@skills/plan-pr-batch/bin/batch-plan-preflight-test.rb` around lines 297 -
316, Add a contract test for the pre-v2/backward-compatible input path that
omits lane_lifecycle_waivers entirely, rather than passing an empty array.
Update or extend input_for usage as needed to remove the key from the generated
payload, then assert the preflight validation accepts the missing key and treats
it as empty.
🧹 Nitpick comments (3)
bin/install-agent-workflows-test.bash (1)

65-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the stat shim decorate the real stat object, as the lstat shim does.

The stat override returns Struct.new(:uid), which exposes only uid. The lstat override keeps the real stat and redefines uid. If any code under test reads another field such as mode or directory? from the shimmed path, the stat shim raises NoMethodError instead of failing the intended assertion. Use the same decoration in both overrides.

♻️ Proposed symmetric shim
         def stat(path, *args)
           result = agent_workflows_original_stat(path, *args)
           return result unless File.expand_path(path.to_s) == ENV["AGENT_WORKFLOWS_TEST_SOURCE_OWNER_PATH"]
 
-          Struct.new(:uid).new(Process.euid.zero? ? 1 : 0)
+          result.define_singleton_method(:uid) { Process.euid.zero? ? 1 : 0 }
+          result
         end
🤖 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 `@bin/install-agent-workflows-test.bash` around lines 65 - 70, Update the stat
override to retain the object returned by agent_workflows_original_stat and
decorate its uid value, matching the existing lstat override. Preserve all other
File::Stat methods and attributes, including mode and directory?, while keeping
the existing uid behavior for AGENT_WORKFLOWS_TEST_SOURCE_OWNER_PATH.
skills/pr-batch/bin/dispatcher-capability-preflight-test.rb (1)

2760-2764: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the exact rejection reason, as the sibling trust-anchor tests do.

The test asserts only invalid-input. Every other trust-anchor rejection test in this file also asserts "launch_confirmation must be a well-formed identity-bound confirmation". Without that assertion, the test still passes if a later change rejects the input for an unrelated reason, so it would stop proving that the RSA-1024 anchor is what fails.

♻️ Proposed assertion
     assert_equal "invalid-input", output.fetch("status")
+    assert_equal "launch_confirmation must be a well-formed identity-bound confirmation",
+                 output.fetch("reason")
     refute(Array(output["active_assignments"]).any? do |assignment|
       assignment["lifecycle"] == "confirmed-active"
     end)
🤖 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 `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb` around lines
2760 - 2764, Add an assertion in the affected trust-anchor rejection test to
verify the exact rejection reason is "launch_confirmation must be a well-formed
identity-bound confirmation", alongside the existing status, active-assignment,
and dispatch assertions. Use the same output field and assertion pattern as the
sibling trust-anchor tests.
bin/agent_doctor/signed_launch_readiness.rb (1)

101-112: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Align read_record with the descriptor-based read used by SignedLaunchWaiverRecord.read.

read_record validates File.lstat(path) and then calls File.read(path). The path can be replaced between the two calls. Also, File.read follows symlinks, so the stat.file? check does not prove the opened file is the inspected inode. bin/agent_doctor/signed_launch_waiver_record.rb already opens once with File::NOFOLLOW and validates file.stat. Use the same pattern here for consistent trust-material reads.

🔒 Proposed descriptor-based read
     def read_record(path, root:, agents:)
       return unless [root, agents].all? { |directory| safe_owned_directory?(directory) }
 
       helper_uid = File.stat(__FILE__).uid
-      stat = File.lstat(path)
-      return unless stat.file? && stat.uid == helper_uid && (stat.mode & 0o022).zero?
-
-      record = JSON.parse(File.read(path, encoding: "UTF-8"))
-      record if record.is_a?(Hash)
+      File.open(path, File::RDONLY | File::NOFOLLOW) do |file|
+        stat = file.stat
+        next unless stat.file? && stat.uid == helper_uid && (stat.mode & 0o022).zero?
+
+        record = JSON.parse(file.read.force_encoding("UTF-8"))
+        record if record.is_a?(Hash)
+      end
     rescue Errno::ENOENT, Errno::ENOTDIR
       nil
     end
🤖 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 `@bin/agent_doctor/signed_launch_readiness.rb` around lines 101 - 112, Update
read_record to open the record once using the descriptor-based pattern from
SignedLaunchWaiverRecord.read, including File::NOFOLLOW, then validate the
opened descriptor with file.stat for regular-file type, ownership, and
permissions before parsing its contents. Replace the separate File.lstat and
File.read calls while preserving the existing directory checks and rescue
behavior.
🤖 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 `@bin/agent-workflows-status-test.rb`:
- Around line 371-382: Extend
test_check_failed_payload_without_resolved_target_reports_typed_unknown_readiness
to assert that signed_launch_readiness.ready is false and
signed_launch_readiness.waiver_allowed is false for the unresolved target
payload, while preserving the existing status, target, capability, and reason
assertions.

---

Outside diff comments:
In `@bin/agent-workflows-status-test.rb`:
- Around line 227-231: Update the “private-key” scenario in the fixture setup to
assign STRONG_RSA_KEY.to_pem instead of generating a 1024-bit RSA key, while
preserving the separate 1024-bit fixture for testing key-size rejection.

In `@skills/plan-pr-batch/bin/batch-plan-preflight-test.rb`:
- Around line 297-316: Add a contract test for the pre-v2/backward-compatible
input path that omits lane_lifecycle_waivers entirely, rather than passing an
empty array. Update or extend input_for usage as needed to remove the key from
the generated payload, then assert the preflight validation accepts the missing
key and treats it as empty.

---

Nitpick comments:
In `@bin/agent_doctor/signed_launch_readiness.rb`:
- Around line 101-112: Update read_record to open the record once using the
descriptor-based pattern from SignedLaunchWaiverRecord.read, including
File::NOFOLLOW, then validate the opened descriptor with file.stat for
regular-file type, ownership, and permissions before parsing its contents.
Replace the separate File.lstat and File.read calls while preserving the
existing directory checks and rescue behavior.

In `@bin/install-agent-workflows-test.bash`:
- Around line 65-70: Update the stat override to retain the object returned by
agent_workflows_original_stat and decorate its uid value, matching the existing
lstat override. Preserve all other File::Stat methods and attributes, including
mode and directory?, while keeping the existing uid behavior for
AGENT_WORKFLOWS_TEST_SOURCE_OWNER_PATH.

In `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb`:
- Around line 2760-2764: Add an assertion in the affected trust-anchor rejection
test to verify the exact rejection reason is "launch_confirmation must be a
well-formed identity-bound confirmation", alongside the existing status,
active-assignment, and dispatch assertions. Use the same output field and
assertion pattern as the sibling trust-anchor tests.
🪄 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: d206cf7f-6716-40cd-9214-d525d979ca04

📥 Commits

Reviewing files that changed from the base of the PR and between 6f39f42 and 181f10e.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • CONTEXT.md
  • bin/agent-workflows-status
  • bin/agent-workflows-status-test.rb
  • bin/agent_doctor/signed_launch_readiness.rb
  • bin/agent_doctor/signed_launch_waiver.rb
  • bin/agent_doctor/signed_launch_waiver_record.rb
  • bin/install-agent-workflows
  • bin/install-agent-workflows-test.bash
  • docs/installation-and-upgrades.md
  • skills/plan-pr-batch/SKILL.md
  • skills/plan-pr-batch/bin/batch-plan-preflight
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb
  • skills/pr-batch/SKILL.md
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight-test.rb
  • workflows/pr-processing.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • CHANGELOG.md
  • CONTEXT.md
  • skills/plan-pr-batch/bin/batch-plan-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • workflows/pr-processing.md
  • bin/agent_doctor/signed_launch_waiver.rb
  • bin/agent-workflows-status
  • bin/install-agent-workflows

Comment thread bin/agent-workflows-status-test.rb
…gned-launch-readiness

* origin/main:
  fix: allow YAML timestamps in action scanner (#305)
Comment thread bin/agent_doctor/signed_launch_waiver.rb
Comment thread bin/install-agent-workflows
Comment thread skills/pr-batch/bin/dispatcher-capability-preflight Outdated
@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review: Report unsupported signed launch readiness (#306)

Overview

This PR adds a new, self-contained "signed launch readiness" subsystem: agent-workflows-status now reports a typed supported / unsupported / UNKNOWN capability independent of install freshness (bin/agent_doctor/signed_launch_readiness.rb), the installer provisions (but never populates) a host-owned .agents/ trust directory, and a new durable, exact-batch/lane/route-bound human waiver path (signed_launch_waiver.rb / signed_launch_waiver_record.rb) lets an unsupported Codex host bypass the RSA-signed launch-confirmation requirement under tight, fail-closed constraints. dispatcher-capability-preflight and batch-plan-preflight are extended to accept and validate these waivers, persisting a distinct waived-active lifecycle so it's never conflated with a cryptographically confirmed-active one.

Strengths

  • Consistently fail-closed: partial/malformed/symlinked/mismatched/writable trust material all collapse to UNKNOWN, and UNKNOWN can never be waived.
  • Installer changes correctly refuse to replace or chmod pre-existing unsafe user-owned paths rather than silently "fixing" them — tests explicitly assert the unsafe artifact is left untouched.
  • Newly added RSA key-size floor (>= 2048 bits) closes a real gap in the existing dispatcher/workflow-control trust-anchor checks, not just the new code.
  • Receipt/waiver identifier components are now restricted to a safe charset, closing a URI-ambiguity/collision class of issue in the durable-reference scheme.
  • Very thorough test coverage for the new paths (symlink rejection, ownership mismatch, weak keys, cross-batch/lane/route reuse, mutated canonical records, chronology edge cases).

Findings (posted inline)

  1. Likely correctness/reliability gapsigned_launch_waiver.rb's dispatcher_chronology_valid? re-checks that the waiver's observed_at is within 300s of "now" on every dispatcher-capability-preflight call while an assignment is waived-active (enforced by the mandatory-launch_waiver-on-replay check in dispatcher-capability-preflight). confirmed-active assignments need no such re-validation on replay. Unless a fresh live observation is expected to be minted by the coordinator on every single touch (undocumented, untested), any waived lane running past 5 minutes will start failing preflight with invalid-input instead of a routine replay-already-active. Worth confirming intent and adding a test for "replay after the freshness window elapses."
  2. Minor duplicationbin/install-agent-workflows's new ensure_signed_launch_directories duplicates the same symlink/ownership/permission-check block for $target and $target/.agents. Low risk, but security-sensitive logic living in two copies is easy to let drift apart; consider extracting a shared helper.

Other notes (not blocking)

  • The human-waiver trust model is filesystem-permission-based rather than cryptographic (unlike the RSA-signed dispatcher/workflow-control paths), which the PR description/docs explicitly acknowledge ("Digest integrity does not prove a human"). Worth a final sanity check from stakeholders that this residual trust level is acceptable for a security-boundary bypass mechanism, even though it's tightly scoped.
  • The signed_launch_readiness.rb and signed_launch_waiver_record.rb modules independently reimplement very similar "owned directory / open-with-NOFOLLOW-then-fstat" primitives. Not a bug, but worth watching — any future fix to one needs to be mirrored in the other.

No security vulnerabilities (injection, path traversal, TOCTOU beyond the pre-existing pattern) found beyond what's noted above; the new code is fail-closed by default everywhere I checked.

@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: 5490d2747d

ℹ️ 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/pr-batch/bin/dispatcher-capability-preflight Outdated
@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review: Report unsupported signed launch readiness (#299)

Overview

This PR adds a "signed-launch readiness" subsystem that is deliberately independent from install freshness, plus a tightly-scoped human waiver path for Codex hosts that haven't yet integrated host-owned signing. Main pieces:

  • bin/agent_doctor/signed_launch_readiness.rb (new) — reads/validates host capability + trust-anchor files, classifies supported / unsupported / UNKNOWN.
  • bin/agent_doctor/signed_launch_waiver.rb / signed_launch_waiver_record.rb (new) — validates the durable human bootstrap waiver and binds it to dispatcher/workflow-control lifecycle wrappers.
  • bin/install-agent-workflows — creates/validates .agents ownership/permissions, reports readiness post-install (non-fatal on probe failure).
  • skills/pr-batch/bin/dispatcher-capability-preflight and skills/plan-pr-batch/bin/batch-plan-preflight — wire the waiver into dispatch/lifecycle-receipt validation, add a waived-active lifecycle, and raise the RSA trust-anchor floor to 2048 bits.
  • Docs + a large, well-targeted test suite across all of the above.

Code quality

  • Consistently fail-closed: any partial/malformed/symlinked/writable state collapses to UNKNOWN, never unsupported/supported. That's the right default for security-sensitive code.
  • Strict keys.sort == expected schema checks throughout reject any record with unexpected extra fields rather than silently ignoring them — deliberate whitelisting rather than a permissive parser.
  • The 2048-bit RSA floor is applied consistently at all three verification points (signed_launch_readiness.rb, batch-plan-preflight, dispatcher-capability-preflight) — no leftover path still accepts the previous weaker key size.
  • Waiver replay binds the full canonical SHA-256 digest of both the durable bootstrap record and the activated wrapper, so mutating the waiver file after activation (even at the same path) is rejected — this is exercised directly by test_lifecycle_waiver_rejects_mutated_canonical_record_content_at_the_same_path and its dispatcher-side equivalent.

Security

  • No shell/path injection: the installer's post-install readiness probe passes $host/$target as separate ruby -e ARGV entries rather than interpolating into the script body, and the final printf uses a fixed '%s\n' format string rather than embedding untrusted values in the format itself.
  • File reads for capability/trust/waiver records use File::RDONLY | File::NOFOLLOW plus a post-open fstat check (owner/uid/perm bits), which avoids the classic TOCTOU symlink-swap between an existence check and the actual read.
  • Directory ownership/writability checks (mode & 0o022) are applied at every level of the path chain (safe_ancestor_chain? in signed_launch_waiver_record.rb), not just the leaf file.

Concerns / suggestions

  • Scale for review purposes: this is dense, security-critical trust/crypto logic spread across 3 new files and 2 modified preflight scripts (~2500 lines changed). It reads as internally consistent, and the accompanying test suite is unusually thorough (symlink swaps, ownership mismatches, weak keys, mutated records, cross-batch/lane/route reuse, chronology violations, world-writable TMPDIR, etc.). Given the PR description already flags that independent final-head QA/review is pending, I'd treat that as the higher-value next step over further diff-level read-throughs — this class of code benefits most from someone deliberately trying to construct a bypass rather than reviewing the diff top-to-bottom.
  • Two smaller, non-blocking items called out inline below: a permission-mode behavior change on upgrade for existing installs, and a narrow rescue clause in the readiness record reader.

Performance

Negligible — a handful of small local stat/JSON reads per status/dispatch invocation, no loops over unbounded input.

Test coverage

Strong on both new libraries and the two modified preflight scripts, with good negative-case coverage (mutation, replay-after-expiry, cross-lane/batch/route reuse, weak keys, symlinked install roots, duplicate signed+waived records for one lane).

Note: given the size of this diff, I focused on the core trust logic (signed_launch_readiness, signed_launch_waiver*) and its two callers rather than re-deriving every branch of the full ~2500-line change.

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

ℹ️ 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/pr-batch/bin/dispatcher-capability-preflight
Comment thread skills/plan-pr-batch/bin/batch-plan-preflight
@justin808

Copy link
Copy Markdown
Member Author

Address-review checkpoint for head 3768b06. Scope: review activity since the previous checkpoint at 2026-08-02T10:54:55Z.

Resolved outcomes:

  • Fixed: installation-host binding now comes only from validated flat-install metadata; clean Claude installs cannot use the Codex waiver path.
  • Declined with rationale: v1 direct-human provenance remains an explicitly procedural, owner-safe durable record rather than cryptographic proof; all other gates stay non-waived.
  • Fixed: changelog wording is concise and links to the detailed safety contract.
  • Fixed fail-closed: plugin source/cache helpers cannot borrow caller-selected companion-home state; use the validated flat helper until authenticated host binding exists.
  • Fixed: lifecycle receipt identifiers are validated before any lane launch.

Validation:

  • dispatcher-capability-preflight-test: 92 runs, 980 assertions, green.
  • batch-plan-preflight-test: 68 runs, 587 assertions, green.
  • install-agent-workflows-test: green.
  • repository validation: all behavioral suites green; one unrelated pr-merge-submit wall-clock assertion flaked under local load (2.91s vs 2s) and passed immediately in isolation (0.82s). RuboCop 1.87.0 is green on the changed files.

All five source threads have evidence replies and are resolved. Historical review-summary bodies in scope did not add a distinct unresolved blocker beyond their inline findings. Current-head hosted validation and configured reviews remain the merge gate.

…gned-launch-readiness

* origin/main:
  Report source lines in security preflight findings (#311)
Comment thread bin/agent_doctor/signed_launch_installation.rb
@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review

Overview

This PR adds a signed_launch_readiness capability, reported independently from install freshness (supported / unsupported / fail-closed UNKNOWN), plus a tightly-scoped human waiver path for Codex hosts that are typed unsupported. It also extracts a shared AgentDoctor::SignedLaunchInstallation module used by both batch-plan-preflight and dispatcher-capability-preflight to resolve the installation root, replacing each script's previous ad-hoc File.realpath(__FILE__)-based trust resolution with validated symlink-install detection.

Strengths

  • Defense in depth on file access: reads of capability/trust/waiver records consistently open with File::NOFOLLOW, re-check stat.uid/(mode & 0o022).zero? on the open file descriptor (not just a prior lstat), and validate UTF-8 — a solid TOCTOU-resistant pattern applied uniformly across signed_launch_readiness.rb, signed_launch_installation.rb, and signed_launch_waiver_record.rb.
  • Symlink-install hardening: SignedLaunchInstallation.resolve now requires an exact chain (real non-writable root, matching skills/bin/.agents symlinks, strict install-receipt binding) before trusting a symlink-delivered helper's lexical root — closing an "arbitrary invocation symlink redirects trust" gap that plain File.realpath resolution didn't guard against.
  • Waiver scope is genuinely narrow: SignedLaunchWaiver/SignedLaunchWaiverRecord bind the durable human waiver to exact batch/lane/dispatcher/route, require direct-human provenance with an exact non-UNKNOWN issue match, canonicalize+digest the record to detect mutation, enforce a 300s freshness window on every replay (not just activation), and explicitly forbid fallbacks/inherited routing/generated keys via the constraints schema. The waived-active lifecycle is kept distinct from confirmed-active throughout (status text, replay reason, assignment merge), so it can't be silently conflated with a real signed confirmation.
  • RSA key strength is now enforced (>= 2048 bits) for both the pre-existing signed-receipt trust anchors and the new capability trust anchors — good tightening while in the area.
  • Test coverage is unusually thorough for the threat model described (symlinked .agents, writable/wrong-owner directories, mutated waiver records, cross-batch/lane/route reuse, stale/future-skewed observations, weak keys, URI-ambiguous identifiers in receipt refs, plugin-companion binding, etc.).

Issues / suggestions

  • Minor (code quality): left an inline note on bin/agent_doctor/signed_launch_installation.rbresolve returns a non-nil hash with "host" => nil in the direct-helper/no-metadata/no-companion branch instead of nil. It's harmless in practice (everything downstream fails closed on a nil/mismatched host), but it makes "resolve returned non-nil" not actually mean "a validated install was found," which is a bit of a footgun for future callers.
  • Doc duplication maintenance cost: the large Accepted binding evidence is ... / new Signed-launch readiness is separate from install freshness ... paragraphs are now duplicated near-verbatim across CONTEXT.md, docs/agent-workflows-model-routing.md, docs/pr-batch-skills.md, skills/pr-batch/SKILL.md, skills/triage/SKILL.md, and workflows/pr-processing.md. This pre-dates this PR, but this PR grows it further — six places to keep in sync by hand for any future correction to the signed-launch contract. Not asking for a rework here, just flagging the ongoing cost given how easy it'd be for one copy to drift.
  • CI environment caveat: the PR description notes the local full-gate run required overriding BASH_ENV/ENV because ambient Homebrew Bash 5.3 reproducibly deadlocked heredoc fixtures, with "no source workaround included." Worth confirming the actual CI runner doesn't hit the same deadlock before merge, since that's exactly the kind of environment-dependent flake that's easy to miss in a review.

Security

No exploitable issue found on manual read-through. The trust boundary described in the PR body holds up under inspection: private keys stay host-owned, the installer never synthesizes capability/trust/waiver material, and partial/malformed/symlinked/writable state fails to UNKNOWN rather than silently degrading to unsupported (which is the only state a waiver can be layered onto). The one thing I'd want independent eyes on (beyond what the batch-state note already calls for) is the interaction between SignedLaunchInstallation.resolve's new symlink-validation path and any less-common install layouts (e.g. plugin-companion homes) not covered by the new tests — the logic is dense enough that a second reviewer tracing through valid_symlink_install?/validated_plugin_companion independently seems worthwhile given this is trust-anchor resolution.

Test coverage

Extensive new Minitest coverage in bin/agent-workflows-status-test.rb, bin/install-agent-workflows-test.bash, skills/plan-pr-batch/bin/batch-plan-preflight-test.rb, and skills/pr-batch/bin/dispatcher-capability-preflight-test.rb. Nothing missing jumped out; the negative-path coverage (mutated records, stale observations, weak keys, cross-lane/batch/route reuse, symlink/ownership attacks) is notably more thorough than the positive-path coverage, which is the right emphasis for this kind of change.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
skills/plan-pr-batch/bin/batch-plan-preflight (1)

317-319: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Reject a missing stage dependency plan ID instead of raising KeyError.

Line 318 calls stage_dependency_plan.fetch("id") before validate_stage_dependencies validates the object. If the plan is {} and a waiver names a valid lane, preflight raises KeyError instead of returning a rejected result.

Proposed fix
 def validate_lane_lifecycle_records(receipts, waivers, plan, stage_dependency_plan)
   receipt_violations, receipt_lane_ids = validate_lane_lifecycle_receipts(receipts, plan, stage_dependency_plan)
   violations = receipt_violations.dup
   waiver_lane_ids = []
+  stage_dependency_plan_id =
+    stage_dependency_plan.is_a?(Hash) ? stage_dependency_plan["id"] : nil
   unless waivers.is_a?(Array)
     # ...
   end

   # ...
       valid = lane && valid_lane_lifecycle_waiver?(
-        waiver, plan, stage_dependency_plan.fetch("id"), lane
+        waiver, plan, stage_dependency_plan_id, lane
       )
🤖 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 `@skills/plan-pr-batch/bin/batch-plan-preflight` around lines 317 - 319, Update
the validation flow around valid_lane_lifecycle_waiver? so a missing "id" in
stage_dependency_plan is rejected as an invalid result before calling
fetch("id"), while preserving normal validation for plans that include an ID and
preventing KeyError from escaping preflight.
skills/pr-batch/SKILL.md (2)

839-843: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Do not expose GitHub tokens to interpreter validation unless the entire interpreter chain is trusted.

Both contracts accept an identity-recorded absolute interpreter spawn after a validated check; replacing outside the consumer repository with ownership/fixed-path validation does not eliminate the TOCTOU risk that a replaced interpreter or writable ancestor directory can run with the guard’s GitHub environment.

  • skills/pr-batch/SKILL.md#L839-L843: require descriptor-based execution or a trusted-owner, non-writable interpreter chain.
  • workflows/pr-processing.md#L2933-L2937: apply the same validation before spawning the guard.
🤖 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 `@skills/pr-batch/SKILL.md` around lines 839 - 843, Update the interpreter
validation and spawn guidance in skills/pr-batch/SKILL.md lines 839-843 and
workflows/pr-processing.md lines 2933-2937 so GitHub tokens are exposed only
when the entire interpreter chain is trusted: require descriptor-based execution
or validate trusted ownership and non-writable permissions for the interpreter
and every ancestor directory before spawning the guard, and apply the same rule
in both locations.

831-834: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize the source Git origin before invoking the trusted guard.

Both guarded-direct materializations preserve the source origin in the isolated Git root inside .git/config. If that origin carries URL userinfo, Git operations can expose credentials in the protected checkout and to child Git processes. Reject credential-bearing origins or materialize a redacted non-credential origin before the guard runs.

🤖 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 `@skills/pr-batch/SKILL.md` around lines 831 - 834, Sanitize the source Git
origin before either guarded-direct materialization invokes the trusted guard:
reject credential-bearing URL userinfo or replace it with a redacted
non-credential origin before writing the isolated repository’s .git/config.
Apply this guidance at skills/pr-batch/SKILL.md lines 831-834 and
workflows/pr-processing.md lines 2925-2928, keeping the existing
HEAD/index/worktree isolation and source-origin behavior for safe origins.
🧹 Nitpick comments (1)
bin/agent_doctor/signed_launch_installation.rb (1)

158-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the owner-validated JSON reader into one shared helper.

read_metadata duplicates SignedLaunchWaiverRecord.read (bin/agent_doctor/signed_launch_waiver_record.rb lines 36-49) almost exactly: the same File.const_defined?(:NOFOLLOW) guard, the same descriptor-level file?/uid/0o022 checks, the same UTF-8 validation, and the same JSON object parse. Both sites gate trust decisions, so the two copies must not drift.

signed_launch_waiver_record.rb already requires this file. Expose one reader here, for example read_owned_json(path, owner_uid), and call it from the waiver record reader after its ancestor-chain check.

🤖 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 `@bin/agent_doctor/signed_launch_installation.rb` around lines 158 - 174,
Extract the shared owner-validated JSON reading logic from read_metadata into a
reusable private-class helper such as read_owned_json(path, owner_uid),
preserving the NOFOLLOW, descriptor ownership/permission, UTF-8 validation, and
Hash-only JSON behavior. Update read_metadata and SignedLaunchWaiverRecord.read
to use this helper, with the waiver reader retaining its existing ancestor-chain
validation before calling it.
🤖 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 `@bin/agent_doctor/signed_launch_waiver_record.rb`:
- Around line 18-20: Add the complete REQUIRED_GATES token list to the waiver
documentation generated by waiver, using the exact strings—including the entries
shown near REQUIRED_GATES—so authors can copy them and bootstrap? validation
succeeds.

In `@CONTEXT.md`:
- Line 150: Separate confirmation-record versioning from signed-observation
payload versioning so launch-confirmation v2 cannot serialize as version 1;
introduce distinct confirmation and observation version fields and update
replay/migration wording accordingly. Apply the same terminology and distinction
in CONTEXT.md:150, docs/agent-workflows-model-routing.md:308,
docs/pr-batch-skills.md:181, skills/pr-batch/SKILL.md:98,
skills/triage/SKILL.md:257, and workflows/pr-processing.md:781.

In `@docs/agent-workflows-model-routing.md`:
- Line 310: Clarify the naming of the durable bootstrap waiver record across the
documentation: either designate agent-workflow-bootstrap-waiver v1 as the
canonical name wherever launch_waiver v1 refers to the same record, or
explicitly document both as aliases. If they represent distinct records,
document each record’s lifecycle and conversion behavior.

In `@docs/installation-and-upgrades.md`:
- Around line 87-88: Use agent-workflow-bootstrap-waiver v1 consistently as the
durable human-approval record name across
docs/installation-and-upgrades.md:87-88,
docs/agent-workflows-model-routing.md:310, docs/pr-batch-skills.md:182,
skills/pr-batch/SKILL.md:99, and workflows/pr-processing.md:783. Replace
launch_waiver v1 references and descriptions of it as an input or wrapper, or
explicitly document its mapping and canonical serialized type if that wrapper
must remain.

---

Outside diff comments:
In `@skills/plan-pr-batch/bin/batch-plan-preflight`:
- Around line 317-319: Update the validation flow around
valid_lane_lifecycle_waiver? so a missing "id" in stage_dependency_plan is
rejected as an invalid result before calling fetch("id"), while preserving
normal validation for plans that include an ID and preventing KeyError from
escaping preflight.

In `@skills/pr-batch/SKILL.md`:
- Around line 839-843: Update the interpreter validation and spawn guidance in
skills/pr-batch/SKILL.md lines 839-843 and workflows/pr-processing.md lines
2933-2937 so GitHub tokens are exposed only when the entire interpreter chain is
trusted: require descriptor-based execution or validate trusted ownership and
non-writable permissions for the interpreter and every ancestor directory before
spawning the guard, and apply the same rule in both locations.
- Around line 831-834: Sanitize the source Git origin before either
guarded-direct materialization invokes the trusted guard: reject
credential-bearing URL userinfo or replace it with a redacted non-credential
origin before writing the isolated repository’s .git/config. Apply this guidance
at skills/pr-batch/SKILL.md lines 831-834 and workflows/pr-processing.md lines
2925-2928, keeping the existing HEAD/index/worktree isolation and source-origin
behavior for safe origins.

---

Nitpick comments:
In `@bin/agent_doctor/signed_launch_installation.rb`:
- Around line 158-174: Extract the shared owner-validated JSON reading logic
from read_metadata into a reusable private-class helper such as
read_owned_json(path, owner_uid), preserving the NOFOLLOW, descriptor
ownership/permission, UTF-8 validation, and Hash-only JSON behavior. Update
read_metadata and SignedLaunchWaiverRecord.read to use this helper, with the
waiver reader retaining its existing ancestor-chain validation before calling
it.
🪄 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: 108f54d3-a734-42e2-b3a3-61e27f23fc85

📥 Commits

Reviewing files that changed from the base of the PR and between 181f10e and 0526c9d.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • CONTEXT.md
  • bin/agent-workflows-status-test.rb
  • bin/agent_doctor/signed_launch_installation.rb
  • bin/agent_doctor/signed_launch_readiness.rb
  • bin/agent_doctor/signed_launch_waiver.rb
  • bin/agent_doctor/signed_launch_waiver_record.rb
  • bin/install-agent-workflows
  • bin/install-agent-workflows-test.bash
  • docs/agent-workflows-model-routing.md
  • docs/installation-and-upgrades.md
  • docs/pr-batch-skills.md
  • skills/plan-pr-batch/SKILL.md
  • skills/plan-pr-batch/bin/batch-plan-preflight
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb
  • skills/pr-batch/SKILL.md
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight-test.rb
  • skills/pr-batch/bin/model-routing-contract-test.rb
  • skills/triage/SKILL.md
  • workflows/pr-processing.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • bin/agent_doctor/signed_launch_readiness.rb
  • CHANGELOG.md
  • bin/install-agent-workflows
  • bin/agent-workflows-status-test.rb
  • bin/install-agent-workflows-test.bash
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight-test.rb
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb

Comment thread bin/agent_doctor/signed_launch_waiver_record.rb
Comment thread CONTEXT.md Outdated
Comment thread docs/agent-workflows-model-routing.md Outdated
Comment thread docs/installation-and-upgrades.md Outdated

@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: 0526c9d4e4

ℹ️ 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 bin/agent_doctor/signed_launch_installation.rb Outdated
Comment thread bin/agent_doctor/signed_launch_waiver.rb Outdated
Comment thread skills/pr-batch/bin/dispatcher-capability-preflight Outdated
Comment thread bin/agent_doctor/signed_launch_installation.rb Outdated
…gned-launch-readiness

* origin/main:
  Add explicit multi-language lint CI (#313)
  Reconcile later-completed audit targets (#315)

# Conflicts:
#	CHANGELOG.md
#	bin/install-agent-workflows
…gned-launch-readiness

* origin/main:
  Emit coordination telemetry and provenance at workflow checkpoints (#290)

# Conflicts:
#	CONTEXT.md
#	docs/agent-workflows-model-routing.md
#	docs/pr-batch-skills.md
#	skills/pr-batch/SKILL.md
#	skills/pr-batch/bin/dispatcher-capability-preflight
#	skills/pr-batch/bin/model-routing-contract-test.rb
#	skills/triage/SKILL.md
#	workflows/pr-processing.md
@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review: Report unsupported signed-launch readiness (#299)

Overview

This PR adds a signed_launch_readiness capability report (supported / unsupported / UNKNOWN) independent of install freshness, teaches the installer to create/validate a host-owned .agents directory without ever generating trust material, and introduces a tightly-scoped, exact-binding human waiver path (agent-workflow-bootstrap-waiver v1) for typed-unsupported Codex hosts. It's a large, security-critical change (23 files, +3686/-60) with extensive new test coverage across bin/agent_doctor/signed_launch_readiness.rb, signed_launch_installation.rb, signed_launch_waiver.rb, and signed_launch_waiver_record.rb, plus integration into agent-workflows-status, install-agent-workflows, dispatcher-capability-preflight, and batch-plan-preflight.

Strengths

  • Consistent fail-closed design: any partial/malformed/symlinked/writable/mismatched state resolves to UNKNOWN, never a false supported/unsupported.
  • New readers (signed_launch_readiness.rb, signed_launch_waiver_record.rb) correctly avoid symlink-swap TOCTOU by opening with File::RDONLY | File::NOFOLLOW and re-checking the fd's stat before parsing.
  • The waiver model is carefully exact-bound: batch/lane/dispatcher/route/model/effort, canonical SHA-256 digest binding across dispatcher and lifecycle wrappers, a bounded liveness window (300s) for replay/heartbeats, and an explicit not_waived gate list that can't omit required gates.
  • Public RSA anchors are now required to be ≥2048 bits (public_key.n.num_bits >= 2048) — a good hardening addition applied consistently in signed_launch_readiness.rb's public_anchor? and both existing trust-anchor verifiers.
  • Good test coverage of edge cases: invalid UTF-8 records, symlinked trust files, 1024-bit keys, world-writable files, stale/future-skewed observations, duplicate lane completion via receipt+waiver, etc.

Issue found

TOCTOU inconsistency in the two pre-existing trust-anchor readers. dispatcher_trust_anchor (skills/pr-batch/bin/dispatcher-capability-preflight:105-131) and workflow_control_trust_anchor (skills/plan-pr-batch/bin/batch-plan-preflight:427-) both validate the trust file via File.lstat(TRUST_CONFIG_PATH) and then separately reopen it by path via File.read(TRUST_CONFIG_PATH). Between the lstat and the read, the path could be swapped for a symlink to an attacker-controlled file (classic check-then-use race), and the read would silently follow it. This PR touches both functions directly (switching helper_uid to INSTALLATION_OWNER_UID and adding the new >= 2048 bit check), and it introduces the correct fix for this exact class of bug elsewhere in the same PR — signed_launch_readiness.rb and signed_launch_waiver_record.rb both open the file with File::RDONLY | File::NOFOLLOW and validate the opened file descriptor's stat, not a second lstat on the path. Since the safer pattern was clearly known and used in new code in this same change, it'd be worth aligning dispatcher_trust_anchor/workflow_control_trust_anchor to the same NOFOLLOW-fd pattern rather than leaving the older TOCTOU-prone path-based read in place. (Exploitability is bounded by requiring same-privilege write access to the .agents directory, so this is a hardening/consistency gap rather than a high-severity bug, but it undermines the "opened once without following the final symlink" invariant the PR's own docs (docs/installation-and-upgrades.md) claim for the waiver file.)

Minor observations

  • bin/install-agent-workflows's ensure_signed_launch_directories has the same check-then-use shape (mkdir, then a separate ruby -e stat check) rather than an atomic O_NOFOLLOW-based check; likely fine given install-time same-user threat model, but noting for consistency with the hardened pattern used elsewhere.
  • The large "Accepted binding evidence is..." paragraph is duplicated near-verbatim across CONTEXT.md, docs/agent-workflows-model-routing.md, skills/triage/SKILL.md, and workflows/pr-processing.md, and this PR edits all four copies in lockstep. That's a pre-existing repo convention (self-contained docs per surface), not something introduced here, but it does raise the risk of the four copies drifting out of sync in future edits.

Security

The core trust model looks sound: private keys stay host-owned, .agents/ is created/validated but never seeded with keys or capability claims, waivers are exact-bound and fail closed on any ambiguity, and replay requires fresh liveness evidence rather than trusting caller-supplied state. The one concrete gap is the TOCTOU inconsistency above.

Test coverage

Coverage is thorough and includes adversarial cases (symlinks, permission bits, weak keys, encoding, chronology, duplicate lifecycle records). No gaps stood out beyond the trust-anchor TOCTOU path itself not having a regression test (understandably, since it's a pre-existing pattern not newly introduced).

Comment thread skills/pr-batch/bin/dispatcher-capability-preflight
Comment thread skills/plan-pr-batch/bin/batch-plan-preflight

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
skills/pr-batch/bin/dispatcher-capability-preflight-test.rb (1)

296-312: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The shim swallows Errno::ENOENT from the original File.stat.

The rescue clause covers the whole method body, including line 301. If agent_workflows_original_stat raises Errno::ENOENT or Errno::ENOTDIR, the shim returns nil instead of propagating the error. File.stat then returns nil for missing paths for every path in the process, not only paths under the source root. That can change the failure mode of the code under test and make test_validated_symlink_install_uses_the_installed_home_owner_for_dispatcher_trust pass for the wrong reason.

Limit the rescue to the File.realpath call.

🐛 Proposed fix to scope the rescue
         def stat(path, *args)
           result = agent_workflows_original_stat(path, *args)
           source_root = ENV.fetch("AGENT_WORKFLOWS_TEST_SOURCE_OWNER_ROOT")
-          expanded = File.realpath(path.to_s)
+          expanded = begin
+            File.realpath(path.to_s)
+          rescue Errno::ENOENT, Errno::ENOTDIR
+            nil
+          end
           return result unless expanded == source_root || expanded.start_with?("\#{source_root}/")
 
           result.define_singleton_method(:uid) { Process.euid.zero? ? 1 : 0 }
           result
-        rescue Errno::ENOENT, Errno::ENOTDIR
-          result
         end
🤖 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 `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb` around lines 296
- 312, In the File.stat shim’s singleton method, limit the Errno::ENOENT and
Errno::ENOTDIR rescue to only the File.realpath(path.to_s) call. Ensure
agent_workflows_original_stat continues propagating missing-path errors, while
realpath failures still return the original stat result.
skills/plan-pr-batch/SKILL.md (1)

606-607: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Fail closed on ad-hoc targets instead of recording an unqualified preflight bypass.

trusted-direct adhoc:=>skip is not enforced by pr-security-preflight; the helper does not parse ad-hoc inputs. The preflight rule in skills/pr-batch/SKILL.md already says do not pass adhoc: targets to it and to record the trusted direct user instruction separately. Make the generated Goal prompt follow that rule, and reject ad-hoc lanes in this skill/workflow unless the direct instruction has trusted, authenticated host provenance. Add a negative test for an untrusted ad-hoc target.

🤖 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 `@skills/plan-pr-batch/SKILL.md` around lines 606 - 607, Update the generated
Goal prompt in the lane-card/preflight guidance to exclude adhoc: targets from
pr-security-preflight and record trusted-direct instructions separately. Add
validation in the relevant plan/batch workflow to reject adhoc lanes unless the
direct instruction has trusted, authenticated host provenance, and add a
negative test covering an untrusted adhoc target.
workflows/pr-processing.md (1)

2096-2100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep mirrored label removal claim-specific.

After agent-coord release, a replacement can acquire the lane and reapply agent-claimed before this cleanup removes it. The cleanup can then remove the replacement worker's label.

Bind label removal to the released holder and generation. Do not remove the label unless it still represents the cancelled claim.

Based on learnings: verify that the lane still matches the claim holder and generation before removing the mirrored label.

🤖 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 `@workflows/pr-processing.md` around lines 2096 - 2100, Update the cancellation
cleanup instructions around agent-coord release so mirrored agent-claimed label
removal is conditional on the lane still matching the released claim’s holder
and generation. Do not remove the label if a replacement has acquired the lane
or re-applied the label; only remove it when it still represents the cancelled
claim.

Source: Learnings

skills/pr-batch/SKILL.md (1)

314-318: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the complete helper-resolution chain.

Line 315 states that the command resolves three locations. Lines 316-317 use only the environment variable and the repo-local fallback. A loaded installed skill can therefore contain pr-security-preflight but still fail to run it.

Resolve PR_BATCH_SKILL_DIR through the loaded skill base before .agents/skills/pr-batch, then stop with a blocker when no helper exists.

Based on learnings: resolve PR_BATCH_SKILL_DIR through environment, loaded skill base, then repo-local pinned copy, and stop.

🤖 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 `@skills/pr-batch/SKILL.md` around lines 314 - 318, Update the
PR_BATCH_SKILL_DIR resolution instructions and invocation to check the explicit
environment value, then the loaded skill base, then the repo-local pinned copy;
verify that pr-security-preflight exists in the selected directory and stop with
a blocker if no candidate provides it.

Source: Learnings

🧹 Nitpick comments (2)
skills/pr-batch/bin/dispatcher-capability-preflight-test.rb (2)

2352-2437: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared waived-active fixture for these two tests.

test_waived_active_replay_rejects_legacy_confirmation_without_its_durable_waiver and test_waived_active_replay_rejects_pre_actual_host_confirmation_without_its_durable_waiver repeat the same 20 lines of setup. They differ only in the confirmation flavor and the added write_supported_readiness(root) call. Both assert the same status and reason.

Extract a helper that returns the helper path, root, and activated waived assignment. Then drive both cases from one table.

♻️ Sketch of the shared fixture
def activated_waived_lane
  helper, root = installed_unsupported_dispatcher_helper
  route = { "model" => "gpt-5.6-sol", "effort" => "xhigh" }
  input = {
    "batch_id" => "batch-299",
    "expected_issue" => "shakacode/agent-workflows#299",
    "lane_id" => "aw299-implementation",
    "requested" => { "route" => route, "dispatcher" => "codex-collaboration", "hard_route" => true },
    "candidates" => [{
      "route" => route, "dispatcher" => "codex-collaboration", "binding" => "dispatcher-bound",
      "attestation" => "instance-bound", "instance_id" => "live-worker-299"
    }]
  }
  pending = dispatch(input, helper)
  waiver_path, waiver_record = bootstrap_waiver(
    root, batch_id: input.fetch("batch_id"), lane_id: input.fetch("lane_id"), route:
  )
  waiver = launch_waiver(path: waiver_path, record: waiver_record, assignment: pending.fetch("dispatch"))
  activated = dispatch(
    input.merge("active_assignments" => pending.fetch("active_assignments"), "launch_waiver" => waiver), helper
  )
  [helper, root, input, activated.fetch("active_assignments").fetch(0)]
end

def test_waived_active_replay_rejects_confirmations_without_their_durable_waiver
  helper, root, input, active_assignment = activated_waived_lane
  legacy = {
    "type" => "launch-confirmation", "version" => 1,
    "id" => "legacy-launch-confirmation", "assignment" => active_assignment
  }
  write_supported_readiness(root)
  historical = pre_actual_host_v2_confirmation(active_assignment)

  { "legacy v1" => legacy, "pre-actual_host v2" => historical }.each do |label, confirmation|
    replay = dispatch(
      input.merge("candidates" => [], "active_assignments" => [active_assignment],
                  "launch_confirmation" => confirmation),
      helper
    )

    assert_equal "invalid-input", replay.fetch("status"), label
    assert_equal "waived-active assignment requires its durable launch_waiver", replay.fetch("reason"), label
  end
end
🤖 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 `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb` around lines
2352 - 2437, Extract the duplicated waived-active setup from
test_waived_active_replay_rejects_legacy_confirmation_without_its_durable_waiver
and
test_waived_active_replay_rejects_pre_actual_host_confirmation_without_its_durable_waiver
into an activated_waived_lane helper returning helper, root, input, and
active_assignment. Replace the two tests with one table-driven test covering the
legacy and pre-actual_host confirmations, retaining
write_supported_readiness(root) only for the latter case and applying the shared
status and reason assertions to both.

415-425: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Ignore fixture directories created in the skill tree.

secure_mktmpdir creates @secure_fixture_root under skills/pr-batch/bin, and teardown only purges it after the run completes. Add a gitignore rule for directories starting with dispatcher-preflight-fixtures in that tree so an interrupted or aborted test cannot leave tracked files exposed.

🤖 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 `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb` around lines 415
- 425, The test creates temporary dispatcher-preflight-fixtures directories
under the skill tree without ignoring them. Add a gitignore rule scoped to the
skills/pr-batch/bin tree that matches directories beginning with
dispatcher-preflight-fixtures, preserving normal tracking for other files.
🤖 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 `@bin/agent_doctor/signed_launch_installation_test.rb`:
- Around line 12-23: The test_platform_symlink_ancestor_is_canonicalized fixture
does not guarantee that the selected ancestor is a symlink, so it may bypass the
platform-symlink branch. Update the test to create and use a real symlink
ancestor, or skip it unless the selected ancestor is confirmed to be a symlink,
ensuring the assertion exercises canonicalization rather than direct_helper.

In `@docs/agent-workflows-model-routing.md`:
- Around line 308-310: Replace the duplicated signed-launch policy with one
canonical summary or reference, preserving RSA anchors of at least 2048 bits and
waiver observations bounded in both directions with a non-future grant. Update
docs/agent-workflows-model-routing.md lines 308-310, docs/pr-batch-skills.md
lines 181-182, skills/pr-batch/SKILL.md lines 98-99, and skills/triage/SKILL.md
line 259; keep the SKILL.md guidance concise and portable. Update
skills/pr-batch/bin/model-routing-contract-test.rb lines 117-168 to validate the
canonical rule and required references instead of large verbatim policy copies.

---

Outside diff comments:
In `@skills/plan-pr-batch/SKILL.md`:
- Around line 606-607: Update the generated Goal prompt in the
lane-card/preflight guidance to exclude adhoc: targets from
pr-security-preflight and record trusted-direct instructions separately. Add
validation in the relevant plan/batch workflow to reject adhoc lanes unless the
direct instruction has trusted, authenticated host provenance, and add a
negative test covering an untrusted adhoc target.

In `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb`:
- Around line 296-312: In the File.stat shim’s singleton method, limit the
Errno::ENOENT and Errno::ENOTDIR rescue to only the File.realpath(path.to_s)
call. Ensure agent_workflows_original_stat continues propagating missing-path
errors, while realpath failures still return the original stat result.

In `@skills/pr-batch/SKILL.md`:
- Around line 314-318: Update the PR_BATCH_SKILL_DIR resolution instructions and
invocation to check the explicit environment value, then the loaded skill base,
then the repo-local pinned copy; verify that pr-security-preflight exists in the
selected directory and stop with a blocker if no candidate provides it.

In `@workflows/pr-processing.md`:
- Around line 2096-2100: Update the cancellation cleanup instructions around
agent-coord release so mirrored agent-claimed label removal is conditional on
the lane still matching the released claim’s holder and generation. Do not
remove the label if a replacement has acquired the lane or re-applied the label;
only remove it when it still represents the cancelled claim.

---

Nitpick comments:
In `@skills/pr-batch/bin/dispatcher-capability-preflight-test.rb`:
- Around line 2352-2437: Extract the duplicated waived-active setup from
test_waived_active_replay_rejects_legacy_confirmation_without_its_durable_waiver
and
test_waived_active_replay_rejects_pre_actual_host_confirmation_without_its_durable_waiver
into an activated_waived_lane helper returning helper, root, input, and
active_assignment. Replace the two tests with one table-driven test covering the
legacy and pre-actual_host confirmations, retaining
write_supported_readiness(root) only for the latter case and applying the shared
status and reason assertions to both.
- Around line 415-425: The test creates temporary dispatcher-preflight-fixtures
directories under the skill tree without ignoring them. Add a gitignore rule
scoped to the skills/pr-batch/bin tree that matches directories beginning with
dispatcher-preflight-fixtures, preserving normal tracking for other files.
🪄 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: 0fcfcf7d-5315-4e76-b939-00519dabde2e

📥 Commits

Reviewing files that changed from the base of the PR and between 0526c9d and f55faa8.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • CONTEXT.md
  • bin/agent_doctor/signed_launch_installation.rb
  • bin/agent_doctor/signed_launch_installation_test.rb
  • bin/agent_doctor/signed_launch_waiver.rb
  • bin/agent_doctor/signed_launch_waiver_record.rb
  • docs/agent-workflows-model-routing.md
  • docs/installation-and-upgrades.md
  • docs/pr-batch-skills.md
  • skills/plan-pr-batch/SKILL.md
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb
  • skills/pr-batch/SKILL.md
  • skills/pr-batch/bin/dispatcher-capability-preflight
  • skills/pr-batch/bin/dispatcher-capability-preflight-test.rb
  • skills/pr-batch/bin/model-routing-contract-test.rb
  • skills/triage/SKILL.md
  • workflows/pr-processing.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • CONTEXT.md
  • docs/installation-and-upgrades.md
  • bin/agent_doctor/signed_launch_waiver.rb
  • bin/agent_doctor/signed_launch_waiver_record.rb
  • bin/agent_doctor/signed_launch_installation.rb
  • skills/plan-pr-batch/bin/batch-plan-preflight-test.rb

Comment thread bin/agent_doctor/signed_launch_installation_test.rb
Comment thread docs/agent-workflows-model-routing.md Outdated

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

ℹ️ 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 docs/installation-and-upgrades.md Outdated
Comment thread bin/agent_doctor/signed_launch_waiver.rb
Comment thread bin/agent_doctor/signed_launch_waiver_record.rb
@claude

claude Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review: Report unsupported signed-launch readiness (#306)

Overview

This PR adds a new signed_launch_readiness capability report (supported / Codex-only-clean unsupported / fail-closed UNKNOWN), wires it into agent-workflows-status and install-agent-workflows, and adds a tightly-scoped, durable human-waiver mechanism (agent-workflow-bootstrap-waiver v1) that lets a human authorize dispatcher launch and workflow-control-lifecycle completion for an exact batch/lane/dispatcher/route on a clean-unsupported Codex host — without ever letting the installer or a caller synthesize keys, signatures, or trust anchors.

Code quality

  • The new AgentDoctor::SignedLaunchReadiness / SignedLaunchInstallation / SignedLaunchWaiver / SignedLaunchWaiverRecord modules are consistently fail-closed: every file read uses File.open(path, File::RDONLY | File::NOFOLLOW) + fstat (not a separate stat+read), which avoids the classic TOCTOU symlink-swap race between checking and reading a file. This pattern is applied uniformly across signed_launch_readiness.rb, signed_launch_waiver_record.rb, signed_launch_installation.rb, and the two preflight helpers updated to use it (dispatcher-capability-preflight, batch-plan-preflight).
  • Good consistency fix bundled into this PR: RSA trust-anchor validation now uniformly requires >= 2048 bits (n.num_bits >= 2048) in signed_launch_readiness.rb, dispatcher-capability-preflight, and batch-plan-preflight — previously only some of these paths checked key strength.
  • Test coverage is extensive and well-targeted: dangling/live symlinks, invalid UTF-8, weak keys, wrong ownership, world-writable dirs, TMPDIR portability, cross-batch/lane/route/dispatcher reuse, mutated waiver records, plugin-source vs. flat-install provenance, and replay stability are all exercised (bin/agent-workflows-status-test.rb, bin/agent_doctor/signed_launch_installation_test.rb, skills/plan-pr-batch/bin/batch-plan-preflight-test.rb, skills/pr-batch/bin/dispatcher-capability-preflight-test.rb).
  • Left two inline comments on points worth the author confirming are intentional rather than oversights:
    1. signed_launch_waiver.rb — the lifecycle waiver's chronology check has no freshness/max-age bound (unlike the dispatcher observation's 300s window), so a granted lifecycle waiver can mark a lane completed indefinitely into the future on replay.
    2. signed_launch_waiver_record.rbwaiver_ref isn't confined to a fixed location (e.g. under .agents/); it can be anywhere the installation owner can write, as long as the whole ancestor chain is owned by root/owner and not group/world-writable. Doesn't appear to widen the actual trust boundary (same uid already controls .agents/), but worth a short comment noting this is deliberate (likely for TMPDIR portability, per test_positive_lifecycle_waiver_is_portable_when_tmpdir_is_world_writable).

Minor nit

  • SignedLaunchReadiness.assess's top-level rescue SystemCallError, JSON::ParserError, OpenSSL::PKey::PKeyError (bin/agent_doctor/signed_launch_readiness.rb) is dead code — every helper it calls (read_record, public_anchor?) already rescues those same exceptions internally. Harmless, but could be dropped for clarity, or a comment added if it's intentional defense-in-depth.

Maintainability observation (non-blocking)

The long "Accepted binding evidence is..." / "Signed-launch readiness is separate from install freshness..." contract paragraphs are duplicated near-verbatim across 6 files (CONTEXT.md, docs/agent-workflows-model-routing.md, docs/pr-batch-skills.md, skills/plan-pr-batch/SKILL.md, skills/pr-batch/SKILL.md, skills/triage/SKILL.md, workflows/pr-processing.md), kept in sync only by model-routing-contract-test.rb's exact-string assertions. That's an existing pattern in the repo, not introduced by this PR, but this PR adds another paragraph to the pile — worth considering a single canonical source (e.g. one doc file, included/linked from the others) the next time this contract needs to change, since a 6-way hand-edit is easy to get subtly wrong without the test catching wording drift in prose that isn't asserted verbatim.

Security

No blocking issues found. The trust model is consistently local-filesystem-ownership-based (root or a single validated owner uid, no group/world write), applied uniformly to directories, files, and their entire ancestor chains, with explicit tests for symlink/ownership/permission bypass attempts. The waiver path is clearly scoped (exact batch/lane/dispatcher/model/effort/route binding, non-UNKNOWN fields enforced recursively via nested_unknown?, mandatory not_waived gate list) and cannot be reached except from an exactly-typed unsupported readiness assessment.

Size

This is a large, dense PR (25 files, ~3800 additions) implementing a security-critical trust boundary. Per the PR description, independent final-head QA/review is still pending — given the density of the fail-closed logic here, I'd recommend that pass pay particular attention to the two points flagged inline above.

@justin808

Copy link
Copy Markdown
Member Author

Address-review summary

Scan scope: full history via check all reviews

Mattered

  • Fixed both trust-anchor TOCTOU gaps by opening with RDONLY plus NOFOLLOW, validating the opened descriptor, and reading through it; regressions cover both helpers (70f8a21).
  • Fixed the clean-host fail-open found by independent review: signed confirmation now requires complete supported readiness for dispatcher and workflow-control trust, with a partial-readiness rejection regression (70f8a21).
  • Fixed the platform-symlink test so it only passes when the canonicalization branch is actually exercised (70f8a21).
  • Fixed fail-closed ad-hoc provenance, helper resolution, cancellation claim identity, plugin-companion documentation, prompt-contract guarantees, and interrupted-fixture cleanup (70f8a21).
  • Confirmed the late lifecycle and waiver-path questions are intentional: terminal lifecycle receipts are durable exact-binding replay evidence, while active launch liveness remains 300-second bounded; waiver storage is portable but owner, ancestry, permission, digest, and identity constrained. Both threads received rationale and were resolved.
  • Preserved the portable self-contained routing contract while adding the omitted RSA-2048 and bidirectional/non-future freshness requirements; a broad canonicalization refactor was declined for this security fix head.

Optional

  • Declined the duplicated waived-active fixture refactor: the cases retain distinct preconditions and the current form is clearer for security regressions.
  • Retained the top-level readiness rescue as harmless defense-in-depth; all current helpers already handle the same failures locally.

Skipped

  • Reviewer status posts, acknowledgments, and positive summaries required no code change.

Validation: focused dispatcher, batch-plan, installation, routing, installer, and status suites passed; local bin/validate passed; independent Codex review found no remaining actionable defect; exact-head hosted Validate, Lint, Claude review, and CodeRabbit checks passed. All review threads are resolved.

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

@justin808

Copy link
Copy Markdown
Member Author

Closing without merge by maintainer product decision.

The implementation is no longer the desired architecture. Agent Workflows is a portable workflow source pack and should not create a project-level signing, trust-anchor, readiness, or waiver system for runtime facts owned by Codex or Claude. Model and effort routes will be advisory; unavailable preferences will not block ordinary work.

Superseding ownership:

This closure is a change in product direction, not a claim that the final #306 code was defective. The branch is intentionally preserved for historical review; none of its waiver machinery should be copied into the replacement implementation.

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.

Remove unsupported signed-launch enforcement and restore advisory routing

1 participant