Skip to content

Move non-compile CI jobs to GitHub-hosted runners - #322

Open
leynos wants to merge 5 commits into
fix/rustsec-wasmtime-rkyvfrom
ci/free-runners-for-glue-jobs
Open

Move non-compile CI jobs to GitHub-hosted runners#322
leynos wants to merge 5 commits into
fix/rustsec-wasmtime-rkyvfrom
ci/free-runners-for-glue-jobs

Conversation

@leynos

@leynos leynos commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Pull Request

Summary

  • Move five non-compile CI jobs from ubicloud-standard-8 to GitHub-hosted
    ubuntu-latest: the two pull-request labelling workflows, the
    regression-test check, the Claude Code review, and the weekly dependency
    audit. None of them compiles the workspace — they are single-threaded shell
    scripts, an action call, or an API-bound agent run — and Axinite is a public
    repository, so hosted runners execute them at no cost.
  • A July 2026 Ubicloud usage audit attributed roughly 1,600 billed premium-8
    minutes per month to these five workflows.
  • Pin the resulting split as a contract test:
    tests/workflow_contracts/runner_policy_test.py records the runner for
    every job in the repository, so adding a job or moving one between pools
    fails until RUNNER_POLICY is updated deliberately. The suite also asserts
    that no free-runner job runs a compile command or installs a Rust build
    cache, and that the five migrated jobs kept their triggers, permissions,
    guards, and steps.
  • Document the policy in docs/developers-guide.md §5 and record the decision
    in ADR 013.
  • Compile-bound workflows (test.yml, code_style.yml, coverage.yml,
    codescene-coverage.yml, e2e.yml, staging-ci.yml) stay on Ubicloud.
    release-plz.yml is deliberately untouched: its jobs are gated to the
    nearai repository owner and never execute here.

Two further commits fix pre-existing gate failures that this branch's
commit gates surfaced. Neither originates here — the branch changes no
.rs files — so they are self-contained and can be split out on request:

  • f872cb09 clears 102 no_expect_outside_tests findings. Local
    whitaker-installer is 0.2.7 while CI pins 0.2.6, and 0.2.7 no longer
    treats #[cfg(test)] helper functions as test code. Test arrangement
    helpers now return Result and only test bodies unwrap. The CI pin is
    unchanged; this removes the backlog a future bump would surface at once.
  • 1bf74b4e fixes html_to_markdown::convert_test_pages_to_markdown,
    failing since d0efc5d1 reflowed the converter's golden fixtures to 80
    columns. The fixtures record converter output, which does not wrap.

This PR is stacked on #323
(cargo-audit fixes), which should merge first; the base branch is
fix/rustsec-wasmtime-rkyv.

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

None.

Review walkthrough

  • Start with tests/workflow_contracts/runner_policy_test.py — the
    RUNNER_POLICY table is the whole change in one place.
  • Then the runner-only diffs:
    pr-label-classify.yml,
    pr-label-scope.yml, and
    regression-test-check.yml
    labelling scripts and a git diff/grep check, no build step.
  • claude-review.yml — API-bound; the
    action prompt already instructs the agent not to build or test, and the
    contract test now asserts that instruction stays.
  • audit.yml — installs cargo-audit as a
    prebuilt binary and reads the lockfile only.
  • Finish with docs/adr-013-split-ci-runners-by-compile-cost.md for the
    rationale and the alternatives considered.

Validation

  • make check-fmt
  • make lint
  • make typecheck
  • Relevant tests pass: make test-workflow-contracts — 60 passed,
    including the new runner_policy_test.py suite.
  • make test — 4244 tests run, 4244 passed, 8 skipped.
  • make markdownlint, make spelling, make nixie — clean.
  • Manual testing: actionlint on the five changed workflows is clean. The
    runner-policy table was verified to fail closed by temporarily editing a
    recorded runner and confirming the drift assertion fires.

Security Impact

None. The change is runs-on only; no permissions, secrets, triggers, or
step logic were altered, and the contract tests now assert each migrated
workflow's permissions block and trigger set explicitly.

Worth noting for reviewers: the two labelling workflows use
pull_request_target and therefore run with repository write scope. Their
trigger, permissions, checkout ref, and step list are unchanged and are now
pinned by tests — moving to a GitHub-hosted runner does not widen that
surface, and the jobs continue to check out the base branch rather than
untrusted head code.

Database Impact

None. No migrations, schema changes, or store code are touched.

Blast Radius

CI and test code only; no runtime, library, or binary code changes. The
Whitaker commit touches 43 test modules but changes no production code and
no assertion outcomes.

  • The five migrated jobs now depend on GitHub's shared hosted-runner
    concurrency instead of Ubicloud's. If hosted capacity is congested, these
    checks queue longer. They run on every pull-request event, so a regression
    is visible on the next PR rather than at a distance.
  • The scheduled audit and the label-gated Claude review are the only migrated
    jobs not on the pull-request critical path.
  • Adding a workflow or a job now fails make test-workflow-contracts until
    RUNNER_POLICY is updated. That is intended friction, but it is a new way
    for an unrelated CI PR to go red.

Rollback Plan

Revert the merge commit. The change is five one-line runs-on edits plus
additive tests, docs, and test-only refactoring, so a straight git revert
restores the previous behaviour with no data, schema, or deployment state to
unwind. The four commits are independent and revert cleanly on their own.
To roll back a single workflow instead, set its runs-on back to ubicloud-standard-8 and
update the matching entry in RUNNER_POLICY and FREE_RUNNER_JOBS.


Review track: C (CI)

References

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Move non-compile CI jobs to GitHub-hosted runners

  • Run five non-compile jobs on ubuntu-latest.
  • Keep compile-bound workflows on ubicloud-standard-8.
  • Preserve triggers, permissions, guards, and steps.
  • Reduce approximately 1,600 billed premium-8 runner minutes per month.
  • Enforce runner assignments with workflow contract tests.
  • Document the policy in ADR 013 and the developers guide.
  • Improve test error propagation and restore byte-sensitive Markdown fixtures.
  • Validate with actionlint, 60 workflow-contract tests, and 4,244 project tests.

Walkthrough

The pull request defines compile-cost-based CI runner policy, adds workflow contract tests and documentation, makes test fixtures propagate errors, separates attachment tests, and excludes byte-sensitive fixtures from formatter discovery.

Changes

CI runner policy and test reliability

Layer / File(s) Summary
CI runner policy
.github/workflows/*, tests/workflow_contracts/runner_policy_test.py, docs/*, Makefile
Move five non-compiling jobs to ubuntu-latest. Add runner policy contracts and document the policy.
Fallible test support
src/**/tests/*
Replace internal panics with propagated Result errors across test fixtures, assertions, database setup, filesystem setup, and server tasks.
Attachment and fixture validation
src/channels/wasm/wrapper/tests/dispatch/*, tests/test-pages/*
Add attachment-preservation tests. Reflow expected Markdown fixtures without changing content.
Tooling configuration
.fdignore, typos*.toml
Exclude byte-sensitive fixtures from formatter discovery. Update typo-ignore patterns.

Possibly related PRs

Suggested reviewers: codescene-access

Poem

Runners shift to lighter ground,
Clearer test errors now resound.
Fixtures fail with context near,
Attachments keep their details clear.
CI rules guide every round.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning The guide and ADR state that every non-compiling job uses ubuntu-latest, but test.yml:audit runs lockfile-only make audit on Ubicloud; the policy records this exception. Amend the guide and ADR to describe the actual workflow/job exceptions, or migrate the remaining non-compiling jobs before claiming a universal per-job rule.
Unit Architecture ❓ Inconclusive Investigation is still in progress; no verdict has been reached. Gather code evidence before deciding.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: moving non-compiling CI jobs to GitHub-hosted runners.
Description check ✅ Passed The description follows the template, explains the CI changes, records impacts and rollback, and documents comprehensive validation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Testing (Overall) ✅ Passed Accept: runner_policy_test.py parses every workflow, checks exact per-job runners, blocks compile/cache use on free jobs, and verifies migrated triggers, permissions, guards, and steps.
User-Facing Documentation ✅ Passed Pass this check: the PR changes CI runners, developer documentation, and test code only; no user-guide or production-source changes require user-facing documentation.
Module-Level Documentation ✅ Passed Verify that every changed Rust module starts with a //! purpose description and the new Python contract-test module has a detailed docstring covering purpose and policy relationships.
Testing (Unit And Behavioural) ✅ Passed Accept the testing: runner_policy_test.py parses every workflow and checks runners, reusable pins, compile restrictions, triggers, permissions and steps; integration tests also cover attachment pre...
Testing (Property / Proof) ✅ Passed No generated input, state, ordering, or transition domain was introduced; the finite YAML domain is exhaustively checked across every workflow and job by parameterised contract tests.
Testing (Compile-Time / Ui) ✅ Passed Rust edits are confined to test modules, with no TypeScript or production compile-time API behaviour; converter golden files and focused YAML contract assertions cover the text and structured outputs.
Domain Architecture ✅ Passed PASS — Keep the domain boundary intact: all Rust changes are under test paths, and workflow edits only change runs-on; no production domain or adapter logic changed.
Observability ✅ Passed PASS — Treat this as CI-only: GitHub Actions exposes queue, status, timing, and step logs, while contract tests preserve each migrated job's triggers, permissions, guards, and steps.
Security And Privacy ✅ Passed Pass the check: the complete diff adds no production code or secrets; the five workflow edits change only runners and preserve triggers, permissions, checkout refs, and steps.
Performance And Resource Use ✅ Passed The five workflow diffs change only runs-on; all 43 Rust edits are test-only, and the policy checks process finite workflow/job data with no new unbounded runtime work.
Concurrency And State ✅ Passed Keep this change: Rust edits are test-only; local mutexes have narrow scopes, spawned servers are returned, aborted and awaited, and tests cover parallel tools plus task cancellation and shutdown.
Architectural Complexity And Maintainability ✅ Passed Accept this change: the runner policy enforces a documented invariant, while the new Rust support modules remain test-local extractions with no new dependencies or production architecture.
Rust Compiler Lint Integrity ✅ Passed Accept this check: the PR adds no Rust lint suppressions or artificial anchors; support modules are harness-local, and added clones have explicit snapshot, shared-ownership, or response-generation...
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/free-runners-for-glue-jobs

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Updates five non-compile GitHub Actions workflows to run on GitHub-hosted ubuntu-latest runners instead of Ubicloud, without changing any job logic, triggers, or permissions.

File-Level Changes

Change Details Files
Move non-compile CI workflows from Ubicloud runners to GitHub-hosted ubuntu-latest to reduce billed minutes while keeping behavior identical.
  • Updated runs-on for the audit job to use ubuntu-latest
  • Updated runs-on for the Claude code review job to use ubuntu-latest
  • Updated runs-on for the PR label classification job to use ubuntu-latest
  • Updated runs-on for the PR label scope job to use ubuntu-latest
  • Updated runs-on for the regression-test enforcement job to use ubuntu-latest
.github/workflows/audit.yml
.github/workflows/claude-review.yml
.github/workflows/pr-label-classify.yml
.github/workflows/pr-label-scope.yml
.github/workflows/regression-test-check.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added scope: ci CI/CD workflows size: S 10-49 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Aug 5, 2026
codescene-access[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review August 5, 2026 20:54
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

codescene-access[bot]

This comment was marked as outdated.

@leynos
leynos force-pushed the ci/free-runners-for-glue-jobs branch from 0334f5f to d1a42ec Compare August 5, 2026 21:59
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: S 10-49 changed lines labels Aug 5, 2026
codescene-access[bot]

This comment was marked as outdated.

@github-actions github-actions Bot added scope: docs Documentation scope: dependencies Dependency updates labels Aug 5, 2026
@leynos
leynos changed the base branch from main to fix/rustsec-wasmtime-rkyv August 5, 2026 21:59
The PR label classify and scope jobs, the regression-test check, the
Claude Code review, and the weekly dependency audit all ran on
`ubicloud-standard-8`. None of them compiles the workspace: they are
single-threaded script or API-bound jobs (the audit installs
`cargo-audit` as a prebuilt binary and only reads the lockfile), so an
8-vCPU paid runner is wasted on them.

Axinite is a public repository, so `ubuntu-latest` runs these jobs at
no cost. A July 2026 Ubicloud usage audit attributed roughly 1,600
billed premium-8 minutes per month to these five workflows alone.

Compile-bound workflows (tests, staging CI, coverage, CodeScene
coverage) stay on Ubicloud. Release-plz is left untouched because its
jobs are gated to the `nearai` owner and never execute here.
@leynos
leynos force-pushed the ci/free-runners-for-glue-jobs branch from d1a42ec to db44a91 Compare August 5, 2026 22:01
@github-actions github-actions Bot added size: S 10-49 changed lines and removed size: XL 500+ changed lines labels Aug 5, 2026
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access 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.

No quality gates enabled for this code.

leynos added 3 commits August 8, 2026 01:48
The previous commit moved five non-compile jobs to GitHub-hosted runners
but left the split as an undocumented convention. The failure mode is
silent: a new job copied from an existing workflow inherits whichever
runner the template used, and nothing surfaces the mistake until the
next billing audit.

Add tests/workflow_contracts/runner_policy_test.py, which records the
runner for every job in the repository. Adding a workflow or a job, or
moving one between pools, now fails until RUNNER_POLICY is updated
deliberately. The suite also asserts that no job on the free pool runs a
compile command or installs a Rust build cache, and that each of the
five migrated jobs kept its triggers, permissions, guards, and steps —
so the runner move is provably behaviour-preserving.

Reusable-workflow callers are matched by shape, not by SHA value, per
the repository's Dependabot guidance.

Document the policy in the developer's guide and record the decision,
its rationale, and the alternatives considered in ADR 013.
The runner policy table records GitHub's own job identifiers and trigger
values verbatim, so `build-local-artifacts`, `build-global-artifacts`,
and the `labeled` pull-request trigger type cannot be respelled without
breaking the assertions they anchor.

Add narrow ignore patterns for those three literals to the typos overlay
rather than excluding the whole file, so the test's prose stays under
the Oxford spelling gate. Reword the one occurrence that was prose
rather than an upstream identifier.
`whitaker-installer` 0.2.7 narrowed the test-detection heuristic behind
`no_expect_outside_tests`: a function inside a `#[cfg(test)]` module is
no longer treated as test code unless it is itself a recognised test.
Attributes are gone by the time the lint sees HIR, so rstest fixtures,
rstest-bdd steps and plain helper functions all read as production code.
That surfaced 102 findings across 32 modules.

Convert them the way the suite intends: arrangement is not a verdict, so
a helper that can fail returns `Result` and propagates, and only the
test body unwraps. No `#[allow]`, no `#[expect]`, and no helper renamed
to look like a test.

Three cases needed judgement rather than a mechanical rewrite:

- The `tokio::spawn`ed test servers cannot propagate from inside the
  closure, so the fallible work moved out of the closure and the serve
  result now surfaces through the join handle. Every call site already
  aborted the handle and discarded the result, so no verdict changes.
- Trait-impl methods with fixed signatures (`complete_with_tools`,
  `set_setting`) were already fallible, so a poisoned lock now maps to
  the trait's own error rather than panicking. A poisoned lock means the
  double panicked while holding it, which is worth reporting, not
  swallowing.
- rstest-bdd's return classifier only recognises a bare `Result<..>`,
  not the `anyhow::Result` alias, so fallible steps spell the type out
  in full. Aliased steps would have discarded their errors silently.

Splitting the pipeline and dispatch test doubles out of their parent
modules keeps both within the 400-line module limit that the added
error handling pushed them over.

The CI pin stays at 0.2.6; this only removes the backlog that a future
bump would otherwise surface all at once.
codescene-access[bot]

This comment was marked as outdated.

@github-actions github-actions Bot added the scope: channel/web Web gateway channel label Aug 8, 2026
@github-actions github-actions Bot added scope: tool/builtin Built-in tools scope: llm LLM integration scope: orchestrator Container orchestrator scope: worker Container worker scope: agent Agent core (agent loop, router, scheduler) scope: channel/wasm WASM channel runtime size: XL 500+ changed lines and removed size: S 10-49 changed lines labels Aug 8, 2026
`convert_test_pages_to_markdown` has been failing on the Yahoo page since
d0efc5d adopted `mdformat-all` and normalised every tracked Markdown
file. That pass reflowed `tests/test-pages/*/expected.md`, which are not
prose: they are a byte-for-byte record of converter output, and the
converter does not wrap. The test compares line by line, so a fixture
wrapped to 80 columns no longer describes any real conversion.

Restore the three fixtures to their pre-reformat content. The test
passes against them unchanged, which confirms the converter itself never
regressed — only the fixtures did.

`.markdownlint-cli2.jsonc` already excludes these paths, with that exact
rationale, but `mdformat-all` runs `mdtablefix --wrap` before
`markdownlint-cli2` and `mdtablefix` has no exclusion flag. Since both
tools discover files through `fd`, withhold the fixtures there instead.
`.fdignore` rather than `.ignore`, so ripgrep still searches them.
@wafflecat-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🤖 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 `@docs/adr-013-split-ci-runners-by-compile-cost.md`:
- Line 5: Update the ADR headings and metadata by adding separate ## Status and
## Date sections, and rename ## Context to ## Context and Problem Statement.
Remove the combined Status/Date metadata line while preserving its information
under the corresponding sections.

In `@docs/developers-guide.md`:
- Around line 95-98: Update the runner-policy explanation in the developer guide
to remove the incorrect claim that release.yml pins ubuntu-22.04, and accurately
describe build-local-artifacts as using its matrix-selected runner. Clarify the
relevant job scope, distinguishing release.yml’s audit behavior from test.yml’s
separate audit job that remains on Ubicloud.

In `@src/agent/dispatcher/tests/auth.rs`:
- Around line 18-36: Remove the detected.is_some() assertion in
assert_auth_detected and let detected.context(...)? return the documented
missing-detection error. Also update the timeout helper in
src/agent/dispatcher/tests/loop_guard.rs at lines 191-215 by removing its
timeout assertion and allowing result.map_err(...)? to return the timeout error;
both sites require direct changes.
- Around line 54-55: Replace the affected helper-call .expect() sites with ? and
update each enclosing test or scenario function to return anyhow::Result<()>,
preserving normal success behavior. Apply this in
src/agent/dispatcher/tests/auth.rs lines 54-55 for assert_auth_detected;
src/agent/dispatcher/tests/image_sentinel.rs lines 245-247 for
run_image_generate_and_count_statuses; src/agent/dispatcher/tests/loop_guard.rs
lines 243-244 for assert_agentic_loop_text_response; both
assert_rendered_snapshot sites in
src/agent/dispatcher/tests/skill_bundle_context_bdd.rs lines 222-223 and
233-234; and every changed skill-construction .expect() site in
src/agent/dispatcher/tests/skills.rs, using proper error propagation throughout
src/agent/**/*.rs.

In `@src/agent/dispatcher/tests/pipeline/support.rs`:
- Around line 155-158: Update the custom-tool registration loop in the pipeline
test support helper to propagate failures from the awaited
deps.tools.register(tool) call with ?, while keeping builtin registration and
successful registrations unchanged.

In `@src/agent/scheduler/tests/approval.rs`:
- Around line 117-123: Update the result-matching helper around result to match
the Result directly without expect_err, assert_eq!, or panic!. Return Ok(())
only for an AuthRequired error whose name matches tool_name; convert unexpected
successes, other errors, and name mismatches into descriptive anyhow errors so
every unexpected outcome propagates through the Result<()> return type.

In `@src/bootstrap/tests/migration_support.rs`:
- Around line 202-214: Refactor RenameFixture::prepare to remove the three-way
match and apply setup through predicates such as should_write_legacy_file and
should_make_directory_read_only derived from RenameSetup. Keep prepare limited
to filesystem mutations, preserving legacy-file creation and Unix
read-only-directory behavior for the corresponding setups.

In `@src/channels/wasm/wrapper/tests/dispatch/attachments.rs`:
- Around line 150-181: Update
test_dispatch_emitted_messages_no_attachments_backward_compat to use the
existing dispatch_messages_for_test helper instead of constructing its own
channel, rate limiter, metadata, and DispatchContext setup. Preserve the test’s
assertions for successful dispatch, message content, and empty attachments while
keeping the shared helper as the sole dispatch harness.

In `@src/skills/registry/tests/install.rs`:
- Around line 34-66: Move the recursive installed-file traversal from the local
collect_installed_files implementation in
src/skills/registry/tests/install.rs:34-66 into shared test support, preserving
relative-path validation and file-read error context; replace that
implementation with an import of the shared collector. In
src/skills/registry/tests/prop_tests.rs:59-91, import the same shared collector
and retain only the TestCaseError conversion, removing its duplicate traversal
logic.

In `@src/worker/api/tests/client_methods.rs`:
- Around line 37-47: Update the server shutdown handling so expected
cancellation after abort is ignored, but all other JoinError values and inner
std::io::Error results are propagated from handle.await and server.await. Apply
this in src/worker/api/tests/client_methods.rs at lines 37-47 and 61-61, and
src/worker/container/tests/hosted_fidelity.rs at lines 31-32 and 97-122;
preserve the existing test-server behavior while removing any unconditional
result ignoring or unwrapping.

In `@src/worker/container/tests/shutdown.rs`:
- Around line 35-49: The shutdown cleanup around spawn_event_server must
gracefully await the server task instead of aborting it and ignoring its result.
Await the tokio::task::JoinHandle with ?, then apply ? again to propagate the
axum::serve std::io::Result error; update the enclosing test/helper flow as
needed to return Result and preserve existing cleanup behavior.

In `@tests/workflow_contracts/runner_policy_test.py`:
- Line 385: Add a descriptive assertion message to the `assert` validating
`checkout.get("uses", "")` in the workflow contract test, while preserving the
existing `startswith("actions/checkout@")` condition.
- Around line 392-394: Split the compound assertion in the workflow contract
test into separate assertions for “git diff” and “git log,” giving each
assertion a failure message that identifies its missing probe while preserving
the existing git-history validation.
- Around line 180-189: Update _workflow_files to include workflow filenames
ending in both .yml and .yaml, while preserving the sorted inventory returned to
test_workflow_inventory_matches_the_recorded_policy. Leave _load and other call
sites unchanged.
- Around line 320-321: Update the action-input handling around the prompt
assertion to assign the action’s “with” value, verify it is a mapping before
accessing it, and only then read its “prompt” field. Ensure an empty “with:”
produces a failed contract assertion rather than an AttributeError, while
preserving the existing prompt string assertion.
🪄 Autofix

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: ASSERTIVE

Plan: Pro Plus

Run ID: fcfe878d-6fb5-4851-8f84-1bd000e24ea2

📥 Commits

Reviewing files that changed from the base of the PR and between 6452f8f and 1bf74b4.

📒 Files selected for processing (59)
  • .fdignore
  • .github/workflows/audit.yml
  • .github/workflows/claude-review.yml
  • .github/workflows/pr-label-classify.yml
  • .github/workflows/pr-label-scope.yml
  • .github/workflows/regression-test-check.yml
  • Makefile
  • docs/adr-013-split-ci-runners-by-compile-cost.md
  • docs/contents.md
  • docs/developers-guide.md
  • src/agent/dispatcher/tests/auth.rs
  • src/agent/dispatcher/tests/image_sentinel.rs
  • src/agent/dispatcher/tests/loop_guard.rs
  • src/agent/dispatcher/tests/pipeline.rs
  • src/agent/dispatcher/tests/pipeline/support.rs
  • src/agent/dispatcher/tests/skill_bundle_context_bdd.rs
  • src/agent/dispatcher/tests/skills.rs
  • src/agent/scheduler/tests/approval.rs
  • src/agent/thread_ops/document_store/tests/mod.rs
  • src/bootstrap/tests/env_format.rs
  • src/bootstrap/tests/migration.rs
  • src/bootstrap/tests/migration_disk_to_db.rs
  • src/bootstrap/tests/migration_rename.rs
  • src/bootstrap/tests/migration_support.rs
  • src/channels/wasm/wrapper/tests/channel/typing.rs
  • src/channels/wasm/wrapper/tests/dispatch.rs
  • src/channels/wasm/wrapper/tests/dispatch/attachments.rs
  • src/channels/web/handlers/skills/tests/helpers.rs
  • src/channels/web/handlers/skills/tests/json.rs
  • src/channels/web/handlers/skills/tests/multipart.rs
  • src/channels/web/server/tests/fixtures.rs
  • src/channels/web/server/tests/oauth.rs
  • src/channels/web/server/tests/relay_oauth.rs
  • src/history/migrations/tests.rs
  • src/history/migrations/tests/postgres_testing.rs
  • src/llm/rig_adapter/tests/unsupported_params.rs
  • src/orchestrator/api/tests/fixtures/remote_tool_helpers.rs
  • src/orchestrator/api/tests/remote_tools/execute.rs
  • src/skills/registry/tests/discovery.rs
  • src/skills/registry/tests/fixtures.rs
  • src/skills/registry/tests/install.rs
  • src/skills/registry/tests/install/lifecycle.rs
  • src/skills/registry/tests/install/payloads.rs
  • src/skills/registry/tests/prop_tests.rs
  • src/tools/builtin/skill_tools/tests/read_file_adapter.rs
  • src/tools/schema_validator/tests/fixture_groups.rs
  • src/worker/api/tests/client_methods.rs
  • src/worker/api/tests/transport_types.rs
  • src/worker/claude_bridge/tests/claude_fs_setup.rs
  • src/worker/container/tests/hosted_fidelity.rs
  • src/worker/container/tests/pre_loop.rs
  • src/worker/container/tests/remote_tools.rs
  • src/worker/container/tests/shutdown.rs
  • tests/test-pages/cnn/expected.md
  • tests/test-pages/medium/expected.md
  • tests/test-pages/yahoo/expected.md
  • tests/workflow_contracts/runner_policy_test.py
  • typos.local.toml
  • typos.toml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/ironclaw (auto-detected)
  • leynos/memoryd (auto-detected)


**Status:** Accepted **Date:** 2026-08-05 **Deciders:** `@leynos`

## Context

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare second-level headings across all ADRs.
fd -t f 'adr-[0-9]{3}-.*\.md' docs --exec sh -c 'echo "== $1"; rg -n "^## " "$1"' _ {}

Repository: leynos/axinite

Length of output: 4415


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- docs/adr-013-split-ci-runners-by-compile-cost.md ---'
sed -n '1,30p' docs/adr-013-split-ci-runners-by-compile-cost.md
printf '%s\n' '--- ADR filename and required-heading checks ---'
python3 - <<'PY'
from pathlib import Path
import re

for path in sorted(Path("docs").glob("adr-[0-9][0-9][0-9]-*.md")):
    text = path.read_text(encoding="utf-8")
    headings = re.findall(r"^## (.+)$", text, re.MULTILINE)
    required = {
        "Status": bool(re.search(r"^## Status$", text, re.MULTILINE)),
        "Date": bool(re.search(r"^## Date$", text, re.MULTILINE)),
        "Context and Problem Statement": bool(
            re.search(r"^## Context and Problem Statement$", text, re.MULTILINE)
        ),
        "Context and problem statement": bool(
            re.search(r"^## Context and problem statement$", text, re.MULTILINE)
        ),
    }
    print(f"{path}: {required}; first headings={headings[:4]}")
PY

Repository: leynos/axinite

Length of output: 4969


Add the required ADR sections.

Use ## Status, ## Date, and ## Context and Problem Statement. The ADR currently combines Status and Date in one metadata line and uses ## Context.

Triage: [type:docstyle]

🤖 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 `@docs/adr-013-split-ci-runners-by-compile-cost.md` at line 5, Update the ADR
headings and metadata by adding separate ## Status and ## Date sections, and
rename ## Context to ## Context and Problem Statement. Remove the combined
Status/Date metadata line while preserving its information under the
corresponding sections.

Source: Coding guidelines

Comment thread docs/developers-guide.md
Comment on lines +95 to +98
Windows jobs use `windows-latest` and `release.yml` pins `ubuntu-22.04`, both
for reproducibility rather than cost. `release-plz.yml` is left on Ubicloud
because its jobs are gated to the `nearai` repository owner and never execute
here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Correct the release.yml runner claim.

The text states that release.yml pins ubuntu-22.04. RUNNER_POLICY records build-local-artifacts as "${{ matrix.runner }}", so that job takes its runner from a matrix rather than a pin. The sentence contradicts the recorded policy it is meant to describe.

Also consider naming job scope for the audit. test.yml declares its own audit job that stays on Ubicloud, so a reader may infer that all audit work moved to the free pool.

♻️ Proposed fix
-Windows jobs use `windows-latest` and `release.yml` pins `ubuntu-22.04`, both
-for reproducibility rather than cost. `release-plz.yml` is left on Ubicloud
-because its jobs are gated to the `nearai` repository owner and never execute
-here.
+Windows jobs use `windows-latest`. `release.yml` pins `ubuntu-22.04` for every
+job except `build-local-artifacts`, which selects its runner from the release
+matrix. Both choices serve reproducibility rather than cost. `release-plz.yml`
+is left on Ubicloud because its jobs are gated to the `nearai` repository owner
+and never execute here.

Triage: [type:docstyle]

📝 Committable suggestion

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

Suggested change
Windows jobs use `windows-latest` and `release.yml` pins `ubuntu-22.04`, both
for reproducibility rather than cost. `release-plz.yml` is left on Ubicloud
because its jobs are gated to the `nearai` repository owner and never execute
here.
Windows jobs use `windows-latest`. `release.yml` pins `ubuntu-22.04` for every
job except `build-local-artifacts`, which selects its runner from the release
matrix. Both choices serve reproducibility rather than cost. `release-plz.yml`
is left on Ubicloud because its jobs are gated to the `nearai` repository owner
and never execute here.
🤖 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 `@docs/developers-guide.md` around lines 95 - 98, Update the runner-policy
explanation in the developer guide to remove the incorrect claim that
release.yml pins ubuntu-22.04, and accurately describe build-local-artifacts as
using its matrix-selected runner. Clarify the relevant job scope, distinguishing
release.yml’s audit behavior from test.yml’s separate audit job that remains on
Ubicloud.

Comment on lines +18 to +36
///
/// Returns an error when detection did not fire, leaving the calling test to
/// decide how that failure surfaces.
fn assert_auth_detected(
detected: Option<(String, String)>,
expected_name: &str,
expected_instructions_fragment: &str,
) {
) -> anyhow::Result<()> {
assert!(detected.is_some(), "expected auth detection to fire");
let (name, instructions) =
detected.expect("expected auth detection to fire and return (name, instructions)");
detected.context("expected auth detection to fire and return (name, instructions)")?;
assert_eq!(name, expected_name);
assert!(
instructions.contains(expected_instructions_fragment),
"instructions did not contain {:?}: {:?}",
expected_instructions_fragment,
instructions,
);
Ok(())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Return precondition failures through the helper result.

Remove the assertion that precedes error conversion in each helper. Each assertion panics before the later Context or map_err call can return the documented error.

  • src/agent/dispatcher/tests/auth.rs#L18-L36: remove the detected.is_some() assertion and let detected.context(...)? return the missing-detection error.
  • src/agent/dispatcher/tests/loop_guard.rs#L191-L215: remove the timeout assertion and let result.map_err(...)? return the timeout error.
📍 Affects 2 files
  • src/agent/dispatcher/tests/auth.rs#L18-L36 (this comment)
  • src/agent/dispatcher/tests/loop_guard.rs#L191-L215
🤖 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 `@src/agent/dispatcher/tests/auth.rs` around lines 18 - 36, Remove the
detected.is_some() assertion in assert_auth_detected and let
detected.context(...)? return the documented missing-detection error. Also
update the timeout helper in src/agent/dispatcher/tests/loop_guard.rs at lines
191-215 by removing its timeout assertion and allowing result.map_err(...)? to
return the timeout error; both sites require direct changes.

Comment on lines +54 to +55
)
.expect("expected auth detection to fire");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Propagate helper errors from all affected agent tests.

Replace the newly added .expect() calls with ?. Return anyhow::Result<()> from the affected test or scenario functions.

  • src/agent/dispatcher/tests/auth.rs#L54-L55: propagate assert_auth_detected.
  • src/agent/dispatcher/tests/image_sentinel.rs#L245-L247: propagate run_image_generate_and_count_statuses.
  • src/agent/dispatcher/tests/loop_guard.rs#L243-L244: propagate assert_agentic_loop_text_response.
  • src/agent/dispatcher/tests/skill_bundle_context_bdd.rs#L222-L223: propagate assert_rendered_snapshot; apply the same change at Lines 233-234.
  • src/agent/dispatcher/tests/skills.rs#L92-L93: propagate skill-construction failures; apply the same change to the other changed .expect() sites in this file.

As per path instructions, src/agent/**/*.rs requires: “Never call .unwrap() or .expect(); use ? with proper error mapping.”

📍 Affects 5 files
  • src/agent/dispatcher/tests/auth.rs#L54-L55 (this comment)
  • src/agent/dispatcher/tests/image_sentinel.rs#L245-L247
  • src/agent/dispatcher/tests/loop_guard.rs#L243-L244
  • src/agent/dispatcher/tests/skill_bundle_context_bdd.rs#L222-L223
  • src/agent/dispatcher/tests/skills.rs#L92-L93
🤖 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 `@src/agent/dispatcher/tests/auth.rs` around lines 54 - 55, Replace the
affected helper-call .expect() sites with ? and update each enclosing test or
scenario function to return anyhow::Result<()>, preserving normal success
behavior. Apply this in src/agent/dispatcher/tests/auth.rs lines 54-55 for
assert_auth_detected; src/agent/dispatcher/tests/image_sentinel.rs lines 245-247
for run_image_generate_and_count_statuses;
src/agent/dispatcher/tests/loop_guard.rs lines 243-244 for
assert_agentic_loop_text_response; both assert_rendered_snapshot sites in
src/agent/dispatcher/tests/skill_bundle_context_bdd.rs lines 222-223 and
233-234; and every changed skill-construction .expect() site in
src/agent/dispatcher/tests/skills.rs, using proper error propagation throughout
src/agent/**/*.rs.

Source: Path instructions

Comment on lines +155 to +158
deps.tools.register_builtin_tools()?;
for tool in tools {
let _ = deps.tools.register(tool).await;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Propagate custom-tool registration failures.

Replace the discarded result on Line 157 with ?. A rejected custom tool leaves the helper with a different tool configuration, so a pipeline test can exercise the wrong setup and still pass.

Proposed fix
 for tool in tools {
-    let _ = deps.tools.register(tool).await;
+    deps.tools.register(tool).await?;
 }

As per coding guidelines, shared fixtures must propagate errors with Result and ?.

📝 Committable suggestion

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

Suggested change
deps.tools.register_builtin_tools()?;
for tool in tools {
let _ = deps.tools.register(tool).await;
}
deps.tools.register_builtin_tools()?;
for tool in tools {
deps.tools.register(tool).await?;
}
🤖 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 `@src/agent/dispatcher/tests/pipeline/support.rs` around lines 155 - 158,
Update the custom-tool registration loop in the pipeline test support helper to
propagate failures from the awaited deps.tools.register(tool) call with ?, while
keeping builtin registration and successful registrations unchanged.

Source: Coding guidelines

Comment on lines +35 to 49
/// Spawns an ephemeral Axum server that records posted job events.
///
/// Binding is fallible, so the base URL is returned as a `Result`. The spawned
/// task cannot propagate with `?`, so it yields the serve outcome through its
/// join handle instead of unwrapping.
async fn spawn_event_server(
state: Arc<EventState>,
) -> Result<(String, tokio::task::JoinHandle<()>)> {
) -> Result<(String, tokio::task::JoinHandle<std::io::Result<()>>)> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let app = Router::new()
.route(EVENT_ROUTE, post(event_handler))
.with_state(state);
let handle = tokio::spawn(async move {
axum::serve(listener, app)
.await
.expect("event test server should run");
});
let handle = tokio::spawn(async move { axum::serve(listener, app).await });
Ok((format!("http://{addr}"), handle))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Propagate the server task result before cleanup.

Replace task abortion and the ignored join result with graceful shutdown. Await the
join handle and propagate both the Tokio join error and the axum::serve error.
The current cleanup discards the std::io::Result<()>, so an event-server
failure still becomes a timeout or an unrelated test failure.

As per coding guidelines, shared test support must propagate errors with
Result and ?.

🤖 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 `@src/worker/container/tests/shutdown.rs` around lines 35 - 49, The shutdown
cleanup around spawn_event_server must gracefully await the server task instead
of aborting it and ignoring its result. Await the tokio::task::JoinHandle with
?, then apply ? again to propagate the axum::serve std::io::Result error; update
the enclosing test/helper flow as needed to return Result and preserve existing
cleanup behavior.

Source: Coding guidelines

Comment on lines +180 to +189
def _workflow_files() -> list[str]:
"""Return every workflow file name, sorted."""
return sorted(path.name for path in WORKFLOW_DIR.glob("*.yml"))


def test_workflow_inventory_matches_the_recorded_policy() -> None:
"""Every workflow file is accounted for in the runner policy."""
assert _workflow_files() == sorted(RUNNER_POLICY), (
"a workflow was added or removed without updating RUNNER_POLICY"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include .yaml workflows in the inventory glob.

_workflow_files globs *.yml only. GitHub Actions also loads .github/workflows/*.yaml. A job added in a .yaml file escapes RUNNER_POLICY entirely, and test_workflow_inventory_matches_the_recorded_policy still passes. That is exactly the silent drift this suite exists to catch.

🛡️ Proposed fix to cover both extensions
 def _workflow_files() -> list[str]:
     """Return every workflow file name, sorted."""
-    return sorted(path.name for path in WORKFLOW_DIR.glob("*.yml"))
+    return sorted(
+        path.name
+        for path in WORKFLOW_DIR.iterdir()
+        if path.suffix in {".yml", ".yaml"}
+    )

Note that _load already takes a bare file name, so no other call site changes.

📝 Committable suggestion

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

Suggested change
def _workflow_files() -> list[str]:
"""Return every workflow file name, sorted."""
return sorted(path.name for path in WORKFLOW_DIR.glob("*.yml"))
def test_workflow_inventory_matches_the_recorded_policy() -> None:
"""Every workflow file is accounted for in the runner policy."""
assert _workflow_files() == sorted(RUNNER_POLICY), (
"a workflow was added or removed without updating RUNNER_POLICY"
)
def _workflow_files() -> list[str]:
"""Return every workflow file name, sorted."""
return sorted(
path.name
for path in WORKFLOW_DIR.iterdir()
if path.suffix in {".yml", ".yaml"}
)
def test_workflow_inventory_matches_the_recorded_policy() -> None:
"""Every workflow file is accounted for in the runner policy."""
assert _workflow_files() == sorted(RUNNER_POLICY), (
"a workflow was added or removed without updating RUNNER_POLICY"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/workflow_contracts/runner_policy_test.py` around lines 180 - 189,
Update _workflow_files to include workflow filenames ending in both .yml and
.yaml, while preserving the sorted inventory returned to
test_workflow_inventory_matches_the_recorded_policy. Leave _load and other call
sites unchanged.

Comment on lines +320 to +321
prompt = action.get("with", {}).get("prompt")
assert isinstance(prompt, str), "the review action must supply a prompt"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the typecheck target and any Python type-checker configuration.
rg -n -A5 '^typecheck:' Makefile
fd -H -t f 'pyproject.toml|mypy.ini|setup.cfg|pyrightconfig.json' --exec rg -n -C4 'mypy|pyright|ty\b|include|exclude|files' {}

Repository: leynos/axinite

Length of output: 507


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- target file ---'
sed -n '270,335p' tests/workflow_contracts/runner_policy_test.py

printf '%s\n' '--- related definitions and uses ---'
rg -n -C4 'def _job|_job\(|action\.get\("with"|runner_policy' tests/workflow_contracts

printf '%s\n' '--- Python type-checker configuration and test tooling ---'
fd -H -t f 'pyproject.toml|mypy.ini|setup.cfg|tox.ini|noxfile.py|pyrightconfig.json|Makefile|justfile' \
  --exec sh -c 'echo "--- $1"; rg -n -C4 "mypy|pyright|basedpyright|ty\\b|typecheck|pytest|tests/" "$1"' sh {}

Repository: leynos/axinite

Length of output: 21780


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import ast

path = Path("tests/workflow_contracts/runner_policy_test.py")
tree = ast.parse(path.read_text())

for node in ast.walk(tree):
    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == "_job":
        print(f"_job: lines {node.lineno}-{node.end_lineno}")
        print(ast.unparse(node))
PY

Repository: leynos/axinite

Length of output: 380


🏁 Script executed (no clone):

python3 - <<'PY'
# Model the relevant YAML-shaped values without importing or running repository code.
def evaluate(action):
    try:
        prompt = action.get("with", {}).get("prompt")
        return ("value", prompt)
    except Exception as exc:
        return (type(exc).__name__, str(exc))

cases = [
    {"name": "missing with", "value": {}},
    {"name": "empty with", "value": {"with": None}},
    {"name": "mapping with prompt", "value": {"with": {"prompt": "x"}}},
    {"name": "scalar with", "value": {"with": "x"}},
]
for case in cases:
    print(case["name"], evaluate(case["value"]))
PY

Length of output: 333


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- parser helpers and step typing ---'
sed -n '1,175p' tests/workflow_contracts/runner_policy_test.py

printf '%s\n' '--- Claude workflow action ---'
fd -H -t f 'claude-review.yml' --exec sh -c 'echo "--- $1"; cat -n "$1"' sh

printf '%s\n' '--- all relevant with blocks ---'
rg -n -C3 '^[[:space:]]+with:|claude-code-action|prompt:' .github tests/workflow_contracts

Repository: leynos/axinite

Length of output: 50370


🏁 Script executed (no clone):

python3 - <<'PY'
try:
    import yaml
except ImportError:
    print("PyYAML is unavailable")
else:
    samples = [
        ("missing with", "steps:\\n  - uses: example/action"),
        ("empty with", "steps:\\n  - uses: example/action\\n    with:"),
        ("empty mapping with", "steps:\\n  - uses: example/action\\n    with: {}"),
        ("prompt mapping", "steps:\\n  - uses: example/action\\n    with:\\n      prompt: hello"),
    ]
    for name, text in samples:
        value = yaml.safe_load(text)["steps"][0]
        with_value = value.get("with", {})
        print(name, repr(with_value), type(with_value).__name__)
PY

Length of output: 147


Guard the action inputs before reading prompt.

If with: is empty, PyYAML loads it as None. The chained .get then raises AttributeError instead of reporting a failed contract assertion. Assert that inputs is a mapping before reading prompt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/workflow_contracts/runner_policy_test.py` around lines 320 - 321,
Update the action-input handling around the prompt assertion to assign the
action’s “with” value, verify it is a mapping before accessing it, and only then
read its “prompt” field. Ensure an empty “with:” produces a failed contract
assertion rather than an AttributeError, while preserving the existing prompt
string assertion.

], "the regression check must stay a checkout plus a single script step"

checkout = _steps(job)[0]
assert str(checkout.get("uses", "")).startswith("actions/checkout@")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Add an assertion message.

This is the only bare assert in the file. The repository guidelines require a message on every assertion.

♻️ Proposed fix
-    assert str(checkout.get("uses", "")).startswith("actions/checkout@")
+    assert str(checkout.get("uses", "")).startswith("actions/checkout@"), (
+        "the regression check must check out the repository with actions/checkout"
+    )

As per coding guidelines: "Use assert …, "message" over bare asserts".

📝 Committable suggestion

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

Suggested change
assert str(checkout.get("uses", "")).startswith("actions/checkout@")
assert str(checkout.get("uses", "")).startswith("actions/checkout@"), (
"the regression check must check out the repository with actions/checkout"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/workflow_contracts/runner_policy_test.py` at line 385, Add a
descriptive assertion message to the `assert` validating `checkout.get("uses",
"")` in the workflow contract test, while preserving the existing
`startswith("actions/checkout@")` condition.

Source: Path instructions

Comment on lines +392 to +394
assert "git diff" in script and "git log" in script, (
"the check must remain a git-history inspection, not a build"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Split the compound assertion.

Ruff flags PT018. A single failure message cannot tell which of the two probes is missing. Split it so the report names the missing command.

♻️ Proposed fix
-    assert "git diff" in script and "git log" in script, (
-        "the check must remain a git-history inspection, not a build"
-    )
+    assert "git diff" in script, (
+        "the check must remain a git-history inspection, not a build"
+    )
+    assert "git log" in script, (
+        "the check must inspect commit messages, not a build"
+    )
📝 Committable suggestion

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

Suggested change
assert "git diff" in script and "git log" in script, (
"the check must remain a git-history inspection, not a build"
)
assert "git diff" in script, (
"the check must remain a git-history inspection, not a build"
)
assert "git log" in script, (
"the check must inspect commit messages, not a build"
)
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 392-394: Assertion should be broken down into multiple parts

(PT018)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/workflow_contracts/runner_policy_test.py` around lines 392 - 394, Split
the compound assertion in the workflow contract test into separate assertions
for “git diff” and “git log,” giving each assertion a failure message that
identifies its missing probe while preserving the existing git-history
validation.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: ci CI/CD workflows scope: dependencies Dependency updates scope: docs Documentation scope: llm LLM integration scope: orchestrator Container orchestrator scope: tool/builtin Built-in tools scope: worker Container worker size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants