Skip to content

feat(qualification): enforce OpenShell v0.0.101 receipts - #8635

Open
apurvvkumaria wants to merge 2 commits into
mainfrom
codex/8600-openshell-qualification-gates-v2
Open

feat(qualification): enforce OpenShell v0.0.101 receipts#8635
apurvvkumaria wants to merge 2 commits into
mainfrom
codex/8600-openshell-qualification-gates-v2

Conversation

@apurvvkumaria

@apurvvkumaria apurvvkumaria commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a base-trusted, versioned OpenShell v0.0.101 qualification contract and exact receipt authentication for pull requests, maintainer checks, and release retirement. The contract is intentionally bootstrap/draft and activates no compatibility claim until its inventories and external repository governance are configured and verified.

Related Issue

Closes #8600

Parent: #8590

Changes

  • Add the versioned bootstrap contract, strict lifecycle validation, source/test mappings, matrix and artifact validation, and exact candidate/base receipt schema.
  • Add a standalone pull_request_target consumer and base-trusted targeted producer that keep candidate content data-only and authenticate workflow, run, check suite, GitHub Actions App, job, attempt, and freshness metadata.
  • Require exact final receipt authentication and signed retirement evidence in the release-tag path while keeping ordinary maintainer gate collection advisory.
  • Add fail-closed negative coverage for missing, stale, mismatched, skipped, canceled, failed, malformed, linked, oversized, and unknown-schema evidence.
  • Update internal release and merge-gate guidance for the new openshell-qualification context.

The contract remains bootstrap / draft: artifacts and matrices are empty, mappings are pending, and requiredWorkflowGate is null. This PR does not activate organization governance, accept #8616 or #8617, dispatch trusted E2E, waive a receipt, assemble an integration candidate, merge a child, or create/move/publish a tag.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: This changes repository-internal qualification, maintainer, and release safeguards only. It does not change a public CLI command, runtime behavior, default, user configuration, policy schema, or supported integration; documenting v0.0.101 compatibility while the contract is draft would overstate support.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Independent acceptance-boundary and security/release reviews found no blocking or major finding. Live GitHub CI and repository review remain pending while this PR is draft.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: Exact-head review confirmed unchanged docs/ (4f3d426f148c53e9b31ea08017d697a848cf6ce6), fern/ (fb8d2c923f1ae90df0a2ff25648959924f196374), and user-guide routing (62fa453427f36117abfc19c19086238cc6437c18). Applicable internal maintainer guidance is updated in this change.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: Post-rebase focused Vitest run passed 10 files / 296 tests in 339.76 seconds. Repository checks, CLI typecheck, source-shape, test-size, test-title, project-membership, Biome, ShellCheck, shfmt, builds, sourcemaps, and exact mock/live parity also passed.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: Local broad-shard execution was not a reliable platform receipt; the exact failed corrective receipt is below. Fresh GitHub CI is authoritative for all eight shards, and this PR remains draft pending that result.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Frozen local identity

  • Candidate: 6d30e49481d3e7c502f231be9b5cde49f4978e50
  • Local base: 36db1f4acaae5e26a835582fc2a544c9f6be1014
  • A later unrelated main commit (fix(ci): exclude draft PRs from conflict fixer #8631) changes only conflict-fixer files. The pair above remains frozen; GitHub merge-ref CI against current main is authoritative.

Local broad-shard limitation

Four-way and two-way macOS attempts produced unrelated resource timeouts and host-platform differences. One final corrective shard was run sequentially in a non-root Linux bind-mounted environment, then local retries stopped without changing tests, timeouts, lockfiles, or security assertions.

  • Image: node@sha256:c601a46abb4d2ab80a9dc3da208d50d1122642d53f17a101926ace71e5a9bf1c (linux/arm64)
  • Runtime: Node v22.23.1, npm 10.9.8, Python 3.11.2, UID 501
  • SHA pair: candidate 6d30e49481d3e7c502f231be9b5cde49f4978e50, base 36db1f4acaae5e26a835582fc2a544c9f6be1014
  • Exit status: 1
  • Report: /Users/akumaria/.codex/visualizations/2026/08/08/019fdfcf-d1ed-7b32-b4ee-64c1225a0eca/nemoclaw-8600-linux-source/.vitest-reports/blob-1-8.json (36,018,331 bytes; SHA-256 ca21e5bb45ba6f9973639c65c6a96d612cc2297c09cc671094689e569d23416e)
  • Classification: the cached base lacks jq and Python yaml, and host-bind ownership semantics caused unrelated fixture permission failures. This is not a successful receipt.

Exact shard command:

npx vitest run --project cli --project integration --project e2e-support \
  --shard="1/8" \
  --reporter=github-actions \
  --reporter=blob \
  --outputFile.blob=".vitest-reports/blob-1-8.json" \
  --coverage \
  --coverage.reporter=json-summary \
  --coverage.reportsDirectory="coverage/cli/shard-1" \
  --coverage.include="bin/**/*.js" \
  --coverage.include="src/**/*.ts" \
  --coverage.exclude="test/**/*.js" \
  --coverage.exclude="test/**/*.ts"

Signed-off-by: Apurv Kumaria akumaria@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added OpenShell 0.0.101 qualification workflows for pull requests and releases.
    • Added 13 scenarios covering installation, upgrades, runtime behavior, credentials, recovery, and isolation.
    • Added validated qualification receipts for release readiness.
  • Improvements

    • OpenShell qualification is now a required merge check, including sensitive-change validation.
    • Release tagging verifies qualification evidence, signatures, repository state, ancestry, and publication.
    • Added fail-closed validation and recovery guidance for invalid, stale, or incomplete evidence.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds OpenShell 0.0.101 qualification contracts, receipt production and authentication, fail-closed PR and merge gates, and qualification-aware release-tag publication with signed metadata and atomic remote verification.

Changes

OpenShell qualification enforcement

Layer / File(s) Summary
Qualification contracts and lifecycle validation
scripts/checks/openshell-qualification-*.mts
Defines qualification schemas, matrices, bounded I/O, lifecycle transitions, receipt validation, GitHub evidence authentication, and retirement metadata.
Qualification workflows and manifest
.github/workflows/openshell-0.0.101-qualification.yaml, .github/workflows/openshell-0.0.101-pr-gate.yaml, ci/openshell-0.0.101-qualification-v1.json
Adds manual qualification execution, PR classification, receipt verification, authority checks, decision reporting, and 13 qualification test definitions.
Merge-gate integration
.agents/skills/nemoclaw-maintainer-day/..., .agents/skills/nemoclaw-maintainer-pr-comparator/...
Makes openshell-qualification required and validates its exact PR/base identity, GitHub Actions app, workflow, and gate evidence.
Release publication enforcement
.agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md, scripts/release-cut-tag.sh
Requires qualification receipts when applicable, validates frozen consumers and authority files, verifies signed GitHub tag objects, publishes refs atomically, and records retirement evidence.
Validation coverage
test/openshell-qualification-*.test.ts, test/release-qualification-gate.test.ts, test/skills/*
Adds contract, workflow, gate, receipt, authority, race, publication, and fail-closed test coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • NVIDIA/NemoClaw#8590 — The PR implements the qualification contracts, workflows, gates, receipts, and release enforcement specified by the parent issue.

Possibly related PRs

  • NVIDIA/NemoClaw#8485 — Both modify the maintainer release-tag skill and release flow.
  • NVIDIA/NemoClaw#8583 — The qualification manifest defines the Docker, Podman, lifecycle, runtime identity, upgrade, and isolation scenarios covered by this PR.
  • NVIDIA/NemoClaw#8587 — Both extend merge-gate evidence authentication and related fixtures.

Suggested labels: area: security, v0.0.106

Suggested reviewers: cv, jyaunches

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: enforcing OpenShell v0.0.101 qualification receipts.
Linked Issues check ✅ Passed The changes implement versioned receipts, exact SHA binding, fail-closed gates, producer and consumer validation, and extensive invalid-evidence tests for issue #8600.
Out of Scope Changes check ✅ Passed The changes remain focused on qualification contracts, gates, receipt authentication, release retirement evidence, and related tests and guidance.
✨ 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 codex/8600-openshell-qualification-gates-v2

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

@github-code-quality

github-code-quality Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit ae5f1bd in the codex/8600-openshell... branch remains at 96%, unchanged from commit 290eb94 in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit ae5f1bd in the codex/8600-openshell... branch remains at 81%, unchanged from commit 290eb94 in the main branch.

Show a code coverage summary of the most impacted files.
File main 290eb94 codex/8600-openshell... ae5f1bd +/-
src/lib/domain/...ckup-failure.ts 100% 78% -22%
src/lib/policy/...ateway-state.ts 73% 56% -17%
src/lib/messagi...onfig-parser.ts 100% 93% -7%
src/lib/trace.ts 94% 90% -4%
src/lib/credentials/store.ts 56% 55% -1%
src/lib/policy/index.ts 62% 63% +1%
src/lib/state/sandbox.ts 86% 89% +3%
src/lib/policy/...scope-render.ts 94% 97% +3%
src/lib/state/tar-listing.ts 83% 100% +17%
src/lib/core/pr...mpt-activity.ts 67% 92% +25%

Updated August 09, 2026 01:25 UTC


import path from "node:path";

const SAFE_PATH_PATTERN = /^[^\u0000-\u001f\u007f\\\\]{1,4096}$/u;
Comment on lines +8 to +23
import {
fail,
isRecord,
MAX_JSON_DEPTH,
MAX_JSON_ITEMS,
QUALIFICATION_CONTRACT_PATH,
QUALIFICATION_MAX_ARTIFACT_BYTES,
QUALIFICATION_MAX_JSON_BYTES,
QUALIFICATION_RECEIPT_FILE,
type QualificationContract,
type QualificationReceipt,
type QualificationReceiptExpectation,
SAFE_TEXT_PATTERN,
validateQualificationContract,
validateQualificationReceipt,
} from "./openshell-qualification-core.mts";
Comment on lines +5 to +15
import {
expandQualificationMatrix,
QUALIFICATION_MAX_AGGREGATE_CELL_BYTES,
QUALIFICATION_MAX_AGGREGATE_MATRIX_CELLS,
type QualificationCellResult,
qualificationCellInventoryFootprint,
requireCompleteQualificationMatrix,
validateQualificationApprovedExceptions,
validateQualificationCellResults,
validateQualificationMatrix,
} from "./openshell-qualification-matrix.mts";
Comment on lines +16 to +66
import {
type ActiveQualificationTestDescriptor,
type CreateQualificationReceiptInput,
type ProduceQualificationReceiptInput,
QUALIFICATION_CONTRACT_PATH,
QUALIFICATION_CONTRACT_SCHEMA_VERSION,
QUALIFICATION_MAX_ARTIFACT_BYTES,
QUALIFICATION_MAX_JSON_BYTES,
QUALIFICATION_NEMOCLAW_REPOSITORY_BASELINE_SHA,
QUALIFICATION_NEMOCLAW_USER_BASELINE_COMMIT_SHA,
QUALIFICATION_NEMOCLAW_USER_BASELINE_TAG,
QUALIFICATION_NEMOCLAW_USER_BASELINE_TAG_OBJECT_SHA,
QUALIFICATION_PUBLIC_USER_BASELINE_COMMIT_SHA,
QUALIFICATION_PUBLIC_USER_BASELINE_TAG,
QUALIFICATION_PUBLIC_USER_BASELINE_VERSION,
QUALIFICATION_RECEIPT_FILE,
QUALIFICATION_REPOSITORY_BASELINE_COMMIT_SHA,
QUALIFICATION_REPOSITORY_BASELINE_TAG,
QUALIFICATION_REPOSITORY_BASELINE_VERSION,
QUALIFICATION_REQUIRED_WORKFLOW_PATH,
QUALIFICATION_REQUIRED_WORKFLOW_REF,
QUALIFICATION_REQUIRED_WORKFLOW_REPOSITORY_ID,
QUALIFICATION_RETIREMENT_TAG_MESSAGE_PREFIX,
QUALIFICATION_SCOPE,
QUALIFICATION_SOURCE_RECEIPT_FILE,
QUALIFICATION_TARGET_COMMIT_SHA,
QUALIFICATION_TARGET_TAG,
QUALIFICATION_TARGET_VERSION,
type QualificationArtifactProvenance,
type QualificationArtifactReader,
type QualificationContract,
type QualificationExecutionContext,
type QualificationGitHubReader,
type QualificationIdentity,
type QualificationInventoryState,
type QualificationLifecycle,
type QualificationPhase,
type QualificationPhaseMapping,
type QualificationReceipt,
type QualificationReceiptExpectation,
type QualificationReceiptJob,
type QualificationReceiptRun,
type QualificationReceiptTest,
type QualificationRequiredWorkflowGate,
type QualificationResult,
type QualificationRetirementEvidence,
type QualificationRetirementTagMetadata,
type QualificationSource,
type QualificationSourceEvent,
type QualificationTestDescriptor,
} from "./openshell-qualification-schema.mts";
@apurvvkumaria
apurvvkumaria marked this pull request as ready for review August 8, 2026 23:06
fail("qualification artifact must be a bounded regular non-link file");
}
const receipt = readQualificationReceiptArchive(
fs.readFileSync(archivePath),
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > 1024 * 1024) {
fail("OpenShell version blueprint is not a bounded regular file");
}
const source = fs.readFileSync(blueprintPath, "utf8");
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > MAX_FILE_BYTES) {
fail(`${relativePath} must be a bounded regular file`);
}
return fs.readFileSync(absolutePath, "utf8");
fail("producer workflow must be a bounded regular file");
}
try {
return YAML.parse(fs.readFileSync(workflowPath, "utf8")) as unknown;

import path from "node:path";

const SAFE_PATH_PATTERN = /^[^\u0000-\u001f\u007f\\\\]{1,4096}$/u;
Comment on lines +5 to +15
import {
expandQualificationMatrix,
QUALIFICATION_MAX_AGGREGATE_CELL_BYTES,
QUALIFICATION_MAX_AGGREGATE_MATRIX_CELLS,
type QualificationCellResult,
qualificationCellInventoryFootprint,
requireCompleteQualificationMatrix,
validateQualificationApprovedExceptions,
validateQualificationCellResults,
validateQualificationMatrix,
} from "./openshell-qualification-matrix.mts";
Comment on lines +16 to +66
import {
type ActiveQualificationTestDescriptor,
type CreateQualificationReceiptInput,
type ProduceQualificationReceiptInput,
QUALIFICATION_CONTRACT_PATH,
QUALIFICATION_CONTRACT_SCHEMA_VERSION,
QUALIFICATION_MAX_ARTIFACT_BYTES,
QUALIFICATION_MAX_JSON_BYTES,
QUALIFICATION_NEMOCLAW_REPOSITORY_BASELINE_SHA,
QUALIFICATION_NEMOCLAW_USER_BASELINE_COMMIT_SHA,
QUALIFICATION_NEMOCLAW_USER_BASELINE_TAG,
QUALIFICATION_NEMOCLAW_USER_BASELINE_TAG_OBJECT_SHA,
QUALIFICATION_PUBLIC_USER_BASELINE_COMMIT_SHA,
QUALIFICATION_PUBLIC_USER_BASELINE_TAG,
QUALIFICATION_PUBLIC_USER_BASELINE_VERSION,
QUALIFICATION_RECEIPT_FILE,
QUALIFICATION_REPOSITORY_BASELINE_COMMIT_SHA,
QUALIFICATION_REPOSITORY_BASELINE_TAG,
QUALIFICATION_REPOSITORY_BASELINE_VERSION,
QUALIFICATION_REQUIRED_WORKFLOW_PATH,
QUALIFICATION_REQUIRED_WORKFLOW_REF,
QUALIFICATION_REQUIRED_WORKFLOW_REPOSITORY_ID,
QUALIFICATION_RETIREMENT_TAG_MESSAGE_PREFIX,
QUALIFICATION_SCOPE,
QUALIFICATION_SOURCE_RECEIPT_FILE,
QUALIFICATION_TARGET_COMMIT_SHA,
QUALIFICATION_TARGET_TAG,
QUALIFICATION_TARGET_VERSION,
type QualificationArtifactProvenance,
type QualificationArtifactReader,
type QualificationContract,
type QualificationExecutionContext,
type QualificationGitHubReader,
type QualificationIdentity,
type QualificationInventoryState,
type QualificationLifecycle,
type QualificationPhase,
type QualificationPhaseMapping,
type QualificationReceipt,
type QualificationReceiptExpectation,
type QualificationReceiptJob,
type QualificationReceiptRun,
type QualificationReceiptTest,
type QualificationRequiredWorkflowGate,
type QualificationResult,
type QualificationRetirementEvidence,
type QualificationRetirementTagMetadata,
type QualificationSource,
type QualificationSourceEvent,
type QualificationTestDescriptor,
} from "./openshell-qualification-schema.mts";
Comment on lines +8 to +23
import {
fail,
isRecord,
MAX_JSON_DEPTH,
MAX_JSON_ITEMS,
QUALIFICATION_CONTRACT_PATH,
QUALIFICATION_MAX_ARTIFACT_BYTES,
QUALIFICATION_MAX_JSON_BYTES,
QUALIFICATION_RECEIPT_FILE,
type QualificationContract,
type QualificationReceipt,
type QualificationReceiptExpectation,
SAFE_TEXT_PATTERN,
validateQualificationContract,
validateQualificationReceipt,
} from "./openshell-qualification-core.mts";
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Blocking findings reported

Advisor assessment: Blockers require maintainer review
Next action: Review the blockers below.
Findings: 1 blocker · 1 warning · 0 suggestions
Status: Same-session synthesis validation failed; the advisor result is incomplete.

Model lanes

  • GPT-5.6 Terra (primary): Completed · low confidence · 1 blocker · 1 warning · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Failed after a partial review · low confidence · 0 blockers · 0 warnings · 2 suggestions

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

4 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • established — qualification contract at .agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md:43: Continue to use `qualification contract` with the OpenShell modifier where the contract family is not otherwise clear.
  • established — qualification receipt at .github/workflows/openshell-0.0.101-pr-gate.yaml:82: Continue to use `qualification receipt` for the validated evidence artifact.
  • justified — base-trusted at .github/workflows/openshell-0.0.101-pr-gate.yaml:42: Continue to use `base-trusted` when the base-SHA trust boundary distinguishes verifier code from candidate content.
  • define — frozen authority at .agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md:44: Define `frozen authority` at its first explanatory use as the complete first-parent-parity authority inventory.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: managed-image-multiarch-startup

1 optional E2E recommendation
  • managed-image-protected-runtime

Blockers

PRA-1 Blocker — Allow the initial qualification contract transition

  • Location: scripts/checks/verify-openshell-qualification-pr-gate.mts:173
  • Category: architecture
  • Problem: The PR gate rejects a candidate qualification contract whenever the base revision has no contract. This PR adds the contract and marks its files qualification-sensitive, so the required workflow invokes verification and fails before the initial contract can merge.
  • Impact: The required `openshell-qualification` context cannot pass for the PR that introduces this system, preventing the initial contract from entering the repository.
  • Fix: Add a narrow bootstrap path for the absent-base to initial-contract transition. Authenticate the base-owned gate boundary and require an exact, reviewed initial contract shape without treating the candidate contract as already trusted.
  • Verification: Inspect the PR-gate workflow path for this PR and trace `classify` through `verify`; confirm that an absent base contract with this initial candidate contract reaches a successful required-context decision only through the new bootstrap path.
  • Test coverage: Add a PR-gate test for an absent base contract and this initial candidate contract. Assert that the path succeeds only when the initial contract and trusted workflow boundary match the approved bootstrap conditions.
  • Evidence: scripts/checks/verify-openshell-qualification-pr-gate.mts: planQualificationGate rejects `!base && candidate`. scripts/checks/openshell-qualification-paths.mts classifies the contract and PR-gate workflow as qualification-sensitive. ci/openshell-0.0.101-qualification-v1.json is added by this PR, while the base revision has no contract.
1 warning · 0 suggestions

Warnings

Warnings do not block.

PRA-2 Warning — Add managed-image qualification evidence before enforcing the contract

  • Location: ci/openshell-0.0.101-qualification-v1.json:8
  • Category: security
  • Problem: The new qualification contract remains in the draft bootstrap state with an empty artifact inventory and pending source mappings. It cannot authenticate managed-image startup evidence for either supported architecture.
  • Impact: Until the contract inventory and mappings are activated with authenticated source evidence, the new qualification system cannot detect regressions in protected managed-image startup and registry cleanup.
  • Recommendation: Before treating this contract as an enforcement control, add the frozen artifact inventory and active source mappings that authenticate the required multi-architecture startup and cleanup evidence.
  • Verification: Inspect the contract after activation and confirm that every required managed-image test has active source mappings and matrix cells for amd64 and arm64 direct startup, digest identity, and isolated-registry cleanup.
  • Test coverage: Add a contract and receipt test that rejects a final qualification receipt when an amd64 or arm64 agent startup cell, digest binding, or isolated-registry cleanup cell is missing or unsuccessful.
  • Evidence: ci/openshell-0.0.101-qualification-v1.json:8 sets `artifacts` to `[]`. ci/openshell-0.0.101-qualification-v1.json:10 sets `lifecycle` to `bootstrap`; the test mappings are pending. The deterministic risk plan requires `managed-image-multiarch-startup` for these invariants.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

@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

🧹 Nitpick comments (28)
scripts/checks/openshell-qualification-contract.mts (3)

561-573: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the schema constant instead of the literal 1.

Line 567 hardcodes schemaVersion: 1. QUALIFICATION_CONTRACT_SCHEMA_VERSION holds the same value and is already the canonical source. A future bump of the constant leaves this literal behind.

🤖 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 `@scripts/checks/openshell-qualification-contract.mts` around lines 561 - 573,
Replace the hardcoded schemaVersion value in the returned qualification contract
with the existing QUALIFICATION_CONTRACT_SCHEMA_VERSION constant, preserving the
rest of the contract fields unchanged.

503-508: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the archive read bounds with the rest of the cohort.

This call passes maxBytes: QUALIFICATION_MAX_ARTIFACT_BYTES and omits maxEntries. parseQualificationReceiptArchive in scripts/checks/openshell-qualification-io.mts Lines 253-256 uses maxBytes: QUALIFICATION_MAX_JSON_BYTES and maxEntries: 1 for the same entry in the same archive. Line 517 then re-reads the archive with the stricter bounds, so the looser read does not weaken the result today. Use the same bounds in both places so the two reads cannot diverge.

🤖 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 `@scripts/checks/openshell-qualification-contract.mts` around lines 503 - 508,
Update the readValidatedArtifactZipEntry call used for receiptSource to match
parseQualificationReceiptArchive: use QUALIFICATION_MAX_JSON_BYTES for maxBytes
and set maxEntries to 1. Keep the existing digest validation and failure
behavior unchanged.

632-654: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

validate-live accepts a selector context but never requires --pr-number.

common omits --pr-number. For a non-release execution context, expectationFromCli then sets prNumber to Number(undefined), which is NaN. The command still fails, because authenticateFinalQualificationReceipt rejects a non-release receipt, but the reported error describes a mismatched receipt rather than a missing argument.

The validate, validate-archive, create, and produce commands all append --pr-number for a non-release context. Apply the same rule here, or restrict validate-live to the release context 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 `@scripts/checks/openshell-qualification-contract.mts` around lines 632 - 654,
Update the validate-live branch to require --pr-number for non-release contexts,
matching the selector validation used by validate, validate-archive, create, and
produce. Extend the common required CLI values or otherwise restrict this
command to release context, ensuring expectationFromCli never receives an
undefined pull-request number.
scripts/checks/openshell-qualification-core.mts (4)

1264-1280: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Hoist knownResults to module scope.

validateResult rebuilds the Set on every call. The function runs once per job, per run, and per receipt test. Move the Set to a module-level constant.

🤖 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 `@scripts/checks/openshell-qualification-core.mts` around lines 1264 - 1280,
Move the knownResults Set from inside validateResult to a module-level constant,
then have validateResult reuse that constant for validation while preserving the
existing result values and failure behavior.

726-733: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The approval repository is hardcoded.

validateTestDescriptor passes the literal "NVIDIA/NemoClaw" while the caller has already validated value.repository at Line 757. The pin looks deliberate, because QUALIFICATION_SCOPE is NVIDIA/NemoClaw#8590. The literal still duplicates that canonical value and can drift from it.

Derive the repository from QUALIFICATION_SCOPE, or add a short comment that states why the approval host repository is fixed and independent of contract.repository.

🤖 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 `@scripts/checks/openshell-qualification-core.mts` around lines 726 - 733,
Update validateTestDescriptor to derive the approval repository from the
canonical QUALIFICATION_SCOPE value instead of hardcoding "NVIDIA/NemoClaw";
preserve the existing validated value passed to
validateQualificationApprovedExceptions and avoid duplicating the repository
literal.

890-1105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

validateQualificationLifecycleTransition is 216 lines with four nested branch levels.

The per-test mapping loop on Lines 984-1076 encodes five distinct transition rules in one body. Extract one helper per transition, for example assertBootstrapStaging, assertSelectorPromotion, and assertFinalPromotion. Keep the dispatch in this function.

As per coding guidelines: "Keep function complexity low and prefix intentionally unused variables with _."

🤖 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 `@scripts/checks/openshell-qualification-core.mts` around lines 890 - 1105,
Reduce the complexity of validateQualificationLifecycleTransition by extracting
the per-test mapping rules into focused helpers, including bootstrap staging,
selector promotion, final promotion, and the unchanged-mapping validation path.
Keep transition dispatch in validateQualificationLifecycleTransition, preserve
all existing validation behavior and error messages, and prefix intentionally
unused destructured variables with an underscore.

Source: Coding guidelines


750-888: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

validateQualificationContract mixes many independent rule families.

The function spans 139 lines. It validates the schema, the lifecycle, the artifacts, the frozen inventory, the matrix identities, the aggregate budget, and the staged mappings. The frozen branch on Lines 802-871 alone contains four nested loops.

Extract the frozen-inventory branch into a named helper, for example requireFrozenInventory(tests, artifacts, repository). The behavior does not change, and each rule family becomes separately testable.

As per coding guidelines: "Keep function complexity low and prefix intentionally unused variables with _."

🤖 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 `@scripts/checks/openshell-qualification-core.mts` around lines 750 - 888,
Extract the entire inventoryState === "frozen" validation branch from
validateQualificationContract into a named requireFrozenInventory(tests,
artifacts, repository) helper, preserving all matrix, runtime identity,
artifact, aggregate-budget, and staged-mapping checks unchanged. Replace the
inline branch with a helper call and prefix any intentionally unused parameters
or locals with _.

Source: Coding guidelines

scripts/checks/openshell-qualification-io.mts (1)

195-213: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Split the relative path once.

The loop calls relativePath.split("/") on Lines 195, 204, and 208. Hoist the result into a local array. The behavior does not change, and the boundary checks become easier to read.

♻️ Proposed refactor
-  for (const [index, part] of relativePath.split("/").entries()) {
+  const parts = relativePath.split("/");
+  for (const [index, part] of parts.entries()) {
     cursor = path.join(cursor, part);
@@
-    if (index < relativePath.split("/").length - 1 && !stats.isDirectory()) {
+    if (index < parts.length - 1 && !stats.isDirectory()) {
       fail("qualification contract path has an invalid parent");
     }
     if (
-      index === relativePath.split("/").length - 1 &&
+      index === parts.length - 1 &&
🤖 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 `@scripts/checks/openshell-qualification-io.mts` around lines 195 - 213, Split
relativePath once into a local parts array before the loop, then iterate over
that array and use its length for both parent and final-component boundary
checks. Update the loop around cursor and stats without changing validation
behavior.
scripts/checks/openshell-qualification-matrix.mts (1)

378-419: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The identity comparison depends on property insertion order.

JSON.stringify(identity) compares against a literal that repeats the key order of validateCellIdentity. A future reorder of either site breaks the comparison silently. Compare the fields directly instead.

🤖 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 `@scripts/checks/openshell-qualification-matrix.mts` around lines 378 - 419,
Update validateCellException to replace the JSON.stringify(identity) comparison
with direct comparisons of each identity field against the corresponding
expected value. Preserve the existing approvalUrl, approvedBy, and reason checks
and continue failing with the same message when any field differs.
.github/workflows/openshell-0.0.101-qualification.yaml (1)

83-87: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add request timeouts to the GitHub API calls.

The four curl invocations set --fail --silent --show-error --proto '=https' but no time bound. If the API stalls, the step consumes the full timeout-minutes: 10 budget and reports a generic job timeout instead of a clear API failure. Add --max-time and a bounded retry.

⏱️ Proposed change for one call site; apply the same flags to all four
-            permission_json="$(curl --fail --silent --show-error --proto '=https' \
+            permission_json="$(curl --fail --silent --show-error --proto '=https' \
+              --max-time 30 --retry 3 --retry-connrefused \
               --header "Authorization: Bearer ${GITHUB_TOKEN}" \

Also applies to: 115-119, 152-156, 161-165

🤖 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 @.github/workflows/openshell-0.0.101-qualification.yaml around lines 83 - 87,
Update all four GitHub API curl invocations in the workflow, including the calls
near permission_json and the other collaborator/API lookups, to add a bounded
--max-time and retry configuration. Apply identical timeout and retry flags to
each request while preserving the existing failure, silence, error-reporting,
and HTTPS protocol options.
ci/openshell-0.0.101-qualification-v1.json (1)

76-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider an explicit upgrade case for the 0.0.85 lanes.

The 0.0.99 upgrade tests declare upgrade-from-00099 in requiredCases. The 0.0.85 upgrade tests declare only direct-public-v85. The identifiers are contract-owned strings, so both work. A matching upgrade-from-00085 case would keep the required-case vocabulary symmetric and self-describing.

Also applies to: 178-203

🤖 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 `@ci/openshell-0.0.101-qualification-v1.json` around lines 76 - 100, Add the
contract-owned "upgrade-from-00085" identifier to the requiredCases arrays for
both 0.0.85 upgrade test entries, preserving the existing direct-public-v85 case
and matching the vocabulary used by the 0.0.99 upgrade tests.
test/skills/check-gates-openshell-qualification.test.ts (2)

15-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow the appId parameter type on qualificationCheck.

Line 23 maps null to undefined. A caller cannot express an explicit null app on the CheckRun, so null and an omitted argument produce identical fixtures. The declared type number | null = null suggests three states but only two exist. Declare the parameter as number | undefined to match the actual behaviour.

🤖 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 `@test/skills/check-gates-openshell-qualification.test.ts` around lines 15 -
27, Update the appId parameter in qualificationCheck to use number | undefined
and default to undefined, matching the existing appId ?? undefined mapping and
eliminating the unsupported explicit-null state.

73-203: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert a distinguishing failure reason per rejection case.

All fourteen rejection cases assert the same string, "openshell-qualification: latest attempt evidence incomplete". The suite therefore cannot prove that each override triggers its own validation. If a single early guard rejected every fixture for an unrelated reason, for example an unregistered run id, all of these tests would still pass while covering nothing.

Add a per-case discriminator. Either surface a reason code in gates.ci and assert it, or add a positive control that the same fixture without the override passes. The accept test at Lines 46-58 uses run 195, not the run ids used by these cases.

Based on path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@test/skills/check-gates-openshell-qualification.test.ts` around lines 73 -
203, Make each rejection case in the parameterized test prove its specific
override is validated rather than relying on the shared failure message. Add a
distinguishing reason code or equivalent per-case assertion, and ensure the
fixtures use a valid positive-control baseline—such as asserting the same run
without each override passes—using the relevant helpers like
openshellQualificationRun, fixtureForRun, and runGate.

Source: Path instructions

test/openshell-qualification-workflow.test.ts (2)

269-276: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the exact release argument vector instead of membership.

toContain("release") and toContain("final") match any element in the array. They do not prove that release is the value of --execution-context or that final is the value of --phase. The selector test at Lines 208-239 already uses an exact toEqual. Use the same exact comparison here so the release path gets equal protection.

♻️ Suggested assertion shape
-      expect(produced.argumentsPassed).toContain("release");
-      expect(produced.argumentsPassed).toContain("final");
-      expect(produced.argumentsPassed).not.toContain("--pr-number");
+      expect(produced.argumentsPassed).toEqual([
+        "--experimental-strip-types",
+        "--no-warnings",
+        path.join(produced.trustedRoot, "scripts/checks/openshell-qualification-contract.mts"),
+        "produce",
+        "--contract-root",
+        produced.trustedRoot,
+        "--candidate-root",
+        produced.candidateRoot,
+        "--execution-context",
+        "release",
+        "--phase",
+        "final",
+        "--repository",
+        REPOSITORY,
+        "--candidate-sha",
+        CANDIDATE_SHA,
+        "--base-sha",
+        BASE_SHA,
+        "--trusted-workflow-sha",
+        CANDIDATE_SHA,
+        "--trusted-workflow-run-id",
+        "12345",
+        "--trusted-workflow-run-attempt",
+        "1",
+        "--trusted-workflow-run-url",
+        "https://github.com/NVIDIA/NemoClaw/actions/runs/12345/attempts/1",
+        "--output",
+        path.join(produced.receiptDirectory, "qualification.json"),
+      ]);
🤖 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 `@test/openshell-qualification-workflow.test.ts` around lines 269 - 276, Update
the assertions in the release producer test to compare produced.argumentsPassed
with the complete expected argument vector using an exact equality assertion,
preserving the expected release execution context, final phase, and absence of
--pr-number.

181-183: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Read the log unconditionally and let the assertion report the failure.

The ternary hides a failed spawn. If result.status is non-zero, argumentsPassed becomes [], and an argument assertion fails with an empty-array diff instead of the real stderr. The tests already assert status first, so the guard adds a branch without value. It also increases the conditional count that the growth guardrails job measures.

🤖 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 `@test/openshell-qualification-workflow.test.ts` around lines 181 - 183, Update
the result-building code around argumentsPassed to read and split the log
unconditionally, removing the result.status ternary. Preserve the existing log
trimming and return structure so the prior status assertion can report spawn
failures while argument assertions receive the actual logged arguments.
test/skills/check-gates-test-fixtures.ts (1)

640-643: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Compute the check-suite id once and reuse it.

Line 643 and Line 698 repeat value.checkSuiteId ?? 100_000 + Number(runId). The run payload at Line 710 and the check-suites route at Line 711 must return the same id. If one copy changes, the gate requests a suite id that has no case entry and the stub exits 9, which produces a confusing failure far from the cause. Derive the id once per fixture and pass it into actionRunData.

Note also that ?? mixed with + reads ambiguously here. It evaluates as value.checkSuiteId ?? (100_000 + Number(runId)), which is the intent. Add the parentheses to make that explicit.

♻️ Proposed extraction
-  const actionRunData = (runId: string, value: ActionRunFixture): Record<string, unknown> => ({
+  const checkSuiteIdFor = (runId: string, value: ActionRunFixture) =>
+    value.checkSuiteId ?? (100_000 + Number(runId));
+  const actionRunData = (runId: string, value: ActionRunFixture): Record<string, unknown> => ({
     id: Number(runId),
     run_attempt: value.attempt,
-    check_suite_id: value.checkSuiteId ?? 100_000 + Number(runId),
+    check_suite_id: checkSuiteIdFor(runId, value),
-      const checkSuiteId = value.checkSuiteId ?? 100_000 + Number(runId);
+      const checkSuiteId = checkSuiteIdFor(runId, value);
🤖 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 `@test/skills/check-gates-test-fixtures.ts` around lines 640 - 643, Compute the
check-suite ID once per fixture using explicit parentheses around the fallback
expression, then pass that value into actionRunData and reuse it for the
check-suites route response. Remove the duplicate value.checkSuiteId ?? 100_000
+ Number(runId) calculations so the run payload and route always share the same
ID.
test/release-qualification-gate.test.ts (1)

1266-1285: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Convert the authority loop into it.each cases.

The for loop runs eight independent scenarios inside one test. The first failure stops the loop, so the remaining authorities stay unverified, and the report does not name the failing authority. The restore at Lines 1280-1284 is also skipped when an assertion throws. Use it.each(authorities) with a fresh fixture per case.

🤖 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 `@test/release-qualification-gate.test.ts` around lines 1266 - 1285, Replace
the enclosing authority-loop test with an it.each(authorities) case so every
authority is reported independently by label. Create a fresh fixture within each
case, and ensure the authority file and index are restored in a finally block so
cleanup runs even when an assertion fails. Preserve the existing mutation,
preflight assertions, and release-tag checks for each authority.
test/openshell-qualification-pr-gate.test.ts (1)

196-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the two duplicate broken-symlink tests.

Both tests create a broken symlink at the contract path and assert the same "regular file" rejection. Only the symlink target name differs, and contractExists does not read the target name. The second test adds no coverage. Convert them into one it.each case over the target names, or keep one test.

🤖 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 `@test/openshell-qualification-pr-gate.test.ts` around lines 196 - 218, Merge
the duplicate broken-symlink cases around contractExists into a single
parameterized it.each test covering both target names, such as
missing-contract.json and removed-contract.json. Preserve the existing setup,
cleanup, and expectation that contractExists throws the “regular file” error.

Source: Path instructions

test/openshell-qualification-producer-workflow-boundary.test.ts (1)

229-235: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Bind each mutation to the specific rejection reason.

Every mutation asserts the same generic prefix "OpenShell qualification producer workflow check failed". A mutation can therefore pass for the wrong reason. Two cases make this concrete. The step mutations select steps by numeric index, so a future step insertion silently retargets the mutation, and the assertion still passes. The single reviewed job mutation and the job-level permission override mutation both trip generic key checks.

Add an expected reason string to each table entry and assert it. That proves the intended invariant rejected the change.

🤖 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 `@test/openshell-qualification-producer-workflow-boundary.test.ts` around lines
229 - 235, Update the mutations table used by the parameterized test around
validateQualificationProducerWorkflow to include an expected rejection-reason
string for every mutation. Assert each thrown error contains both the existing
generic prefix and that entry’s specific reason, and replace index-based step
selection with stable step identification so insertions cannot retarget the
mutation.

Source: Path instructions

test/openshell-qualification-contract.test.ts (1)

483-487: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the expected failure message in these fail-closed cases.

These assertions call .toThrow() without an expected message. Any error satisfies them, including a fixture defect such as an undefined property access. The claims are about rejection reasons, so bind each case to its message. Add the expected substring to the it.each table at Line 469, to the stale-base and cross-phase cases at Lines 517-523, and to the archive cases at Lines 563-575.

Also applies to: 517-524, 563-576

🤖 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 `@test/openshell-qualification-contract.test.ts` around lines 483 - 487, The
qualification receipt rejection tests currently accept any thrown error,
allowing fixture defects to pass; make them assert the intended failure message.
Update the it.each table around the mismatched-source cases and the stale-base,
cross-phase, and archive cases near validateQualificationReceipt to include each
expected message, then pass that message or substring to toThrow while
preserving the existing test scenarios.

Source: Path instructions

scripts/checks/verify-openshell-qualification-pr-gate.mts (2)

288-293: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard lstatSync so a missing blueprint fails with the gate message.

contractExists catches ENOENT and reports a gate-prefixed failure. readBlueprintVersion calls fs.lstatSync without a guard. If nemoclaw-blueprint/blueprint.yaml is absent from the sparse checkout of either root, the CLI throws a raw ENOENT error instead of the OpenShell qualification PR gate failed: message. The gate still fails closed, but the operator sees an unclear cause.

🛡️ Proposed fix
-  const stat = fs.lstatSync(blueprintPath);
+  let stat: fs.Stats;
+  try {
+    stat = fs.lstatSync(blueprintPath);
+  } catch {
+    fail("OpenShell version blueprint cannot be authenticated");
+  }
🤖 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 `@scripts/checks/verify-openshell-qualification-pr-gate.mts` around lines 288 -
293, Guard the fs.lstatSync call in readBlueprintVersion with the same
missing-file handling used by contractExists, converting ENOENT into the
gate-prefixed fail message. Preserve the existing bounded regular-file
validation for files that do exist.

182-224: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a request timeout and bound the response body before buffering.

request calls fetch with no signal. A hung GitHub connection holds the job until the workflow timeout instead of failing with a clear message. getBytes and getJson also buffer the whole body before the size check. GitHub responses are frequently chunked, so content-length is absent and the pre-check reads 0. An oversized body is fully materialized in memory before rejection.

♻️ Proposed change: bound the request and the stream
   const request = async (apiPath: string): Promise<Response> => {
     if (!apiPath.startsWith("repos/NVIDIA/NemoClaw/")) {
       fail("GitHub API path is outside the qualification repository boundary");
     }
     return fetch(`https://api.github.com/${apiPath}`, {
       headers: {
         Accept: "application/vnd.github+json",
         Authorization: `Bearer ${token}`,
         "User-Agent": "nemoclaw-openshell-qualification-pr-gate",
         "X-GitHub-Api-Version": "2022-11-28",
       },
+      signal: AbortSignal.timeout(30_000),
     });
   };

Read the body through response.body and abort once the accumulated length exceeds the limit.

🤖 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 `@scripts/checks/verify-openshell-qualification-pr-gate.mts` around lines 182 -
224, Update request and the getBytes/getJson response handling to use an
AbortController-based timeout for fetch, converting timeout failures into a
clear request error. Stream response.body incrementally and enforce
MAX_ARTIFACT_BYTES or MAX_JSON_BYTES while accumulating chunks, aborting or
failing as soon as the limit is exceeded instead of buffering the entire body
first; preserve existing HTTP-status and JSON parsing behavior.
.github/workflows/openshell-0.0.101-pr-gate.yaml (1)

14-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The zizmor dangerous-triggers hint is a false positive here.

The workflow uses pull_request_target, but it holds no write permission and no repository secret. It runs only base-trusted verifier code from github.event.pull_request.base.sha. The candidate checkout at Lines 125-136 is sparse, data only, and uses persist-credentials: false. No candidate script, action, or package lifecycle hook runs.

Consider adding an inline # zizmor: ignore[dangerous-triggers] comment with this justification so the finding does not recur in future scans.

🤖 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 @.github/workflows/openshell-0.0.101-pr-gate.yaml around lines 14 - 22, Add
an inline zizmor ignore annotation for dangerous-triggers to the
pull_request_target declaration in the workflow, including a concise
justification that it has read-only permissions, uses base-trusted verifier
code, and does not execute candidate code or access secrets.

Source: Linters/SAST tools

.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts (1)

1484-1509: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

The check-suite lookup runs twice for each qualification run.

fetchActionRunMetadata performs an extra check-suites/{id} API call for every run on the qualification workflow path. latestAttemptJobs calls fetchActionRunMetadata a second time at Line 1657 to confirm stability, so each qualification run costs two extra API calls. The refresh comparison at Line 1661 requires appId to be stable, so the second read is intentional. No change is required for correctness.

If the gate checker becomes rate-limit sensitive, cache the check-suite response by check_suite_id and let both reads share it. The stability comparison stays valid because the run's check_suite_id is compared through the other run fields.

🤖 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 @.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts around lines
1484 - 1509, The comment identifies an optional rate-limit optimization rather
than a required correctness fix. If implementing it, cache the check-suite
response keyed by check_suite_id and reuse it across fetchActionRunMetadata
calls from latestAttemptJobs, while preserving the appId extraction and
stability comparison based on the run fields.
.agents/skills/nemoclaw-maintainer-pr-comparator/scripts/collect-gates.sh (1)

56-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

This is the third copy of the required-check inventory.

required_checks here, REQUIRED_CHECK_NAMES in .agents/skills/nemoclaw-maintainer-day/scripts/shared.ts Line 95, and the list in MERGE-GATE.md Line 57 all enumerate the same five-plus-one contexts. A future check addition must update three files. A missed update makes the comparator and the gate checker disagree silently.

As per path instructions for guardrail code: "Derive inventories and limits from a canonical source where possible; flag duplicated lists that can silently drift."

🤖 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 @.agents/skills/nemoclaw-maintainer-pr-comparator/scripts/collect-gates.sh at
line 56, Replace the hardcoded required_checks inventory in collect-gates.sh
with the canonical required-check source used by REQUIRED_CHECK_NAMES and
MERGE-GATE.md, or otherwise generate it from that source so all gate validation
paths stay synchronized. Preserve the existing six check contexts and ensure
collect-gates.sh continues producing the expected JSON array.

Source: Path instructions

scripts/release-cut-tag.sh (3)

388-392: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Check that the receipt path is a regular file before authentication.

Lines 388 and 389 check only that the path is nonempty and absolute. The script does not reject a symlink, a directory, or a FIFO. .agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md applies this check at line 418, so the consumer should not depend on the caller for it.

♻️ Proposed receipt path hardening
   [[ -n "$QUALIFICATION_RECEIPT_PATH" ]] || fail "--qualification-receipt is required by the `#8590` contract"
   [[ "$QUALIFICATION_RECEIPT_PATH" == /* ]] || fail "--qualification-receipt must use an absolute path"
+  [[ -f "$QUALIFICATION_RECEIPT_PATH" && ! -L "$QUALIFICATION_RECEIPT_PATH" ]] || fail "--qualification-receipt must be a regular file"
   command -v gh >/dev/null 2>&1 || fail "gh is required to authenticate the `#8590` qualification receipt"
🤖 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 `@scripts/release-cut-tag.sh` around lines 388 - 392, Before
authenticate_final_qualification_receipt, validate that
QUALIFICATION_RECEIPT_PATH exists as a regular file and is not a symlink,
directory, or FIFO; reject invalid paths with fail. Keep the existing nonempty
and absolute-path checks, and ensure authentication only runs after this
validation.

67-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the absolute authority paths from the relative constants.

Each authority path is written twice here, and the same eight literals appear a third time in qualification_runtime_authority_paths at lines 154-163. A future path change must be applied in three places.

The comment on lines 152-153 justifies keeping this inventory independent from the .mts modules. It does not require three copies inside this file.

♻️ Proposed deduplication of the absolute/relative pairs
-qualification_contract="$repo_root/ci/openshell-0.0.101-qualification-v1.json"
-qualification_validator="$repo_root/scripts/checks/openshell-qualification-contract.mts"
-qualification_core="$repo_root/scripts/checks/openshell-qualification-core.mts"
-qualification_github="$repo_root/scripts/checks/openshell-qualification-github.mts"
-qualification_io="$repo_root/scripts/checks/openshell-qualification-io.mts"
-qualification_matrix="$repo_root/scripts/checks/openshell-qualification-matrix.mts"
-qualification_schema="$repo_root/scripts/checks/openshell-qualification-schema.mts"
-qualification_archive_reader="$repo_root/scripts/scorecard/read-artifact-zip.mts"
 qualification_contract_relative="ci/openshell-0.0.101-qualification-v1.json"
 qualification_validator_relative="scripts/checks/openshell-qualification-contract.mts"
 qualification_core_relative="scripts/checks/openshell-qualification-core.mts"
 qualification_github_relative="scripts/checks/openshell-qualification-github.mts"
 qualification_io_relative="scripts/checks/openshell-qualification-io.mts"
 qualification_matrix_relative="scripts/checks/openshell-qualification-matrix.mts"
 qualification_schema_relative="scripts/checks/openshell-qualification-schema.mts"
 qualification_archive_reader_relative="scripts/scorecard/read-artifact-zip.mts"
+qualification_contract="$repo_root/$qualification_contract_relative"
+qualification_validator="$repo_root/$qualification_validator_relative"
+qualification_core="$repo_root/$qualification_core_relative"
+qualification_github="$repo_root/$qualification_github_relative"
+qualification_io="$repo_root/$qualification_io_relative"
+qualification_matrix="$repo_root/$qualification_matrix_relative"
+qualification_schema="$repo_root/$qualification_schema_relative"
+qualification_archive_reader="$repo_root/$qualification_archive_reader_relative"
🤖 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 `@scripts/release-cut-tag.sh` around lines 67 - 84, Deduplicate the authority
path definitions in the release-cut script by keeping the relative constants as
the source of truth and deriving the corresponding absolute paths from
repo_root. Update qualification_runtime_authority_paths to reuse those derived
variables instead of repeating path literals, while preserving the independent
inventory from the .mts modules.

22-25: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Validate that --qualification-receipt receives a value.

${2:-} accepts a missing value and stores an empty string. In legacy mode, line 393 then treats the flag as absent and the script continues without a message. In contract mode, shift 2 may also fail before line 388 reports the missing receipt.

♻️ Proposed argument validation
     --qualification-receipt)
-      QUALIFICATION_RECEIPT_PATH="${2:-}"
+      [[ $# -ge 2 && -n "${2:-}" ]] || fail "--qualification-receipt requires a path"
+      QUALIFICATION_RECEIPT_PATH="$2"
       shift 2
       ;;
🤖 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 `@scripts/release-cut-tag.sh` around lines 22 - 25, Update the
--qualification-receipt case in the argument parser to reject a missing or empty
following value before assigning QUALIFICATION_RECEIPT_PATH or executing shift
2. Route the failure through the script’s existing argument-error handling so
both legacy and contract modes report the missing receipt consistently.
🤖 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 `@scripts/checks/openshell-qualification-contract.mts`:
- Around line 194-206: Update validateRepository in
openshell-qualification-core.mts to reject repository components equal to "." or
"..", while preserving the existing repository format validation. In both
createGitHubCliReader and createGitHubReader, normalize and validate the
constructed API path before invoking GitHub, ensuring normalization cannot
escape the repos/ boundary; reject any path containing traversal or resolving
outside that prefix.
- Around line 674-698: Update the validate-archive branch around archivePath and
fs.lstatSync to validate the archive path using the established
readBoundedRegularFile path-checking behavior, and wrap lstatSync failures so
missing or non-canonical paths go through fail with the controlled qualification
artifact message instead of exposing a raw error. Preserve the existing
regular-file, symlink, and size checks before reading the receipt.
- Around line 148-192: Update createGitHubReader so fetch uses an
AbortSignal.timeout(...) and both getBytes and getJson consume response bodies
through bounded streams, enforcing QUALIFICATION_MAX_ARTIFACT_BYTES or
MAX_GITHUB_JSON_BYTES during reads rather than only after buffering; retain the
existing HTTP and JSON validation behavior. Also add an execution timeout to
execFileSync in createGitHubCliReader.

In `@scripts/checks/openshell-qualification-core.mts`:
- Around line 535-554: Update validateArtifactUrl to enforce the contract’s
approved GitHub-host allowlist after parsing the URL, rejecting arbitrary HTTPS
hosts while preserving the existing immutability checks. Add validation tests
covering accepted allowlisted hosts and rejection of non-approved HTTPS hosts.

In `@scripts/checks/openshell-qualification-github.mts`:
- Around line 1050-1077: Update the receipt construction in the tests.push block
to read sourceReceipt.tests[0] once and require it to exist before creating the
run. Replace the optional accesses and ?? [] fallbacks for cells, jobs,
requiredCases, and requiredDimensions with fields from that validated test
entry, and fail immediately when the entry is absent so a success receipt cannot
be produced without evidence.

In `@scripts/checks/openshell-qualification-matrix.mts`:
- Around line 265-288: Update validateApprovalUrl and validateEvidenceUrl to
construct the expected repository path with exact string operations, or properly
escape the entire repository before creating the regular expression. Ensure
repository metacharacters such as "." cannot match alternative path characters,
while preserving the existing issues/pull and positive numeric ID validation.

In `@scripts/checks/openshell-qualification-paths.mts`:
- Around line 101-114: Consolidate the duplicated validateRepositoryPath
implementation shared by openshell-qualification-paths.mts and
openshell-qualification-core.mts. Export and reuse one implementation, or
parameterize its failure prefix, while preserving each caller’s validation
behavior and resolving the differing SAFE_PATH_PATTERN definitions through a
single canonical pattern.

In `@scripts/checks/verify-openshell-qualification-pr-gate.mts`:
- Around line 48-55: The required-check inventory and GitHub Actions app ID are
duplicated and have drifted. In
scripts/checks/verify-openshell-qualification-pr-gate.mts lines 48-55, create or
reuse a canonical shared module containing REQUIRED_STATUS_CONTEXTS—including
openshell-qualification—and GITHUB_ACTIONS_APP_ID, then import both instead of
redeclaring them; update
.agents/skills/nemoclaw-maintainer-pr-comparator/scripts/collect-gates.sh line
56 to derive required_checks from that shared inventory rather than hard-coding
the JSON array.

In `@scripts/checks/verify-openshell-qualification-producer-workflow.mts`:
- Around line 383-392: Move fs.readFileSync out of the try block in the producer
workflow validation function so only YAML.parse failures report “producer
workflow YAML is malformed,” while read errors retain their proper handling.
Apply the same separation in regularFileSource: ensure file-read failures are
converted to that check’s prefixed error before parsing.

In `@scripts/release-cut-tag.sh`:
- Around line 131-133: Update scripts/release-cut-tag.sh lines 131-133 by
wrapping both git cat-file probes in if/then blocks so missing contracts leave
the flags unset and allow the comparison to run. Apply the same if/then form to
CANDIDATE_HAS_CONTRACT and BASE_HAS_CONTRACT in
.agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md lines 149-150,
preserving reachability of the false:false branch.

In `@test/openshell-qualification-contract.test.ts`:
- Around line 489-504: The changed test files contain conditional branching that
violates the growth guardrail. In test/openshell-qualification-contract.test.ts
lines 489-504, replace the loop and three if statements with an it.each table
covering each result and boundary, and move branching in finalArtifactApi,
retirementAuthenticationFixture, and producerApi into a helper under
test/helpers/. In test/openshell-qualification-pr-gate.test.ts lines 89-97, move
the treeReader stub branching into a test helper or use a path-to-response map.
In test/openshell-qualification-producer-workflow-boundary.test.ts lines 14-42,
move the record, array, and step guards into a helper under test/helpers/.

In `@test/openshell-qualification-pr-gate.test.ts`:
- Around line 182-184: Update the contract read in the qualification test around
validateQualificationContract to resolve the file from import.meta.dirname using
path.join(import.meta.dirname, "..", QUALIFICATION_CONTRACT_PATH), matching
openshell-qualification-contract.test.ts. Keep the existing UTF-8 read and
validation flow unchanged, and avoid relying on the process working directory.

In `@test/release-qualification-gate.test.ts`:
- Around line 34-43: Remove the duplicated path array
QUALIFICATION_RUNTIME_AUTHORITY_PATHS and derive the runtime authority inventory
from the imported QUALIFICATION_FROZEN_AUTHORITY_PATHS constant. Update the
labelled table around the qualification authority checks to retain only local
label mappings while iterating over the canonical paths, preserving the existing
labels and test behavior.
- Around line 804-820: Update the test around the cut invocation to create and
use a fresh fixture via createFixture() instead of sharedFixture, while
preserving the existing failure assertions and cleanup behavior.
- Around line 1056-1090: Remove branching from the release qualification tests
to satisfy the growth guardrail: extend the mutation cases used by the it.each
table with per-case callbacks that perform each mutation, then invoke the
callback from the shared test body instead of dispatching on mutation. Extract
the remaining conditionals near the other reported locations into named helpers,
preserving existing behavior while keeping each test body linear.

---

Nitpick comments:
In @.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts:
- Around line 1484-1509: The comment identifies an optional rate-limit
optimization rather than a required correctness fix. If implementing it, cache
the check-suite response keyed by check_suite_id and reuse it across
fetchActionRunMetadata calls from latestAttemptJobs, while preserving the appId
extraction and stability comparison based on the run fields.

In @.agents/skills/nemoclaw-maintainer-pr-comparator/scripts/collect-gates.sh:
- Line 56: Replace the hardcoded required_checks inventory in collect-gates.sh
with the canonical required-check source used by REQUIRED_CHECK_NAMES and
MERGE-GATE.md, or otherwise generate it from that source so all gate validation
paths stay synchronized. Preserve the existing six check contexts and ensure
collect-gates.sh continues producing the expected JSON array.

In @.github/workflows/openshell-0.0.101-pr-gate.yaml:
- Around line 14-22: Add an inline zizmor ignore annotation for
dangerous-triggers to the pull_request_target declaration in the workflow,
including a concise justification that it has read-only permissions, uses
base-trusted verifier code, and does not execute candidate code or access
secrets.

In @.github/workflows/openshell-0.0.101-qualification.yaml:
- Around line 83-87: Update all four GitHub API curl invocations in the
workflow, including the calls near permission_json and the other
collaborator/API lookups, to add a bounded --max-time and retry configuration.
Apply identical timeout and retry flags to each request while preserving the
existing failure, silence, error-reporting, and HTTPS protocol options.

In `@ci/openshell-0.0.101-qualification-v1.json`:
- Around line 76-100: Add the contract-owned "upgrade-from-00085" identifier to
the requiredCases arrays for both 0.0.85 upgrade test entries, preserving the
existing direct-public-v85 case and matching the vocabulary used by the 0.0.99
upgrade tests.

In `@scripts/checks/openshell-qualification-contract.mts`:
- Around line 561-573: Replace the hardcoded schemaVersion value in the returned
qualification contract with the existing QUALIFICATION_CONTRACT_SCHEMA_VERSION
constant, preserving the rest of the contract fields unchanged.
- Around line 503-508: Update the readValidatedArtifactZipEntry call used for
receiptSource to match parseQualificationReceiptArchive: use
QUALIFICATION_MAX_JSON_BYTES for maxBytes and set maxEntries to 1. Keep the
existing digest validation and failure behavior unchanged.
- Around line 632-654: Update the validate-live branch to require --pr-number
for non-release contexts, matching the selector validation used by validate,
validate-archive, create, and produce. Extend the common required CLI values or
otherwise restrict this command to release context, ensuring expectationFromCli
never receives an undefined pull-request number.

In `@scripts/checks/openshell-qualification-core.mts`:
- Around line 1264-1280: Move the knownResults Set from inside validateResult to
a module-level constant, then have validateResult reuse that constant for
validation while preserving the existing result values and failure behavior.
- Around line 726-733: Update validateTestDescriptor to derive the approval
repository from the canonical QUALIFICATION_SCOPE value instead of hardcoding
"NVIDIA/NemoClaw"; preserve the existing validated value passed to
validateQualificationApprovedExceptions and avoid duplicating the repository
literal.
- Around line 890-1105: Reduce the complexity of
validateQualificationLifecycleTransition by extracting the per-test mapping
rules into focused helpers, including bootstrap staging, selector promotion,
final promotion, and the unchanged-mapping validation path. Keep transition
dispatch in validateQualificationLifecycleTransition, preserve all existing
validation behavior and error messages, and prefix intentionally unused
destructured variables with an underscore.
- Around line 750-888: Extract the entire inventoryState === "frozen" validation
branch from validateQualificationContract into a named
requireFrozenInventory(tests, artifacts, repository) helper, preserving all
matrix, runtime identity, artifact, aggregate-budget, and staged-mapping checks
unchanged. Replace the inline branch with a helper call and prefix any
intentionally unused parameters or locals with _.

In `@scripts/checks/openshell-qualification-io.mts`:
- Around line 195-213: Split relativePath once into a local parts array before
the loop, then iterate over that array and use its length for both parent and
final-component boundary checks. Update the loop around cursor and stats without
changing validation behavior.

In `@scripts/checks/openshell-qualification-matrix.mts`:
- Around line 378-419: Update validateCellException to replace the
JSON.stringify(identity) comparison with direct comparisons of each identity
field against the corresponding expected value. Preserve the existing
approvalUrl, approvedBy, and reason checks and continue failing with the same
message when any field differs.

In `@scripts/checks/verify-openshell-qualification-pr-gate.mts`:
- Around line 288-293: Guard the fs.lstatSync call in readBlueprintVersion with
the same missing-file handling used by contractExists, converting ENOENT into
the gate-prefixed fail message. Preserve the existing bounded regular-file
validation for files that do exist.
- Around line 182-224: Update request and the getBytes/getJson response handling
to use an AbortController-based timeout for fetch, converting timeout failures
into a clear request error. Stream response.body incrementally and enforce
MAX_ARTIFACT_BYTES or MAX_JSON_BYTES while accumulating chunks, aborting or
failing as soon as the limit is exceeded instead of buffering the entire body
first; preserve existing HTTP-status and JSON parsing behavior.

In `@scripts/release-cut-tag.sh`:
- Around line 388-392: Before authenticate_final_qualification_receipt, validate
that QUALIFICATION_RECEIPT_PATH exists as a regular file and is not a symlink,
directory, or FIFO; reject invalid paths with fail. Keep the existing nonempty
and absolute-path checks, and ensure authentication only runs after this
validation.
- Around line 67-84: Deduplicate the authority path definitions in the
release-cut script by keeping the relative constants as the source of truth and
deriving the corresponding absolute paths from repo_root. Update
qualification_runtime_authority_paths to reuse those derived variables instead
of repeating path literals, while preserving the independent inventory from the
.mts modules.
- Around line 22-25: Update the --qualification-receipt case in the argument
parser to reject a missing or empty following value before assigning
QUALIFICATION_RECEIPT_PATH or executing shift 2. Route the failure through the
script’s existing argument-error handling so both legacy and contract modes
report the missing receipt consistently.

In `@test/openshell-qualification-contract.test.ts`:
- Around line 483-487: The qualification receipt rejection tests currently
accept any thrown error, allowing fixture defects to pass; make them assert the
intended failure message. Update the it.each table around the mismatched-source
cases and the stale-base, cross-phase, and archive cases near
validateQualificationReceipt to include each expected message, then pass that
message or substring to toThrow while preserving the existing test scenarios.

In `@test/openshell-qualification-pr-gate.test.ts`:
- Around line 196-218: Merge the duplicate broken-symlink cases around
contractExists into a single parameterized it.each test covering both target
names, such as missing-contract.json and removed-contract.json. Preserve the
existing setup, cleanup, and expectation that contractExists throws the “regular
file” error.

In `@test/openshell-qualification-producer-workflow-boundary.test.ts`:
- Around line 229-235: Update the mutations table used by the parameterized test
around validateQualificationProducerWorkflow to include an expected
rejection-reason string for every mutation. Assert each thrown error contains
both the existing generic prefix and that entry’s specific reason, and replace
index-based step selection with stable step identification so insertions cannot
retarget the mutation.

In `@test/openshell-qualification-workflow.test.ts`:
- Around line 269-276: Update the assertions in the release producer test to
compare produced.argumentsPassed with the complete expected argument vector
using an exact equality assertion, preserving the expected release execution
context, final phase, and absence of --pr-number.
- Around line 181-183: Update the result-building code around argumentsPassed to
read and split the log unconditionally, removing the result.status ternary.
Preserve the existing log trimming and return structure so the prior status
assertion can report spawn failures while argument assertions receive the actual
logged arguments.

In `@test/release-qualification-gate.test.ts`:
- Around line 1266-1285: Replace the enclosing authority-loop test with an
it.each(authorities) case so every authority is reported independently by label.
Create a fresh fixture within each case, and ensure the authority file and index
are restored in a finally block so cleanup runs even when an assertion fails.
Preserve the existing mutation, preflight assertions, and release-tag checks for
each authority.

In `@test/skills/check-gates-openshell-qualification.test.ts`:
- Around line 15-27: Update the appId parameter in qualificationCheck to use
number | undefined and default to undefined, matching the existing appId ??
undefined mapping and eliminating the unsupported explicit-null state.
- Around line 73-203: Make each rejection case in the parameterized test prove
its specific override is validated rather than relying on the shared failure
message. Add a distinguishing reason code or equivalent per-case assertion, and
ensure the fixtures use a valid positive-control baseline—such as asserting the
same run without each override passes—using the relevant helpers like
openshellQualificationRun, fixtureForRun, and runGate.

In `@test/skills/check-gates-test-fixtures.ts`:
- Around line 640-643: Compute the check-suite ID once per fixture using
explicit parentheses around the fallback expression, then pass that value into
actionRunData and reuse it for the check-suites route response. Remove the
duplicate value.checkSuiteId ?? 100_000 + Number(runId) calculations so the run
payload and route always share the same ID.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 06080c1c-85f5-42a3-9946-7613c86f4981

📥 Commits

Reviewing files that changed from the base of the PR and between 87f27ae and 6d30e49.

📒 Files selected for processing (32)
  • .agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md
  • .agents/skills/nemoclaw-maintainer-day/MERGE-GATE.md
  • .agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts
  • .agents/skills/nemoclaw-maintainer-day/scripts/shared.ts
  • .agents/skills/nemoclaw-maintainer-pr-comparator/scripts/collect-gates.sh
  • .github/workflows/openshell-0.0.101-pr-gate.yaml
  • .github/workflows/openshell-0.0.101-qualification.yaml
  • ci/openshell-0.0.101-qualification-v1.json
  • scripts/checks/openshell-qualification-contract.mts
  • scripts/checks/openshell-qualification-core.mts
  • scripts/checks/openshell-qualification-github.mts
  • scripts/checks/openshell-qualification-io.mts
  • scripts/checks/openshell-qualification-matrix.mts
  • scripts/checks/openshell-qualification-paths.mts
  • scripts/checks/openshell-qualification-schema.mts
  • scripts/checks/run.mts
  • scripts/checks/verify-openshell-qualification-pr-gate.mts
  • scripts/checks/verify-openshell-qualification-producer-workflow.mts
  • scripts/release-cut-tag.sh
  • test/checks-runner.test.ts
  • test/helpers/openshell-qualification-contract-fixture.ts
  • test/helpers/release-qualification-gate-fixture.ts
  • test/openshell-qualification-contract.test.ts
  • test/openshell-qualification-paths.test.ts
  • test/openshell-qualification-pr-gate-workflow.test.ts
  • test/openshell-qualification-pr-gate.test.ts
  • test/openshell-qualification-producer-workflow-boundary.test.ts
  • test/openshell-qualification-workflow.test.ts
  • test/release-qualification-gate.test.ts
  • test/skills/check-gates-compliance.test.ts
  • test/skills/check-gates-openshell-qualification.test.ts
  • test/skills/check-gates-test-fixtures.ts

Comment on lines +148 to +192
function createGitHubReader(token: string): QualificationGitHubReader {
const request = async (apiPath: string): Promise<Response> => {
if (!apiPath.startsWith("repos/")) fail("GitHub API path is outside the repository boundary");
return fetch(`https://api.github.com/${apiPath}`, {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token}`,
"User-Agent": "nemoclaw-openshell-qualification",
"X-GitHub-Api-Version": "2022-11-28",
},
});
};
return {
async getBytes(apiPath: string): Promise<Buffer> {
const response = await request(apiPath);
const declaredLength = Number(response.headers.get("content-length") ?? "0");
if (Number.isFinite(declaredLength) && declaredLength > QUALIFICATION_MAX_ARTIFACT_BYTES) {
fail("GitHub artifact response is oversized");
}
const result = Buffer.from(await response.arrayBuffer());
if (!response.ok) fail(`GitHub API request failed with HTTP ${response.status}`);
if (result.length > QUALIFICATION_MAX_ARTIFACT_BYTES) {
fail("GitHub artifact response is oversized");
}
return result;
},
async getJson(apiPath: string): Promise<unknown> {
const response = await request(apiPath);
const declaredLength = Number(response.headers.get("content-length") ?? "0");
if (Number.isFinite(declaredLength) && declaredLength > MAX_GITHUB_JSON_BYTES) {
fail("GitHub JSON response is oversized");
}
const source = await response.text();
if (Buffer.byteLength(source, "utf8") > MAX_GITHUB_JSON_BYTES) {
fail("GitHub JSON response is oversized");
}
if (!response.ok) fail(`GitHub API request failed with HTTP ${response.status}`);
try {
return JSON.parse(source) as unknown;
} catch {
fail("GitHub API returned malformed JSON");
}
},
};
}

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 | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Node.js AbortSignal.timeout availability fetch signal option

💡 Result:

The AbortSignal.timeout() static method is available in Node.js and is fully supported for use with the fetch API [1]. Availability: AbortSignal.timeout(delay) was added to Node.js in versions 17.3.0 and 16.14.0 [2][3][4][5]. It is considered a stable and standard feature in all modern, supported versions of Node.js [1]. Usage with fetch: You can pass the signal returned by AbortSignal.timeout() directly into the signal option of the fetch API [1]. This provides a native, concise way to enforce a timeout on HTTP requests without requiring manual timers or an AbortController instance [1][6]. Example: const res = await fetch('https://api.example.com/data', { signal: AbortSignal.timeout(5000), // Aborts after 5 seconds }); Handling Timeouts: When a request is cancelled via AbortSignal.timeout(), the operation will reject with a TimeoutError [7][6]. It is recommended to check for this error name if you need to distinguish a timeout from a standard AbortError (which typically occurs when an operation is cancelled manually via an AbortController) [7][1][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target symbols ---'
rg -n -C 5 'createGitHubReader|createGitHubCliReader|QUALIFICATION_MAX_ARTIFACT_BYTES|MAX_GITHUB_JSON_BYTES|AbortSignal|execFileSync|fetch\\(' scripts/checks/openshell-qualification-contract.mts

printf '%s\n' '--- runtime and package configuration ---'
rg -n -C 3 '"(engines|node|typescript|tsx)"|node-version|setup-node|openshell-qualification-contract' \
  package.json package-lock.json pnpm-lock.yaml yarn.lock .github scripts 2>/dev/null || true

printf '%s\n' '--- relevant file outline and size ---'
wc -l scripts/checks/openshell-qualification-contract.mts
ast-grep outline scripts/checks/openshell-qualification-contract.mts

Repository: NVIDIA/NemoClaw

Length of output: 2014


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target symbols ---'
rg -n -C 5 'createGitHubReader|createGitHubCliReader|QUALIFICATION_MAX_ARTIFACT_BYTES|MAX_GITHUB_JSON_BYTES|AbortSignal|execFileSync|fetch\(' scripts/checks/openshell-qualification-contract.mts

printf '%s\n' '--- runtime and package configuration ---'
rg -n -C 3 '"(engines|node|typescript|tsx)"|node-version|setup-node|openshell-qualification-contract' \
  package.json package-lock.json pnpm-lock.yaml yarn.lock .github scripts 2>/dev/null || true

printf '%s\n' '--- relevant file outline and size ---'
wc -l scripts/checks/openshell-qualification-contract.mts
ast-grep outline scripts/checks/openshell-qualification-contract.mts

Repository: NVIDIA/NemoClaw

Length of output: 2014


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "createGitHubCliReader"

💡 Result:

I couldn’t find any source in the retrieved results that defines or documents a symbol/function named "createGitHubCliReader". What I did find instead are references in the NVIDIA/NemoClaw repo to using the GitHub CLI ("gh") for PR creation and GitHub API access (e.g., skills that instruct using gh auth status and gh pr create) [1][2], and internal code for GitHub API access via tools/advisors/github.mts [3]. None of the discovered files mention or implement createGitHubCliReader. If you meant a different name (e.g., createGitHubCli*, GitHubCliReader, or a function inside another package), tell me the repo/package path or share the surrounding snippet, and I can re-run targeted searches for the exact identifier.

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "openshell-qualification-contract.mts"

💡 Result:

The term openshell-qualification-contract.mts does not refer to a standalone public specification or standard, but rather to internal test suite files (typically named with the.mts extension for TypeScript modules) within the NVIDIA NemoClaw repository [1][2]. These files, such as those found in test/e2e-scenario/support-tests/ or similar directories, act as automated E2E (end-to-end) validation mechanisms [1]. Their primary purpose is to verify the compatibility, authentication boundaries, and runtime requirements between the NemoClaw framework and specific versions of the OpenShell CLI [1][2][3]. Key functions of these "contracts" include: - Validating runtime behavior: Ensuring that when NemoClaw interacts with the OpenShell gateway, it correctly handles authentication, JWTs, mTLS boundaries, and credential resolution [1][2][3]. - Enforcing compatibility: Testing that the installed OpenShell binary meets specific version floors and ceilings required by the NemoClaw release [1][4][5]. - CI/CD validation: Serving as gatekeepers in the build pipeline to ensure that candidate OpenShell versions function as expected before being pinned for production use [2][3][6]. In essence, these are not external contracts, but internal test scripts used to ensure the integration between NemoClaw and OpenShell remains stable and secure across version upgrades [3][7].

Citations:


🌐 Web query:

NVIDIA NemoClaw package.json node version

💡 Result:

NVIDIA NemoClaw requires Node.js version 22.16 or later [1][2]. Official documentation specifies that 22.19 or later is required for current environments [3][4]. The project's package.json file includes a script, node scripts/check-node-version.js, which is executed during the preinstall phase to enforce these version requirements [5].

Citations:


🌐 Web query:

NVIDIA NemoClaw pull 5596 openshell qualification contract createGitHubReader

💡 Result:

The query appears to conflate two distinct technical topics: NVIDIA NemoClaw and the Keystatic library's createGitHubReader function [1][2][3][4]. NVIDIA NemoClaw is an open-source reference stack designed for running AI agents securely within NVIDIA OpenShell sandboxes [2][5]. Pull Request #5596 (titled "chore(openshell): upgrade supported version to 0.0.67") in the NVIDIA/NemoClaw repository was a merged PR that advanced NemoClaw's stable OpenShell support [6]. It implemented updates such as checksum-pinned installation, authenticated gateway startup, and improved failure recovery [6]. Separately, createGitHubReader is a function provided by the Keystatic core library (@keystatic/core) [3][7]. It is used to create a reader instance for fetching content directly from a GitHub repository [4]. This function is unrelated to NVIDIA NemoClaw or OpenShell [2][3]. There is no "qualification contract" or similar mechanism linking createGitHubReader to NemoClaw's pull request #5596 [6][3]. The latter is purely focused on upgrading the OpenShell runtime version and strengthening its sandbox security and lifecycle management within the NemoClaw stack [6][8].

Citations:


🌐 Web query:

NVIDIA NemoClaw commit a709a3538b3eff3292b9d1a863025c2374186879 openshell qualification contract

💡 Result:

NVIDIA NemoClaw commit a709a35, titled ci(deps): validate OpenShell candidate runtime (#6738), is a technical update within the NVIDIA NemoClaw repository [1]. It was authored by HOYALIM and committed on July 15, 2026 [1]. The commit introduces mechanisms to validate the OpenShell candidate runtime, specifically focusing on ensuring compatibility and enforcing security contracts [1]. It includes updates to end-to-end (e2e) tests, such as: - test/e2e/support/openshell-gateway-auth-source-contract-helpers.test.ts [1] - test/e2e/support/openshell-gateway-auth-contract-workflow-boundary.test.ts [1] This commit specifically resolved OpenShell version 0.0.82 for the repository and bound official release digests to ensure the integrity of the runtime environment [1]. The changes ensure that if the gateway-auth contract invocation proof is absent, the system fails closed, maintaining the security posture required for sandboxed AI agents within the NemoClaw framework [1]. This aligns with NemoClaw's architecture, which utilizes NVIDIA OpenShell to provide sandboxed execution environments, policy controls, and managed inference [2][3][4].

Citations:


🌐 Web query:

Node.js 22 fetch Response arrayBuffer text buffers body content-length absent execFileSync timeout documentation

💡 Result:

Node.js 22 fetch + Response body APIs (arrayBuffer/text) and behavior when Content-Length is absent 1) Fetch in Node.js 22 - Node.js exposes fetch as a global (browser-compatible) implementation built on undici. [1] 2) Response body: arrayBuffer and text - A Response’s body-reading methods you’ll commonly use are body.arrayBuffer and body.text. These are part of the Fetch API/Body interface that node-fetch documents (and Node’s fetch follows the same WHATWG Fetch concepts). [2] - In practice, calling res.arrayBuffer or res.text consumes the response body stream and returns the fully buffered content (arrayBuffer returns an ArrayBuffer; text returns a string). [2] 3) When Content-Length is absent - There isn’t a “special” Content-Length requirement for res.arrayBuffer / res.text; if the server didn’t send Content-Length, the response is typically delivered with chunked transfer encoding or using connection close semantics, and the client can still read the stream until it ends. - What does matter: if you are the sender (request side) and you use a stream as the request body, Content-Length is not set automatically by node-fetch. That’s request-side behavior, but it explains a common “Content-Length absent” scenario you may observe in Node apps. [3][4] 4) Note about “body absent” vs “Content-Length: 0” edge cases - The undici project has discussed/triaged an issue where internal readable stream plumbing could be created even when a response effectively has no body, such as when content-length is 0 (i.e., headers indicate no body). This is an implementation detail/bug report, not a guarantee that res.arrayBuffer/res.text will behave differently in user land, but it does relate to “content-length absent / no body” edge cases. [5] execFileSync timeout documentation - Node.js child_process.execFileSync (and execSync) support an options.timeout: “In milliseconds the maximum amount of time the process is allowed to run.” Default is undefined. [6] - If timeout > 0, Node sends the signal identified by options.killSignal (default 'SIGTERM') if the child runs longer than timeout milliseconds. [6] - If the timeout is encountered, execFileSync will not return until the process has completely exited. [6] If you tell me which exact fetch/Response code you’re using (and whether the missing header is on the request you send, or on the response you receive), I can narrow this to the precise behavior you’re trying to confirm.

Citations:


Bound external responses and subprocesses

fetch can stall because createGitHubReader does not provide an AbortSignal. Use AbortSignal.timeout(...). getBytes and getJson read the complete body before enforcing size limits, so missing or inaccurate content-length can allow oversized responses to consume memory. Read responses through a bounded stream. Add a timeout to execFileSync in createGitHubCliReader.

🤖 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 `@scripts/checks/openshell-qualification-contract.mts` around lines 148 - 192,
Update createGitHubReader so fetch uses an AbortSignal.timeout(...) and both
getBytes and getJson consume response bodies through bounded streams, enforcing
QUALIFICATION_MAX_ARTIFACT_BYTES or MAX_GITHUB_JSON_BYTES during reads rather
than only after buffering; retain the existing HTTP and JSON validation
behavior. Also add an execution timeout to execFileSync in
createGitHubCliReader.

Comment on lines +194 to +206
function createGitHubCliReader(): QualificationArtifactReader {
const invoke = (apiPath: string, maxBuffer: number): Buffer => {
if (!apiPath.startsWith("repos/")) fail("GitHub API path is outside the repository boundary");
try {
return execFileSync("gh", ["api", "--hostname", "github.com", apiPath], {
encoding: "buffer",
maxBuffer,
stdio: ["ignore", "pipe", "pipe"],
});
} catch {
fail("authenticated GitHub API lookup failed");
}
};

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

The repos/ prefix check does not stop path escape.

invoke accepts any apiPath that starts with repos/. It does not reject . or .. segments. The same check exists in createGitHubReader at Line 150.

The root cause sits in validateRepository in scripts/checks/openshell-qualification-core.mts Lines 347-352. REPOSITORY_PATTERN is /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u, and . is a member of that class. The value ../.. therefore passes validation. Every API path in this cohort is built by interpolating repository, for example repos/${repository}/git/tags/.... A contract that declares repository as ../.. redirects those calls outside the repository boundary while the repos/ prefix check still passes.

The contract file is base-trusted, so this is not currently reachable from a pull request. The guard should still hold on its own.

Reject . and .. segments in validateRepository, and normalize the API path in both readers before you send it.

🛡️ Proposed fix in `openshell-qualification-core.mts`
 export function validateRepository(value: unknown): string {
-  if (typeof value !== "string" || !REPOSITORY_PATTERN.test(value)) {
+  if (
+    typeof value !== "string" ||
+    !REPOSITORY_PATTERN.test(value) ||
+    value.split("/").some((part) => part === "." || part === "..")
+  ) {
     fail("repository is invalid");
   }
   return value;
 }
🤖 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 `@scripts/checks/openshell-qualification-contract.mts` around lines 194 - 206,
Update validateRepository in openshell-qualification-core.mts to reject
repository components equal to "." or "..", while preserving the existing
repository format validation. In both createGitHubCliReader and
createGitHubReader, normalize and validate the constructed API path before
invoking GitHub, ensuring normalization cannot escape the repos/ boundary;
reject any path containing traversal or resolving outside that prefix.

Comment on lines +674 to +698
if (command === "validate-archive") {
const validateKeys = [...common, "--archive", "--candidate-contract"];
const phase = validatePhase(values.get("--phase"));
const executionContext = validateExecutionContext(values.get("--execution-context"));
validatePhaseExecutionContext(phase, executionContext);
if (executionContext !== "release") validateKeys.push("--pr-number");
requireCliValues(values, validateKeys);
const contract = receiptContractFromCli(values, phase, executionContext);
const archivePath = values.get("--archive") ?? "";
const stats = fs.lstatSync(archivePath);
if (
!stats.isFile() ||
stats.isSymbolicLink() ||
stats.size > QUALIFICATION_MAX_ARTIFACT_BYTES
) {
fail("qualification artifact must be a bounded regular non-link file");
}
const receipt = readQualificationReceiptArchive(
fs.readFileSync(archivePath),
contract,
expectationFromCli(values),
);
process.stdout.write(`${JSON.stringify(receipt)}\n`);
return;
}

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

fs.lstatSync throws a raw error for a missing archive.

Line 683 calls fs.lstatSync(archivePath) without a preceding path check and without a try/catch. readBoundedRegularFile in scripts/checks/openshell-qualification-io.mts Lines 140-157 performs both. A missing or non-canonical --archive value produces an unhandled ENOENT stack trace instead of the controlled fail message.

Validate the path and wrap the lstatSync call.

🛡️ Proposed fix
     const archivePath = values.get("--archive") ?? "";
-    const stats = fs.lstatSync(archivePath);
+    if (!SAFE_TEXT_PATTERN.test(archivePath) || path.normalize(archivePath) !== archivePath) {
+      fail("qualification artifact path is invalid or non-canonical");
+    }
+    let stats: fs.Stats;
+    try {
+      stats = fs.lstatSync(archivePath);
+    } catch {
+      fail("qualification artifact is missing");
+    }
📝 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
if (command === "validate-archive") {
const validateKeys = [...common, "--archive", "--candidate-contract"];
const phase = validatePhase(values.get("--phase"));
const executionContext = validateExecutionContext(values.get("--execution-context"));
validatePhaseExecutionContext(phase, executionContext);
if (executionContext !== "release") validateKeys.push("--pr-number");
requireCliValues(values, validateKeys);
const contract = receiptContractFromCli(values, phase, executionContext);
const archivePath = values.get("--archive") ?? "";
const stats = fs.lstatSync(archivePath);
if (
!stats.isFile() ||
stats.isSymbolicLink() ||
stats.size > QUALIFICATION_MAX_ARTIFACT_BYTES
) {
fail("qualification artifact must be a bounded regular non-link file");
}
const receipt = readQualificationReceiptArchive(
fs.readFileSync(archivePath),
contract,
expectationFromCli(values),
);
process.stdout.write(`${JSON.stringify(receipt)}\n`);
return;
}
if (command === "validate-archive") {
const validateKeys = [...common, "--archive", "--candidate-contract"];
const phase = validatePhase(values.get("--phase"));
const executionContext = validateExecutionContext(values.get("--execution-context"));
validatePhaseExecutionContext(phase, executionContext);
if (executionContext !== "release") validateKeys.push("--pr-number");
requireCliValues(values, validateKeys);
const contract = receiptContractFromCli(values, phase, executionContext);
const archivePath = values.get("--archive") ?? "";
if (!SAFE_TEXT_PATTERN.test(archivePath) || path.normalize(archivePath) !== archivePath) {
fail("qualification artifact path is invalid or non-canonical");
}
let stats: fs.Stats;
try {
stats = fs.lstatSync(archivePath);
} catch {
fail("qualification artifact is missing");
}
if (
!stats.isFile() ||
stats.isSymbolicLink() ||
stats.size > QUALIFICATION_MAX_ARTIFACT_BYTES
) {
fail("qualification artifact must be a bounded regular non-link file");
}
const receipt = readQualificationReceiptArchive(
fs.readFileSync(archivePath),
contract,
expectationFromCli(values),
);
process.stdout.write(`${JSON.stringify(receipt)}\n`);
return;
}
🤖 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 `@scripts/checks/openshell-qualification-contract.mts` around lines 674 - 698,
Update the validate-archive branch around archivePath and fs.lstatSync to
validate the archive path using the established readBoundedRegularFile
path-checking behavior, and wrap lstatSync failures so missing or non-canonical
paths go through fail with the controlled qualification artifact message instead
of exposing a raw error. Preserve the existing regular-file, symlink, and size
checks before reading the receipt.

Comment on lines +535 to +554
function validateArtifactUrl(value: unknown, label: string): string {
if (typeof value !== "string" || !SAFE_TEXT_PATTERN.test(value)) fail(`${label} is invalid`);
let url: URL;
try {
url = new URL(value);
} catch {
fail(`${label} is invalid`);
}
if (
url.protocol !== "https:" ||
url.port ||
url.username ||
url.password ||
url.search ||
url.hash
) {
fail(`${label} is not an immutable HTTPS artifact URL`);
}
return value;
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find code that reads or fetches QualificationArtifactProvenance.url.
set -euo pipefail

rg -n -C3 --type=ts --type=js -g '!**/node_modules/**' 'artifact\.url|artifacts\[[0-9]+\]\.url|\.url\b.*artifact' scripts .agents test || true
ast-grep run --pattern 'fetch($URL, $$$)' --lang typescript scripts || true

Repository: NVIDIA/NemoClaw

Length of output: 2043


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='https://raw.githubusercontent.com/NVIDIA/NemoClaw/main'
api='https://api.github.com/repos/NVIDIA/NemoClaw'

printf '%s\n' '--- repository metadata ---'
curl -fsSL "$api" | jq '{default_branch,archived,visibility}'

printf '%s\n' '--- target file references on default branch ---'
curl -fsSL "$base/scripts/checks/openshell-qualification-core.mts" |
  nl -ba | sed -n '500,575p;1215,1265p'

printf '%s\n' '--- repository tree paths ---'
curl -fsSL "$api/git/trees/$(curl -fsSL "$api" | jq -r .default_branch)?recursive=1" |
  jq -r '.tree[]?.path' |
  grep -E 'openshell-qualification|scripts/checks|test|spec' |
  head -200

Repository: NVIDIA/NemoClaw

Length of output: 2014


🌐 Web query:

NVIDIA NemoClaw openshell-qualification-core.mts validateArtifactUrl QualificationArtifactProvenance

💡 Result:

NVIDIA NemoClaw is an open-source reference stack designed to run AI agents within NVIDIA OpenShell sandboxes [1][2][3]. While the NemoClaw codebase contains logic for blueprint management, sandbox lifecycle, and OpenShell installation [4][5][6], there is no evidence of a core component, function, or file named openshell-qualification-core.mts, nor functions named validateArtifactUrl or QualificationArtifactProvenance within the official NVIDIA NemoClaw repository [1][6][7]. The terms you mentioned appear to be associated with disparate technical contexts rather than the NVIDIA NemoClaw project: 1. validateArtifactUrl: This function name appears in unrelated projects, such as blockchain/network-lumen [8] and platform-package-installer repositories [9], where it is used to validate URLs for software artifacts. 2. QualificationArtifactProvenance: This term follows naming conventions typically found in supply chain security, CI/CD, or artifact integrity frameworks (e.g., Tekton [10] or Rust-based artifact tools [11]), rather than the NemoClaw sandbox management stack [6]. NemoClaw manages agent execution by resolving and applying versioned YAML blueprints through the OpenShell CLI [6]. It prioritizes sandbox security, policy enforcement, and managed inference [6][3]. For authoritative documentation on NemoClaw, refer to the official NVIDIA documentation site at docs.nvidia.com/nemoclaw [1][3].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "openshell-qualification-core.mts"

💡 Result:

The term openshell-qualification-core.mts is associated with the NVIDIA NemoClaw repository, where it appears to be a TypeScript (MTS) file related to system testing, qualification, or compatibility validation [1]. While a specific, standalone documentation page for a file with that exact name does not appear in public index results, it follows the naming conventions found in the NemoClaw project's test and tooling directories [1][2][3]. In this codebase, the.mts extension is used for ECMAScript modules (TypeScript) often utilized in test suites, CI/CD pipelines, or automation tooling [1]. Given the context of other files in the same repository (e.g., tools/candidate-compat.mts, test/e2e-scenario/live/openshell-version-pin.test.ts), openshell-qualification-core.mts is likely a module responsible for core logic used to qualify or validate the OpenShell runtime environment against system requirements or specific blueprint configurations [1][4]. The NemoClaw project heavily uses such modules to enforce OpenShell version pinning, check for required driver binaries, and validate gateway-auth contracts [1][5][2][6][3].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "validateArtifactUrl"

💡 Result:

There is no standard or widely recognized function named validateArtifactUrl in common software libraries or web standards. In the context of the open-source NemoClaw repository (an NVIDIA-related project), similar naming conventions exist for security and configuration validation, such as: validateEndpointUrl: A function used to validate endpoint URLs for security (specifically to prevent Server-Side Request Forgery, or SSRF) by checking for private/internal IP addresses, ensuring allowed URL schemes, and using DNS pinning to prevent DNS rebinding attacks [1][2]. validateNvidiaApiKeyValue: A function used to validate API key formats [3]. validate (general): Various modules within the repository use validation functions to classify connection failures, check configurations against schemas, or normalize endpoint URLs (e.g., removing trailing slashes to ensure consistency) [4][5][6][7]. If you encountered validateArtifactUrl in a specific codebase, it is likely a custom, project-specific utility function designed to verify the integrity, existence, or security of a software artifact (such as a package or binary) at a given URL. You should check the documentation or source code of that specific project for its implementation details and purpose.

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "QualificationArtifactProvenance"

💡 Result:

QualificationArtifactProvenance is the idea/requirement that a “qualification artifact” (a bounded qualification result produced by code) must carry verifiable provenance: it should be produced by a specific, authenticated upstream source/run, include immutable identifiers (e.g., exact artifact digests / resolution IDs / output digests), and preserve a complete evidence chain from producer → materialized artifact → consumer verification so the artifact can be trusted and not substituted or injected. In NemoClaw’s supply-chain/qualification terminology, evidence/provenance is treated as part of the qualification contract: - A topology qualification artifact is a private, typed result with a versioned output schema, physical subject identity, and an output digest; consumers only accept it via an allowlisted materializer rather than trusting arbitrary files or presets. This effectively scopes what “provenance” must cover for that artifact (identity, schema version, digest, and code ownership of how it’s produced/consumed). [1] - The qualification workflow is framed as an evidence pipeline with explicit “producer run state, workflow path/event/attempt, credentials, checkout order,” and notes that base-owned verification workflows and provenance records are used so that a PR code change can’t poison verification tools and so artifacts from the wrong run aren’t accepted. [2] - Dependency/release ledger guidance similarly emphasizes recording upstream release state/ancestry and binding artifacts to producer repository/workflow/run/attempt/source SHAs, i.e., provenance is the recorded identity chain of upstream and downstream. [3] - The “contract audit” document also defines provenance categories for configuration, build/image content, and evidence pipeline, and it distinguishes immutable, digest-bound evidence from mutable or incomplete inputs (i.e., provenance is what makes evidence “strong,” not just a filename/metadata). [2] So, QualificationArtifactProvenance (as used in this line of work) means: the qualification artifact is bound to an exact producer identity (source code SHA(s), workflow path/event/attempt, producer run/attempt identifiers), and the artifact includes digest-verifiable output with recorded subject identity—allowing downstream systems to reject artifacts that can’t be proven to come from the expected trusted production and verification flow. [3][1][2] Note: “QualificationArtifactProvenance” itself is not found as a standalone, separately documented term in the provided sources; the sources instead define the underlying provenance requirements and evidence-chain mechanics for qualification artifacts. [3][1][2]

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "artifacts" "sha256" "url"

💡 Result:

In technical contexts, the combination of "artifacts," "SHA256," and "URL" refers to a common security practice for verifying the integrity and authenticity of downloaded files (artifacts) [1][2][3]. When software or binary files are distributed, they are typically hosted at a specific URL. Because URLs can point to tampered, corrupted, or outdated files, developers use SHA-256 (a cryptographic hash function) to ensure the file downloaded from a URL is exactly the same as the one intended by the publisher [1][3]. The verification process generally works as follows: 1. Identification: A manifest, configuration file, or build script contains both the authoritative source URL of the artifact and a pinned (hardcoded) SHA-256 hash string [1][3]. 2. Download: The system downloads the artifact from the specified URL [1][2]. 3. Calculation: The system calculates the SHA-256 hash of the downloaded file locally [1][3]. 4. Validation: The system compares the locally calculated hash against the pinned SHA-256 hash [1][3]. If the hashes match, the artifact is considered authentic and safe to use; if they do not match, the installation or deployment is typically aborted to prevent security risks [1][2][3]. This practice is frequently seen in Dockerfiles (e.g., pinning base images by digest), package management (e.g., lockfiles), and automated installation scripts [1][3][4]. For instance, instead of using a mutable tag like "latest," images are often pinned using their SHA-256 digest (e.g., image@sha256:...) to ensure the exact same image is used every time [3].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw/commit/a709a3538b3eff3292b9d1a863025c2374186879 "validateArtifactUrl"

💡 Result:

No exact match was found for validateArtifactUrl in commit a709a3538b3eff3292b9d1a863025c2374186879. The commit is titled “ci(deps): validate OpenShell candidate runtime (#6738)” and adds candidate compatibility tooling, but that identifier does not appear in its patch. (github.com)

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw/commit/a709a3538b3eff3292b9d1a863025c2374186879 "ARTIFACT_URL_HOSTS"

💡 Result:

No match found for ARTIFACT_URL_HOSTS in commit a709a3538b3eff3292b9d1a863025c2374186879. The commit is titled “ci(deps): validate OpenShell candidate runtime” and changes seven files. (github.com)

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "validateRunUrl" "validateApprovalUrl" "validateEvidenceUrl"

💡 Result:

The terms validateRunUrl, validateApprovalUrl, and validateEvidenceUrl are associated with the internal architecture of the NemoClaw framework (a project by NVIDIA) [1][2][3]. These specific identifiers are typically used in the context of system validation, endpoint protection, and maintaining the integrity of workflow operations [4][5][6]. While there is no single public "official document" defining these as a monolithic API, they function as part of the framework's internal validation mechanisms: 1. validateRunUrl: Often appears in contexts related to verifying the integrity or origin of workflow execution URLs [1][4]. It is used to ensure that URLs used for workflow run tracking or communication are safe and originate from trusted sources [4]. 2. validateApprovalUrl: Relates to the security of approval processes, specifically in scenarios where automated or manual approval workflows (like device approvals or scope upgrades) are triggered [7][8]. It ensures that the URL being interacted with for approval is legitimate and not susceptible to redirection or tampering [7]. 3. validateEvidenceUrl: Used in the context of verifying evidence artifacts produced by testing or validation gates [2][3]. It ensures that the evidence files (often JSON-based) being referenced or submitted are valid, properly typed, and meet the repository's strict security and consistency requirements [3]. These functions are part of a broader set of security controls within NemoClaw designed to prevent issues such as SSRF (Server-Side Request Forgery) and DNS rebinding attacks [4][5][6], particularly when dealing with external endpoints or sandbox interactions [9]. In the NemoClaw codebase, validation logic typically focuses on normalizing paths, sanitizing environment variables, and pinning DNS lookups to prevent Time-of-Check to Time-of-Use (TOCTOU) security flaws [5][6][8].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "artifacts.map" "sha256" "fetch"

💡 Result:

The term "artifacts.map" does not correspond to a standard technical command or globally recognized file format for SHA-256 fetching. In the context of software development environments like the NVIDIA NemoClaw repository, "artifacts" typically refers to local directories or objects managed by custom automation scripts [1][2][3][4]. Based on technical practices observed in these systems: 1. Artifact Management: Projects often use custom scripts (e.g., check-installer-hash.sh or update-docker-pin.sh) to verify integrity [5][6]. These scripts programmatically fetch remote assets using tools like curl and verify their integrity by comparing the downloaded file's SHA-256 hash against a hardcoded or pinned value [5][7][8]. 2. Mapping Artifacts: It is common for developers to create mapping files or internal objects (sometimes named artifacts.map or similar structures within source code like TypeScript) to track paths, URLs, and corresponding checksums for multiple assets [1][2]. 3. Fetching Logic: If you are looking for a way to fetch artifacts based on a map, the logic is typically implemented as: a. Loading the map (e.g., from a JSON or TypeScript configuration file) [1][3]. b. Iterating through the entries [5]. c. Using a fetch utility (e.g., curl, wget, or an SDK-specific function) to download the artifact [5][6]. d. Calculating the SHA-256 hash of the downloaded file using system tools such as sha256sum or shasum [5][7]. e. Validating that the calculated hash matches the expected value defined in your map [5][7][8]. If you are encountering this in a specific codebase, it is likely a project-specific helper script or configuration object rather than a standardized utility. You should inspect the directory containing the file for local READMEs or utility scripts that define the specific implementation of its "fetch" or "verify" logic [4].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw ".agents/skills" "QualificationArtifactProvenance" "artifact URL"

💡 Result:

The term QualificationArtifactProvenance does not refer to a standalone URL but is a conceptual identifier or metadata field used within the NVIDIA NemoClaw project's agent skill system [1][2][3]. In the context of NemoClaw, agent skills are managed within the.agents/skills/ directory, which serves as the internal source of truth for skill definitions [1][2]. To support signed, catalog-safe synchronization (for the NVIDIA NVSkills catalog), the project utilizes a deterministic export process [1][4]. 1. Source Management: Skills are maintained in.agents/skills/ [1][2]. 2. Provenance and Signing: Integrity and origin are maintained through artifacts such as skill.oms.sig (cryptographic signatures) and skill-card.md (metadata cards) [1][4]. 3. Export Process: The script scripts/export-catalog-skills.py is used to validate and copy allowlisted skills into an export directory (typically skills/nemoclaw/), ensuring that provenance artifacts are preserved and not overwritten during the generation process [1][4][5]. The provenance of an artifact—including its environment, build identity, and verification record—is essential for ensuring that only trusted, audit-verified code is executed [6][3][7]. Documentation within the project emphasizes that artifact metadata alone is insufficient; rather, the system relies on immutable, digest-verified blueprints and provenance records that bind an artifact to its production workflow and execution history [3][8][9].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "artifact provenance" "github.com" "objects.githubusercontent.com"

💡 Result:

The domain objects.githubusercontent.com is a GitHub-owned infrastructure endpoint used for serving static assets, including build artifacts, release attachments, and other binary objects associated with repositories [1][2][3]. When using GitHub Actions, build artifacts (such as those uploaded via actions/upload-artifact) are stored and subsequently retrieved from GitHub's internal storage infrastructure [4][5]. While actions/upload-artifact abstracts the underlying storage mechanism, requests to download these artifacts or access binary release assets often transit through or are served from *.githubusercontent.com domains [6][7]. Key technical aspects include: 1. Role in Provenance: Because this domain serves binary objects and artifacts, network policies in restricted environments (such as sandboxes or hardened CI runners) must explicitly whitelist objects.githubusercontent.com to allow the retrieval of artifacts necessary for build verification, provenance checks, and dependency audits [1][6][8]. 2. Security and Access: Traffic to this domain is typically served over HTTPS [1]. In secure environments, it is critical to ensure TLS verification is enforced for connections to this host to maintain the integrity of the downloaded artifacts [8]. 3. Usage Context: It functions alongside other GitHub-hosted endpoints like raw.githubusercontent.com (for raw file content) and ghcr.io (for container registry images) as part of the broader GitHub ecosystem [1][2][3]. In the context of artifact provenance, the integrity of assets retrieved from this domain relies on GitHub's internal infrastructure and the specific authentication or signing mechanisms (such as release signatures or checksum verification) implemented by the repository workflows themselves [9].

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "immutable HTTPS artifact URL"

💡 Result:

An immutable artifact URL is a reference to a software asset—such as a container image, package, or binary—that points to a specific, unchangeable version of that content, typically verified by a cryptographic hash [1][2][3]. Unlike mutable references (e.g., tags like :latest or version aliases), which can be overwritten at the source to point to different data, immutable URLs ensure that the artifact retrieved today is identical to the one retrieved in the future [1][4][3]. Key characteristics and implementation practices for immutable artifact URLs include: 1. Cryptographic Digest Pinning: For OCI-compliant container images, this involves using the image's SHA-256 digest (e.g., repository/image@sha256:<64-character-hash>) instead of a mutable tag [1][4][3]. This ensures the container runtime fetches the exact bits associated with that digest, preventing malicious injection or silent dependency swaps if the original registry entry is force-pushed or compromised [1][3]. 2. Content-Addressable References: Beyond container images, best practices dictate recording an immutable content identity for any software artifact, including file SHA-256 sums for downloaded binaries, packages, or models [5][2]. 3. Verification Workflows: Artifact metadata alone is often insufficient [2]. Immutable workflows typically include: - Auditing the artifact using a trusted verifier [2]. - Binding the recorded immutable identity (the hash) to the specific artifact intended for use, rather than relying on a path or alias that might be swapped [2]. - Rejecting any artifact that does not match the pre-verified digest [1][2]. 4. Production vs. Development: While mutable tags are sometimes used in development environments for ease of use (allowing users to always pull the latest version without manual updates), production systems should strictly mandate digest-pinned references to guarantee stability and security [4][3]. Automated tools, such as Renovate or Dependabot, are frequently used to help manage the lifecycle of these pins, ensuring they are updated when new, trusted versions are released [3]. Top results: [1], [4], [2], [3]

Citations:


🌐 Web query:

site:github.com/NVIDIA/NemoClaw "sha256" "QualificationArtifact"

💡 Result:

A QualificationArtifact is a private, typed result used within the NVIDIA NemoClaw ecosystem to validate security-sensitive contracts or bounded multi-node configurations [1]. It serves as a verifiable output for system qualifications, such as ensuring that specific hardware topologies (e.g., dual-node fabrics) meet defined requirements [1]. In this context, the sha256 prefix associated with a QualificationArtifact refers to the cryptographic digest used to ensure the integrity and identity of the artifact's output [1]. Specifically: 1. Identification and Integrity: Each QualificationArtifact includes an outputDigest (prefixed with sha256:) that provides a unique, immutable fingerprint of the validated result [1]. 2. Verification: The system uses these sha256 digests to ensure that the qualification result has not been tampered with and that it corresponds to the specific code-owned schema version and physical subject identity it claims to represent [1]. 3. Security: Because these artifacts may contain sensitive information or security-sensitive contract validations, they are restricted; only allowlisted materializers can consume the typed output associated with a qualification identifier [1]. More broadly, within NemoClaw, sha256 digests are a standard mechanism for binding components (such as sandbox images, binary releases, and blueprint configurations) to their exact, immutable versions to prevent supply-chain vulnerabilities [2][3][4]. While QualificationArtifacts are a specific type of evidence, they adhere to the project's general security requirement that all critical system state and components must be verified against their expected sha256 hashes before execution [5][6][7].

Citations:


Enforce the artifact host policy

validateArtifactUrl accepts any HTTPS hostname. If artifacts must use GitHub-hosted sources, reject hosts outside the contract’s approved allowlist. Add tests for approved and arbitrary HTTPS hosts. A sha256 value does not protect consumers that fetch the URL without verifying the digest.

🤖 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 `@scripts/checks/openshell-qualification-core.mts` around lines 535 - 554,
Update validateArtifactUrl to enforce the contract’s approved GitHub-host
allowlist after parsing the URL, rejecting arbitrary HTTPS hosts while
preserving the existing immutability checks. Add validation tests covering
accepted allowlisted hosts and rejection of non-approved HTTPS hosts.

Comment on lines +1050 to +1077
tests.push({
id: descriptor.id,
result: "success",
runs: [
{
authorityPaths: sourceReceipt.authorityPaths,
baseSha: sourceReceipt.baseSha,
candidateSha: sourceReceipt.candidateSha,
cells: sourceReceipt.tests[0]?.cells ?? [],
controllerSha: sourceReceipt.controllerSha,
event: sourceReceipt.event,
executionContext: sourceReceipt.executionContext,
jobs: sourceReceipt.tests[0]?.jobs ?? [],
openshellCommitSha: sourceReceipt.openshellCommitSha,
openshellVersion: sourceReceipt.openshellVersion,
phase: sourceReceipt.phase,
prNumber: sourceReceipt.prNumber,
requiredCases: sourceReceipt.tests[0]?.requiredCases ?? [],
requiredDimensions: sourceReceipt.tests[0]?.requiredDimensions ?? [],
result: sourceReceipt.result,
runAttempt: sourceReceipt.runAttempt,
runId: sourceReceipt.runId,
runUrl: sourceReceipt.runUrl,
workflowId: sourceReceipt.workflowId,
workflowPath: sourceReceipt.workflowPath,
},
],
});

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

The ?? [] fallbacks can hide missing source evidence.

validateQualificationSourceReceipt always returns exactly one entry in tests (Line 870). If that invariant ever breaks, sourceReceipt.tests[0]?.cells ?? [] produces an empty cell list, an empty job list, and empty required-case lists instead of a failure. For an empty matrix the resulting receipt still validates, so the gate reports success without cell evidence.

Read the single test once and fail when it is absent.

🛡️ Proposed fix
+    const sourceTest = sourceReceipt.tests[0];
+    if (!sourceTest) fail(`qualification source receipt for ${descriptor.id} has no test evidence`);
     selectedRuns.push({ descriptor, run });
     tests.push({
       id: descriptor.id,
       result: "success",
       runs: [
         {
           authorityPaths: sourceReceipt.authorityPaths,
           baseSha: sourceReceipt.baseSha,
           candidateSha: sourceReceipt.candidateSha,
-          cells: sourceReceipt.tests[0]?.cells ?? [],
+          cells: sourceTest.cells,
           controllerSha: sourceReceipt.controllerSha,
           event: sourceReceipt.event,
           executionContext: sourceReceipt.executionContext,
-          jobs: sourceReceipt.tests[0]?.jobs ?? [],
+          jobs: sourceTest.jobs,
           openshellCommitSha: sourceReceipt.openshellCommitSha,
           openshellVersion: sourceReceipt.openshellVersion,
           phase: sourceReceipt.phase,
           prNumber: sourceReceipt.prNumber,
-          requiredCases: sourceReceipt.tests[0]?.requiredCases ?? [],
-          requiredDimensions: sourceReceipt.tests[0]?.requiredDimensions ?? [],
+          requiredCases: sourceTest.requiredCases,
+          requiredDimensions: sourceTest.requiredDimensions,
📝 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
tests.push({
id: descriptor.id,
result: "success",
runs: [
{
authorityPaths: sourceReceipt.authorityPaths,
baseSha: sourceReceipt.baseSha,
candidateSha: sourceReceipt.candidateSha,
cells: sourceReceipt.tests[0]?.cells ?? [],
controllerSha: sourceReceipt.controllerSha,
event: sourceReceipt.event,
executionContext: sourceReceipt.executionContext,
jobs: sourceReceipt.tests[0]?.jobs ?? [],
openshellCommitSha: sourceReceipt.openshellCommitSha,
openshellVersion: sourceReceipt.openshellVersion,
phase: sourceReceipt.phase,
prNumber: sourceReceipt.prNumber,
requiredCases: sourceReceipt.tests[0]?.requiredCases ?? [],
requiredDimensions: sourceReceipt.tests[0]?.requiredDimensions ?? [],
result: sourceReceipt.result,
runAttempt: sourceReceipt.runAttempt,
runId: sourceReceipt.runId,
runUrl: sourceReceipt.runUrl,
workflowId: sourceReceipt.workflowId,
workflowPath: sourceReceipt.workflowPath,
},
],
});
const sourceTest = sourceReceipt.tests[0];
if (!sourceTest) fail(`qualification source receipt for ${descriptor.id} has no test evidence`);
tests.push({
id: descriptor.id,
result: "success",
runs: [
{
authorityPaths: sourceReceipt.authorityPaths,
baseSha: sourceReceipt.baseSha,
candidateSha: sourceReceipt.candidateSha,
cells: sourceTest.cells,
controllerSha: sourceReceipt.controllerSha,
event: sourceReceipt.event,
executionContext: sourceReceipt.executionContext,
jobs: sourceTest.jobs,
openshellCommitSha: sourceReceipt.openshellCommitSha,
openshellVersion: sourceReceipt.openshellVersion,
phase: sourceReceipt.phase,
prNumber: sourceReceipt.prNumber,
requiredCases: sourceTest.requiredCases,
requiredDimensions: sourceTest.requiredDimensions,
result: sourceReceipt.result,
runAttempt: sourceReceipt.runAttempt,
runId: sourceReceipt.runId,
runUrl: sourceReceipt.runUrl,
workflowId: sourceReceipt.workflowId,
workflowPath: sourceReceipt.workflowPath,
},
],
});
🤖 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 `@scripts/checks/openshell-qualification-github.mts` around lines 1050 - 1077,
Update the receipt construction in the tests.push block to read
sourceReceipt.tests[0] once and require it to exist before creating the run.
Replace the optional accesses and ?? [] fallbacks for cells, jobs,
requiredCases, and requiredDimensions with fields from that validated test
entry, and fail immediately when the entry is absent so a success receipt cannot
be produced without evidence.

Comment thread test/openshell-qualification-contract.test.ts
Comment on lines +182 to +184
const draft = validateQualificationContract(
JSON.parse(fs.readFileSync("ci/openshell-0.0.101-qualification-v1.json", "utf8")),
);

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

Resolve the contract path from the module directory.

fs.readFileSync("ci/openshell-0.0.101-qualification-v1.json", "utf8") depends on the process working directory. test/openshell-qualification-contract.test.ts Line 80 resolves the same contract with path.join(import.meta.dirname, "..", QUALIFICATION_CONTRACT_PATH). Use the same resolution here so the test does not break when the runner working directory changes.

♻️ Proposed fix
     const draft = validateQualificationContract(
-      JSON.parse(fs.readFileSync("ci/openshell-0.0.101-qualification-v1.json", "utf8")),
+      JSON.parse(
+        fs.readFileSync(
+          path.join(import.meta.dirname, "..", "ci/openshell-0.0.101-qualification-v1.json"),
+          "utf8",
+        ),
+      ),
     );
📝 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
const draft = validateQualificationContract(
JSON.parse(fs.readFileSync("ci/openshell-0.0.101-qualification-v1.json", "utf8")),
);
const draft = validateQualificationContract(
JSON.parse(
fs.readFileSync(
path.join(import.meta.dirname, "..", "ci/openshell-0.0.101-qualification-v1.json"),
"utf8",
),
),
);
🤖 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 `@test/openshell-qualification-pr-gate.test.ts` around lines 182 - 184, Update
the contract read in the qualification test around validateQualificationContract
to resolve the file from import.meta.dirname using
path.join(import.meta.dirname, "..", QUALIFICATION_CONTRACT_PATH), matching
openshell-qualification-contract.test.ts. Keep the existing UTF-8 read and
validation flow unchanged, and avoid relying on the process working directory.

Comment on lines +34 to +43
const QUALIFICATION_RUNTIME_AUTHORITY_PATHS = [
"ci/openshell-0.0.101-qualification-v1.json",
"scripts/checks/openshell-qualification-contract.mts",
"scripts/checks/openshell-qualification-core.mts",
"scripts/checks/openshell-qualification-github.mts",
"scripts/checks/openshell-qualification-io.mts",
"scripts/checks/openshell-qualification-matrix.mts",
"scripts/checks/openshell-qualification-schema.mts",
"scripts/scorecard/read-artifact-zip.mts",
] as const;

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

Derive the runtime authority list from the imported canonical constant.

QUALIFICATION_RUNTIME_AUTHORITY_PATHS restates the same eight paths that QUALIFICATION_FROZEN_AUTHORITY_PATHS already provides at Line 19. Lines 1215-1264 restate them a third time with labels. If production adds or renames an authority path, these two local copies drift silently, and the tests keep passing while covering a stale inventory. Build the labelled table from the imported constant, and keep only the label mapping local.

Based on path instructions: "Derive inventories and limits from a canonical source where possible; flag duplicated lists that can silently drift."

🤖 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 `@test/release-qualification-gate.test.ts` around lines 34 - 43, Remove the
duplicated path array QUALIFICATION_RUNTIME_AUTHORITY_PATHS and derive the
runtime authority inventory from the imported
QUALIFICATION_FROZEN_AUTHORITY_PATHS constant. Update the labelled table around
the qualification authority checks to retain only local label mappings while
iterating over the canonical paths, preserving the existing labels and test
behavior.

Source: Path instructions

Comment thread test/release-qualification-gate.test.ts
Comment thread test/release-qualification-gate.test.ts
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>

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

🧹 Nitpick comments (2)
test/helpers/openshell-qualification-contract-test-support.ts (1)

50-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unused ancestor property from finalArtifactApi's overrides type. finalArtifactApi never reads it. The non-ancestor test passes ancestor to retirementAuthenticationFixture, where it is used.

🤖 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 `@test/helpers/openshell-qualification-contract-test-support.ts` around lines
50 - 59, Remove the unused ancestor property from the overrides type in
finalArtifactApi, while preserving the ancestor argument used by
retirementAuthenticationFixture in the non-ancestor test.
test/openshell-qualification-producer-workflow-boundary.test.ts (1)

11-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the .js extension from the helper import.

The helper file is TypeScript, and the sibling test uses extensionless helper imports.

🤖 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 `@test/openshell-qualification-producer-workflow-boundary.test.ts` around lines
11 - 16, Update the helper import used by the qualification producer workflow
test to remove the “.js” extension, matching the extensionless import style used
by the sibling test.
🤖 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.

Nitpick comments:
In `@test/helpers/openshell-qualification-contract-test-support.ts`:
- Around line 50-59: Remove the unused ancestor property from the overrides type
in finalArtifactApi, while preserving the ancestor argument used by
retirementAuthenticationFixture in the non-ancestor test.

In `@test/openshell-qualification-producer-workflow-boundary.test.ts`:
- Around line 11-16: Update the helper import used by the qualification producer
workflow test to remove the “.js” extension, matching the extensionless import
style used by the sibling test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 22ad9ff0-9cc3-405e-af73-b7c08093335c

📥 Commits

Reviewing files that changed from the base of the PR and between 6d30e49 and ae5f1bd.

📒 Files selected for processing (9)
  • scripts/release-cut-tag.sh
  • test/helpers/openshell-qualification-contract-test-support.ts
  • test/helpers/openshell-qualification-producer-workflow-test-support.ts
  • test/helpers/release-qualification-gate-fixture.ts
  • test/openshell-qualification-contract.test.ts
  • test/openshell-qualification-pr-gate.test.ts
  • test/openshell-qualification-producer-workflow-boundary.test.ts
  • test/release-qualification-gate.test.ts
  • test/skills/triage-runtime.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/release-qualification-gate.test.ts

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.

Define OpenShell v0.0.101 qualification receipts and gates

2 participants