diff --git a/.agents/skills/build-from-issue/SKILL.md b/.agents/skills/build-from-issue/SKILL.md index 06d1324b02..d2c7ec07ea 100644 --- a/.agents/skills/build-from-issue/SKILL.md +++ b/.agents/skills/build-from-issue/SKILL.md @@ -1,745 +1,34 @@ --- name: build-from-issue -description: Given a GitHub issue number, plan and implement the work described in the issue. Supports direct user requests and unattended queue processing through the `agent:*` workflow labels. Includes tests, documentation updates, and PR creation. Trigger keywords - build from issue, implement issue, work on issue, build issue, start issue. +description: Plan and implement work described in a GitHub issue, including verification, documentation, and a PR that closes the issue. +metadata: + internal: true --- # Build From Issue -Plan, iterate on feedback, and implement work described in a GitHub issue. +Use a specific GitHub issue to plan and implement a scoped change. Direct user instructions authorize the phase requested. Planning alone does not authorize implementation. For unattended work, inspect the current `state:*` label descriptions, maintainer assignments, and issue comments to infer the authorized phase. Proceed only when those records authorize the phase; do not assume every state permits implementation. -This skill operates as a stateful workflow — it can be run repeatedly against the same issue. Each invocation inspects the issue's labels, plan comment, and conversation history to determine the correct next action. +## Inspect the issue -## Prerequisites +1. Run `gh issue view --json number,title,body,state,labels,comments,assignees` and follow Label Discovery in `CONTRIBUTING.md` to retrieve every page of current `state:*` label definitions and resolve unclear meanings. Infer whether triage, validation, and human acceptance have happened. Do not hard-code label names or change disposition as part of building. +2. Read the issue, comments, linked PRs, and current code. Check for an active owner or implementation. If the issue concerns a vulnerability, use `review-security-issue` and `fix-security-issue` instead. +3. Confirm that the User Story attests to the human operator's first-hand OpenShell use and gives a specific use case. If the issue lacks this, ask the operator before proceeding with planning or implementation. For a bug, require reproduction using only an OpenShell deployment; do not install third-party tools solely to demonstrate the problem. +4. If the user's direct request starts before the normal issue disposition, briefly report the discrepancy and continue with the authorized phase. Stop only when information needed to do the work is actually unavailable or a conflicting owner needs resolution. -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote +## Plan -## Invocation and Authorization +Identify the user-visible outcome, affected code, alternatives, tests, and documentation. For configuration, CLI, SDK, or other UX changes, include notional commands, configuration, or API examples so a human can review the proposed interaction. Consider existing extensibility points such as middleware, interceptors, and providers. Prefer an applicable extension when it satisfies the use case; the need to run another service alone does not disqualify it. -This skill supports two invocation modes: +Use a single issue comment beginning with `> **🏗️ build-plan**` when a plan should be recorded on GitHub. On later invocations, read that comment and any newer human feedback before taking action. Respond to unanswered feedback with a comment beginning `> **🏗️ build-from-issue-agent**`; update the existing plan comment in place when the design changes. Do not repeat a completed plan or open a second PR. Distinguish technical findings from product decisions. If the user requested only a plan, stop after the plan is available for review. -- **Direct mode:** A user explicitly asks the agent to plan or implement a specific issue. The request itself authorizes the requested phase; the corresponding `agent:*` request label is not required. -- **Queue mode:** An always-on or unattended agent scans for work without a live user directing it to a specific issue. In this mode, `agent:plan-requested` authorizes planning and `agent:implementation-requested` authorizes implementation. +## Implement -A direct request authorizes only what it says. A request to review or plan does not authorize implementation. A request to build, implement, or work on an issue authorizes both the planning needed to perform the work and implementation unless the user asks to stop after planning. +1. Check the current branch and working tree. Preserve unrelated work. Create an issue-specific branch or worktree as needed, following Branch Names in `CONTRIBUTING.md`. +2. Implement the smallest coherent change that fulfills the acceptance criteria. Update relevant skills when behavior or commands change. Keep published documentation minimal: explain exactly what users need, avoid duplication across pages, and omit internal details with no user impact. +3. Add meaningful tests for changed behavior and follow the verification guidance in `CONTRIBUTING.md`. Select format, lint, compile or type checks, and tests for affected components and their dependencies. Run the relevant E2E lane for infrastructure, sandbox, or policy changes. Guidance and template edits need applicable Markdown, YAML, link, and consistency checks. Do not require full Rust, SDK, or repository CI solely because a commit or PR is being created; broaden checks only for a concrete remaining risk or failed check. +4. Review the diff, use a signed-off Conventional Commit, and prepare a PR following `create-github-pr`. -The two request labels remain human-only queue controls. Under **no circumstances** should this skill or any agent apply them, ask to apply them, or suggest automating their application. +Every PR from this issue-backed workflow must have its own existing issue and use `Closes #` in its Related Issue section. For work needing multiple PRs, split the scope into a closable issue per PR. A high-level issue may track those issues but should not be closed by an incomplete PR. Report the implementation, verification, and any remaining limitation in the PR description, rather than copying earlier issue diagnostics. -Do not refuse a direct user request merely because its request label is absent. If direct work begins on an issue that was not already in the label-driven workflow, do not introduce `agent:in-progress` or `agent:pr-opened` solely for that invocation. If a matching request label is present, preserve the existing label transitions so unattended agents can track the workflow. - -## Agent Comment Markers - -This skill uses two distinct markers to identify its comments: - -### Plan marker - -The implementation plan lives in a **single comment** that is edited in place as the plan evolves. It is identified by this marker on its first line: - -``` -> **🏗️ build-plan** -``` - -### Conversation marker - -All other comments (responses to human feedback, status updates, PR announcements) use this marker: - -``` -> **🏗️ build-from-issue-agent** -``` - -These markers distinguish agent comments from human comments and from other skills (e.g., `🔒 security-review-agent`, `🔧 security-fix-agent`). - -## State Machine Overview - -Each invocation follows this decision tree: - -``` -Fetch issue + comments - │ - ├─ topic:security present? - │ → Route to review-security-issue or fix-security-issue; STOP - │ - ├─ Triage incomplete, awaiting information, or awaiting human disposition? - │ → Report the blocking state and STOP - │ - ├─ state:accepted and roadmap association both absent? - │ → Human has not accepted the issue; STOP - │ - ├─ No plan comment and no direct planning request and agent:plan-requested absent? - │ → No request for agent planning; STOP - │ - ├─ No plan comment + direct planning request or agent:plan-requested present? - │ → Generate plan via principal-engineer-reviewer - │ → Post plan comment - │ → Advance labels only for a label-driven invocation - │ → Continue if the direct request also authorized implementation; otherwise STOP - │ - ├─ Plan exists + new human comments since last agent response? - │ → Respond to each comment (quote context, address feedback) - │ → Update the plan comment if feedback requires plan changes - │ → STOP - │ - ├─ Plan exists + direct implementation request or 'agent:implementation-requested' label? - │ → Run scope check (warn if high complexity) - │ → Check for conflicting branches/PRs - │ → BUILD (Steps 6–14) - │ - ├─ 'agent:in-progress' label present? - │ → Detect existing branch and resume if possible - │ → Otherwise report current state - │ - ├─ 'agent:pr-opened' label present? - │ → Report that PR already exists, link to it - │ → STOP - │ - └─ Plan exists + no new comments + neither a direct implementation request nor 'agent:implementation-requested'? - → Report: "Plan is posted and awaiting review. No new comments to address." - → STOP -``` - -## Step 1: Fetch the Issue - -The user provides an issue ID (e.g., `#42` or `42`). Strip any leading `#` and fetch: - -```bash -gh issue view --json number,title,body,state,labels,author -``` - -If the issue is closed, report that and stop. - -If `topic:security` is present, stop. General build agents must not plan or implement security issues. Route planning/review to `review-security-issue` and authorized remediation to `fix-security-issue`. - -Stop before planning in any of these states: - -- `state:triage-needed`: the issue has not been assessed; use `triage-issue`. -- `state:needs-info`: triage is waiting for evidence from the reporter. -- `state:validated` without roadmap placement: triage is complete, but a human has not yet decided whether OpenShell should invest in the work. - -Next, require a human acceptance signal: either `state:accepted` or placement on the roadmap. The label records acceptance without requiring scheduling; roadmap placement records acceptance and sequencing. If no plan exists, require either a direct user request for planning or the human-applied `agent:plan-requested` label before generating one. Never add or remove `state:accepted`, either human request label, or the `roadmap` label. - -## Step 2: Fetch and Classify Comments - -Fetch all comments: - -```bash -gh issue view --json comments --jq '.comments[] | {id: .id, body: .body, author: .author.login, createdAt: .createdAt, updatedAt: .updatedAt}' -``` - -Classify each comment into one of: - -- **Plan comment**: body starts with `> **🏗️ build-plan**` -- **Agent comment**: body starts with `> **🏗️ build-from-issue-agent**` -- **Human comment**: everything else (not agent-marked) - -Record the plan comment's `id` (needed for editing via API) and its `updatedAt` timestamp. - -## Step 3: Determine Action - -Using the state machine above, determine what to do based on: - -1. Whether a plan comment exists -2. Whether there are human comments newer than the last agent comment (plan or conversation) -3. Whether this is direct mode and which phase the user requested -4. Which disposition, roadmap, and agent-workflow labels are present (`state:accepted`, `agent:plan-requested`, `agent:plan-ready`, `agent:implementation-requested`, `agent:in-progress`, `agent:pr-opened`, and the `roadmap` label) - -Follow the appropriate branch below. - ---- - -## Branch A: Generate the Plan - -If no plan comment exists, generate one when the user directly requested planning or implementation, or when `agent:plan-requested` is present. Otherwise report that no one has requested agent planning and stop. - -### A1: Analyze the Issue with Principal Engineer Reviewer - -Pass the issue title, description, labels, and any relevant code references to the `principal-engineer-reviewer` sub-agent. Use the Task tool: - -``` -Task tool with subagent_type="principal-engineer-reviewer" -``` - -In the prompt, instruct the reviewer to: - -1. Read the issue's user story and identify what needs to change in the codebase. Treat reporter diagnostics or solution ideas as optional context, not as authoritative or current analysis. -2. Map the requirements to existing code — read the relevant source files. -3. Determine the **issue type** — one of: `feat` (new feature), `fix` (bug fix), `refactor`, `chore`, `perf`, `docs`. -4. Propose the minimal set of changes that satisfies the requirements. -5. Sequence the work so each step is independently testable. -6. Identify what tests are needed (unit, integration, e2e) and where they should live. -7. Assess **complexity** on a scale: - - **Low**: Isolated change, < 3 files, clear path forward - - **Medium**: Multiple files/components, some design decisions, but well-scoped - - **High**: Cross-cutting changes, architectural decisions needed, significant unknowns -8. Call out risks, unknowns, and decisions that need stakeholder input. -9. Assess **gateway config documentation impact** — if the change adds, removes, renames, or changes defaults for gateway TOML keys or driver-specific config options, the plan must include an update to `docs/reference/gateway-config.mdx`. If the change is surfaced through Helm or a compute-driver overview, also include `docs/reference/sandbox-compute-drivers.mdx` or the relevant deployment docs. -10. Assess **LSM compatibility** — if the change touches process identity, `/proc` filesystem access, binary execution, or inter-process visibility, flag whether it will behave differently on hosts running SELinux (enforcing) or AppArmor. In particular, tests that fork+exec into system binaries will fail on SELinux-enforcing hosts due to cross-label `/proc//exe` access restrictions. - -Perform this investigation against the current branch and current product behavior. If the issue contains earlier diagnostics, verify them rather than relying on them. - -### A2: Post the Plan Comment - -Post the plan as a comment on the issue. This is the **canonical plan comment** that will be edited in place as the plan evolves. - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🏗️ build-plan** - -## Implementation Plan - -**Issue type:** `` -**Complexity:** -**Confidence:** - -### Summary -<2-3 sentences describing what will be built/changed and the approach> - -### Scope -- ``: -- ``: -- ... - -### Implementation Steps -1. -2. -3. ... - -### Test Plan -- **Unit tests:** -- **Integration tests:** -- **E2E tests:** - -### Risks & Open Questions -- - -### Documentation Impact -- - ---- -*Revision 1 — initial plan* -EOF -)" -``` - -### A3: Mark the Plan Ready in Queue Mode - -If `agent:plan-requested` was present, replace it with `agent:plan-ready`. Do not add `agent:plan-ready` for a direct invocation that was not already using the label workflow. - -```bash -gh issue edit --remove-label "agent:plan-requested" --add-label "agent:plan-ready" -``` - -If the direct request authorized implementation, continue to Branch C. Otherwise report that the plan has been posted and stop. In queue mode, a human reviews the plan and applies `agent:implementation-requested` before an unattended agent can build. - ---- - -## Branch B: Respond to Feedback - -If a plan exists and there are human comments newer than the last agent response, address them. - -### B1: Process Each Unanswered Human Comment - -For each human comment that is newer than the most recent agent comment (plan `updatedAt` or conversation comment `createdAt`): - -1. Read the comment. -2. Quote the relevant portion using `>` blockquote syntax. -3. Formulate a response based on the codebase and the current plan. -4. Post a response with the conversation marker. - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -> - - -EOF -)" -``` - -### B2: Update the Plan if Needed - -If any feedback requires changes to the plan, **edit the existing plan comment** rather than posting a new one. Use the GitHub API with the comment's node ID: - -```bash -gh api graphql -f query=' - mutation { - updateIssueComment(input: {id: "", body: ""}) { - issueComment { id } - } - } -' -``` - -Or use the REST API: - -```bash -gh api repos/{owner}/{repo}/issues/comments/ -X PATCH -f body="$(cat <<'EOF' -> **🏗️ build-plan** - -## Implementation Plan - -<... updated plan content ...> - ---- -*Revision — * -*Revision — * -*Revision 1 — initial plan* -EOF -)" -``` - -Preserve the full revision history at the bottom so readers can track how the plan evolved. - -Report to the user what feedback was addressed and whether the plan was updated. Stop. - ---- - -## Branch C: Build - -Proceed with implementation when the plan exists and either the user directly requested implementation or `agent:implementation-requested` is present. An existing `agent:in-progress` or `agent:pr-opened` label still triggers the resume or existing-PR checks below. - -### Step 4: Scope Check - -Read the plan comment and check the **Complexity** and **Confidence** fields. - -- **If Complexity is High or Confidence is Low**, warn the user: - - > "This issue is rated High complexity / Low confidence. The plan includes open questions that may need human decisions during implementation. Proceeding, but flagging this for your awareness." - - Continue — do not hard-stop. The user directly requested implementation or chose to apply `agent:implementation-requested`. - -### Step 5: Conflict Detection - -Before creating a branch, check for conflicts: - -#### Check for existing branches - -```bash -git fetch origin -git branch -r | grep -i "" -``` - -If a remote branch referencing this issue ID exists, report it and ask the user whether to continue on that branch or abort. - -#### Check for existing PRs - -```bash -gh pr list --state open --search "Closes #" --json number,title,url -``` - -If an open PR already references this issue, report it and stop. Do not create a competing PR. - -### Step 6: Create Branch - -Determine the branch prefix from the issue type in the plan: - -| Issue type | Branch prefix | -| --- | --- | -| `feat` | `feat/` | -| `fix` | `fix/` | -| `refactor` | `refactor/` | -| `chore` | `chore/` | -| `perf` | `perf/` | -| `docs` | `docs/` | - -Get the current username and create the branch: - -```bash -USERNAME=$(gh api user --jq '.login') -git checkout main -git pull origin main -git checkout -b -/$USERNAME -``` - -### Step 7: Mark Queue Work In Progress - -If `agent:implementation-requested` is present, replace it and `agent:plan-ready` with `agent:in-progress`. In direct mode without a request label, do not add an agent-workflow label. - -```bash -gh issue edit --remove-label "agent:implementation-requested" --remove-label "agent:plan-ready" --add-label "agent:in-progress" -``` - -### Step 8: Implement the Changes - -Follow the implementation steps from the plan. Principles: - -- **Follow the plan**: The plan was reviewed and approved. Stick to it unless you discover something that requires deviation. -- **Minimal scope**: Only change what the plan calls for. No unrelated refactors. -- **If you must deviate**: Note the deviation — it will be included in the PR description. - -Read the relevant source files before making changes. Implement step by step per the plan's sequence. - -### Step 9: Write Tests - -Write tests as specified in the plan's Test Plan section. Follow the project's existing test conventions. - -#### Unit tests - -- Place alongside existing tests for the module (e.g., `#[cfg(test)]` blocks in Rust, `test_*.py` for Python) -- Cover the new/changed behavior, edge cases, and error paths -- Ensure pre-existing behavior still works - -#### Integration tests - -- Place in the project's existing integration test directories -- Cover interactions between the changed components -- Test realistic scenarios including error conditions - -#### E2E tests - -- Only if the plan calls for them -- Cover the full user-facing workflow affected by the change - -#### Test naming - -Use descriptive names that document intent: -- `test_pagination_returns_correct_page_count` -- `test_rejects_negative_offset_parameter` -- `test_retry_succeeds_after_transient_failure` - -### Step 10: Verify — Tests, Lint, Pre-commit (Retry Loop) - -Verification has two phases: unit tests + pre-commit, then E2E tests (if applicable). Run with up to **3 attempts per phase**. - -#### Phase 1: Unit Tests and Pre-commit - -On each attempt: - -```bash -# Run pre-commit checks (linting, formatting, license headers) -mise run pre-commit -``` - -**If verification fails:** - -1. Read the error output carefully. -2. Fix the issues (test failures, lint errors, formatting). -3. Decrement the retry counter and try again. - -**If all 3 attempts fail**, stop and report to the user: -- What passed and what failed -- The specific errors from the last attempt -- That manual intervention is needed - -Do not proceed to Phase 2 or PR creation if Phase 1 is not green. - -#### Phase 2: E2E Tests (Conditional) - -**Trigger**: Run this phase if any files under `e2e/` were added or modified in this build. Check with: - -```bash -git diff --name-only main -- e2e/ -``` - -If there are no changes under `e2e/`, skip this phase entirely. - -If E2E files were modified, run the relevant E2E lane for the driver touched by the change: - -```bash -# Docker-backed gateway smoke E2E -mise run e2e:docker -``` - -Use `mise run e2e:podman`, `mise run e2e:vm`, or a Helm-backed Kubernetes E2E lane when the change targets those drivers. - -**E2E retry loop** (up to 3 attempts): - -1. Run the selected E2E lane. -2. If tests fail: - - Read the pytest output carefully — identify which tests failed and why. - - Distinguish between **test bugs** (the test itself is wrong) and **implementation bugs** (the code under test is wrong). - - Fix the failing code or tests. - - Decrement the retry counter and try again. -3. If tests pass, Phase 2 is green. - -**If all 3 E2E attempts fail**, stop and report to the user: -- Which E2E tests are failing -- The pytest output from the last attempt -- Whether the failures appear to be test issues or implementation issues -- That manual intervention is needed - -Do not proceed to PR creation if E2E verification is not green. - -### Step 11: Update Documentation - -Review the documentation requirements in `AGENTS.md` and update any affected -docs as part of the implementation. Keep documentation changes scoped to the -behavior or subsystem that changed. - -If the implementation changes gateway TOML parsing, `[openshell.gateway]` -fields, `[openshell.drivers.]` fields, driver config defaults, or Helm -rendering of `gateway.toml`, update `docs/reference/gateway-config.mdx` in the -same branch. If the change affects user-facing compute-driver setup, also -update `docs/reference/sandbox-compute-drivers.mdx` or the relevant deployment -page. - -Use the `sync-agent-infra` skill's maintenance map to identify related skill updates when the implementation changes behavior, commands, or development workflows. Run its full consistency check when the implementation adds, removes, or renames skills or crates; changes workflow relationships or skill coverage; modifies issue or PR templates; or changes agent cross-references. Fix any drift before committing. - -### Step 12: Commit and Push - -Commit all changes using conventional commit format. The `` comes from the issue type in the plan: - -```bash -git add -git commit -m "$(cat <<'EOF' -(): - -Closes # - - -EOF -)" -``` - -Push: - -```bash -git push -u origin HEAD -``` - -### Step 13: Open PR - -Create the PR: - -```bash -gh pr create \ - --title "(): " \ - --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -## Summary -<1-3 sentences describing what was built and the approach taken> - -## Related Issue -Closes # - -## Changes -- ``: -- ``: - -### Deviations from Plan - - -## Testing -- [x] `mise run pre-commit` passes -- [x] Unit tests added/updated -- [x] E2E tests added/updated (if applicable) - -**Tests added:** -- **Unit:** -- **Integration:** -- **E2E:** - -## Checklist -- [x] Follows Conventional Commits -- [x] Commits are signed off (DCO) - -**Documentation updated:** -- ``: -EOF -)" -``` - -**Display the PR URL** so it's easily clickable: - -``` -Created PR [#](https://github.com/OWNER/REPO/pull/) -``` - -### Step 14: Post-Build Cleanup - -#### Post summary comment on the issue - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -## Implementation Complete - -PR: [#](https://github.com/OWNER/REPO/pull/) - -### What was built -<1-2 sentence summary> - -### Tests -- Unit: tests added -- Integration: -- E2E: - -### Docs updated -- - -The issue will auto-close when the PR is merged. -EOF -)" -``` - -#### Post E2E attestation comment on the PR - -If E2E tests were run in Phase 2 of Step 10, post an attestation comment on the **PR** documenting that local E2E tests passed. This is necessary because E2E tests are not yet running in CI — this comment serves as the verification record for reviewers. - -Collect the metadata before posting: - -```bash -# Get the commit SHA that was tested -COMMIT_SHA=$(git rev-parse HEAD) - -# Get the test output summary (last few lines of pytest output) -# This was captured during the Phase 2 run — include the pass/fail/skip counts -``` - -Post the attestation: - -```bash -gh pr comment --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -## E2E Test Attestation - -Local E2E tests passed. CI does not currently run E2E tests, so this comment serves as the verification record. - -| Field | Value | -|-------|-------| -| **Commit** | `` | -| **Command** | `` | -| **Gateway mode** | `` | -| **Result** | ✅ All passed | - -### Test Summary - -``` - -``` - -### Tests Executed -- `::` — PASSED -- `::` — PASSED -- ... -EOF -)" -``` - -Include **every test** that ran (not just the new ones) so the reviewer can see full coverage. If any tests were skipped, note them and explain why. - -#### Update labels - -If `agent:in-progress` is present, replace it with `agent:pr-opened`. Do not add `agent:pr-opened` for an unlabeled direct invocation: - -```bash -gh issue edit --remove-label "agent:in-progress" --add-label "agent:pr-opened" -``` - -#### Report workflow run URL - -Get the workflow run URL from the PR so the user can monitor CI: - -```bash -BRANCH=$(gh pr view --json headRefName --jq '.headRefName') -gh run list --branch "$BRANCH" --limit 1 --json databaseId,status,url -``` - -Report the workflow run URL and suggest the user can use the `watch-github-actions` skill to monitor it. - ---- - -## Branch D: Resume In-Progress Build - -If the `agent:in-progress` label is present, the skill was previously started but may not have completed. - -1. Check for an existing branch matching the issue ID: - ```bash - git branch -r | grep -i "" - ``` -2. If found, check it out and inspect the state (are there uncommitted changes? committed but not pushed? pushed but no PR?). -3. Resume from the appropriate step (9, 10, 12, or 13). -4. If the state is unrecoverable, report to the user and suggest starting fresh. Queue mode requires a human to reapply `agent:implementation-requested`; a new direct implementation request can resume without it. - ---- - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue view --json number,title,body,state,labels,author` | Fetch full issue metadata | -| `gh issue view --json comments` | Fetch all comments on an issue | -| `gh issue comment --body "..."` | Post a comment on an issue | -| `gh api repos/{owner}/{repo}/issues/comments/ -X PATCH -f body="..."` | Edit an existing comment | -| `gh issue edit --add-label "..."` | Add labels | -| `gh issue edit --remove-label "..."` | Remove labels | -| `gh pr list --state open --search "..."` | Search for open PRs | -| `gh pr create --title "..." --body "..."` | Create a pull request | -| `gh api user --jq '.login'` | Get current GitHub username | -| `mise run pre-commit` | Run pre-commit checks (lint, format, license headers) | -| `mise run e2e:docker` | Run smoke E2E against a standalone Docker-backed gateway | -| `mise run e2e:podman` | Run smoke E2E against a Podman-backed gateway | -| `mise run e2e:vm` | Run smoke E2E against the VM compute driver | - -## Example Usage - -### First run — no plan exists - -User says: "Plan issue #42" - -1. Fetch issue #42 — title: "Add pagination to dataset list endpoint" -2. Confirm `state:accepted` with no blocking triage state; the user's direct request authorizes planning even if `agent:plan-requested` is absent -3. Fetch comments — no `🏗️ build-plan` marker found -4. Pass issue to `principal-engineer-reviewer` for analysis -5. Reviewer produces a plan: feat type, Medium complexity, 3 implementation steps, unit + integration tests needed -6. Post the plan comment with the `🏗️ build-plan` marker -7. Because this direct invocation was unlabeled, leave the `agent:*` workflow labels unchanged -8. Report to user: "Plan posted on issue #42. Awaiting review." - -### Second run — human left feedback - -User says: "Check on issue #42" - -1. Fetch issue #42 and comments -2. Find existing plan comment (Revision 1) -3. Find new human comment: "Should we also paginate the search endpoint?" -4. Post response quoting the question, explaining that search pagination is out of scope for this issue but could be a follow-up -5. Report to user: "Responded to feedback on #42. Plan unchanged." - -### Third run — human revised scope, plan needs update - -User says: "Check issue #42" - -1. Fetch issue #42 and comments -2. Find plan + new human comment: "Actually, let's include search pagination. Updated the issue description." -3. Post response acknowledging the scope change -4. Edit the plan comment to include search endpoint pagination — Revision 2 -5. Report to user: "Updated plan to include search pagination (Revision 2)." - -### Fourth run — implementation requested - -User says: "Build issue #42" - -1. Fetch issue #42 — `state:accepted` is present; the user's direct request authorizes implementation -2. Plan exists (Revision 2), complexity: Medium, confidence: High -3. No conflicting branches or PRs -4. Create branch `feat/42-add-pagination/jmyers` -5. Leave `agent:*` labels unchanged because this direct invocation was not picked up from the queue -6. Implement pagination for both endpoints per the plan -7. Add unit tests for pagination logic, integration tests for both endpoints -8. `mise run pre-commit` passes on first attempt -9. E2E tests skipped (no changes under `e2e/`) -10. Commit, push, create PR with `Closes #42` -11. Post summary comment on issue with PR link -12. No agent-workflow label transition is needed -13. Report PR URL and workflow run status to user - -### Run on issue with existing PR - -User says: "Build issue #42" - -1. Fetch issue #42 — `agent:pr-opened` label present -2. Find existing PR #789 linked to the issue -3. Report: "PR [#789](...) already exists for issue #42. Nothing to build." - -### Run on high-complexity issue - -User says: "Build issue #99" - -1. Fetch issue #99 — `state:accepted` is present; the user's direct request authorizes implementation -2. Plan exists: complexity High, confidence Low, has open questions -3. Warn user: "Issue #99 is rated High complexity / Low confidence. Proceeding but flagging for your awareness." -4. Continue with build +Do not apply acceptance or roadmap decisions on behalf of a maintainer. Do not introduce `agent:*` workflow labels. diff --git a/.agents/skills/build-openshell-mxc-windows/SKILL.md b/.agents/skills/build-openshell-mxc-windows/SKILL.md index d61fc555e9..a6f71388ff 100644 --- a/.agents/skills/build-openshell-mxc-windows/SKILL.md +++ b/.agents/skills/build-openshell-mxc-windows/SKILL.md @@ -1,6 +1,8 @@ --- name: build-openshell-mxc-windows description: Maintain and validate OpenShell's build-only Windows MSVC lane for x64 and ARM64. Use when working on Windows compilation, `windows:*` mise tasks, unsupported Windows compute-driver contracts, or Windows build reports. This skill does not implement Docker, Kubernetes, Podman, VM, MXC driver, policy translation, MSI, service, or supervisor runtime support on Windows. +metadata: + internal: true --- # Build OpenShell-MXC for Windows @@ -28,8 +30,7 @@ The Windows build lane is implemented by these tracked files: | `tasks/windows.toml` | Mise task entry points for `windows:*` commands. | | `tasks/rust.toml`, `tasks/test.toml`, and `tasks/markdown.toml` | Windows routing for compiler-bearing checks, explicit Unix-only test skips, and Markdown dependency setup. | | `tasks/scripts/windows-msvc.ps1` | PowerShell wrapper that enters the Visual Studio developer environment and invokes Cargo. | -| `.github/workflows/windows-msvc.yml` | Manually dispatched GitHub Actions jobs with architecture-specific Rust caches for x64 and future ARM64 Windows validation. | -| `architecture/windows-msvc-build.md` | Design notes and validation contract. | +| `.github/workflows/windows-msvc.yml` | Opt-in PR lint and test plus advisory `windows` branch cache seeding and dependent binary builds on native x64 and ARM64 runners. | | `.agents/skills/build-openshell-mxc-windows/` | This skill and companion reference material. | Use the code that is already in the repo. Do not generate a parallel Windows @@ -112,8 +113,8 @@ The lane targets a Windows host with Visual Studio Build Tools and rustup. | Visual Studio 2022 or newer | `where.exe cl.exe` from a Developer PowerShell | Build Tools, Community, Professional, and Enterprise editions work when the target C++ components are installed. The wrapper discovers `VsDevCmd.bat` through `OPENSHELL_VSDEVCMD`, `vswhere`, or installed release directories such as `18` and `2022`. | | Visual C++ ARM64 tools | `vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.ARM64 -property installationPath` | Required for native ARM64 check, build, and tests and for x64-to-ARM64 check/build. Tests always require a native runner. | | Visual C++ ARM64 Spectre-mitigated libraries | `vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre -property installationPath` | Required by `regorus` through `msvc_spectre_libs`; the build fails when the selected MSVC toolset lacks `lib\spectre\arm64`. | -| Visual C++ Clang tools | `vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Llvm.Clang -property installationPath` | Provides host-native `libclang.dll` for `bindgen` and `clang-cl.exe` for ARM64 crypto dependencies such as `ring` and `aws-lc-sys`. On ARM64, the wrapper uses `VC\Tools\Llvm\Arm64\bin`. | -| Visual C++ CMake tools | `vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.CMake.Project -property installationPath` | Provides CMake and Ninja. The x64-to-ARM64 path adds Ninja to `PATH` for native dependencies but keeps bundled Z3 on CMake's Visual Studio ARM64 generator with native MSVC `cl.exe`. | +| Visual C++ Clang tools | `vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Llvm.Clang -property installationPath` | Provides host-native `libclang.dll` for `bindgen` and `clang-cl.exe` for ARM64 crypto dependencies such as `aws-lc-sys`. On ARM64, the wrapper uses `VC\Tools\Llvm\Arm64\bin`. | +| Visual C++ CMake tools | `vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.CMake.Project -property installationPath` | Provides CMake and Ninja for native dependencies. The x64-to-ARM64 path adds Ninja to `PATH`; Z3 uses an architecture-specific prebuilt release. | | Windows SDK | `where.exe rc.exe` from a Developer PowerShell | Install an SDK containing target libraries and ARM64 tools. | | Rust via rustup | `rustc --version` | Add each target being validated: `x86_64-pc-windows-msvc` and/or `aarch64-pc-windows-msvc`. The wrapper also adds the selected target. | | mise | `mise --version` | Used as a task runner only. | @@ -133,8 +134,9 @@ from this skill. | `CARGO_TARGET_DIR` | `target` under repo root | Override Cargo output location. Use a short absolute path when x64-to-ARM64 builds approach Windows path-length limits. | | `Z3_LIBRARY_PATH_OVERRIDE` | unset | Directory containing an x64 system `libz3.lib`; not valid for ARM64. | | `Z3_SYS_Z3_HEADER` | unset | Full `z3.h` path required with a system Z3 library. | -| `Z3_SYS_BUNDLED_DIR_OVERRIDE` | pinned source cached under `CARGO_TARGET_DIR` when explicit, otherwise `%LOCALAPPDATA%\OpenShell\cache\z3` | Use an existing Z3 source tree containing `src/api/z3.h`; otherwise the wrapper fetches the pinned revision through Git and sets this automatically. | -| `RUSTC_WRAPPER` | cleared by wrapper | The wrapper clears inherited values because `--skip-tools` does not provision `sccache`. | +| `Z3_SYS_Z3_VERSION` | `5.1.0` | Pinned official prebuilt Z3 release selected by the wrapper. | +| `READ_ONLY_GITHUB_TOKEN` | unset | Optional token for the Z3 release lookup; GitHub Actions supplies `github.token`. | +| `RUSTC_WRAPPER` | inherited | The wrapper resolves an available command to an absolute path. If it is unavailable, the wrapper warns and continues without compiler caching. | Legacy fork variables such as `OPENSHELL_UPSTREAM`, `OPENSHELL_MXC_FORK_DIR`, and `OPENSHELL_MXC_FORK_BRANCH` are no longer part @@ -187,17 +189,24 @@ order: 6. Focused unsupported-driver contract tests. 7. Artifact reporting. -The GitHub Actions jobs use architecture-specific `Swatinem/rust-cache` -entries for the Cargo registry and dependency target artifacts. Failed runs -also save their usable dependency artifacts. The workflow remains manually -dispatched until cache-hit runtimes justify restoring automatic triggers. +The GitHub Actions jobs layer architecture-specific `Swatinem/rust-cache` +entries for Cargo registry and dependency target artifacts with sccache's GHA +backend for cacheable Rust compiler outputs. Failed runs also save their usable +dependency artifacts. Pull-request mirrors labeled `test:windows` run Clippy for the +Windows-supported workspace and e2e crates plus Rust tests. Pushes to `windows` and +manual dispatches on that branch run the same lint and test commands in a cache-seed job, +followed by a dependent release-binary build job. The seed and PR jobs use the +same cache namespaces, but pull-request mirrors cannot restore the +`windows` branch cache because GitHub scopes caches by branch. Merge queues do +not run this workflow. Release-branch seed +and build jobs use job-level `continue-on-error: true`; opt-in PR jobs report +failures normally. Applying the label alone does not start a run: re-run all +jobs in the current mirror push run, or push a new mirrored commit. The binaries are not uploaded or published. The ARM64 check/build steps in this x64-host contract are cross-builds. The wrapper discovers and adds host-native LLVM and Ninja to `PATH`, requires the ARM64 compiler and Spectre-mitigated libraries, lets ARM64 crypto crates select -`clang-cl`, and keeps bundled Z3 on native MSVC `cl.exe` with CMake's Visual -Studio ARM64 generator. Z3 does not use Ninja because `z3-sys 0.10.9` passes -the MSBuild-only `-m` argument. +`clang-cl`, and downloads the official prebuilt ARM64 Z3 static library. On ARM64 hosts, validate the native ARM64 check, build, and test path. The wrapper rejects test targets that do not match the host architecture, so x64 @@ -206,8 +215,13 @@ compatibility under emulation is not part of these tasks. The aggregate commands above on an ARM64 host. The repository-wide `mise run pre-commit` task is also supported on Windows. +Run `rust:lockfiles:check`, `sdk:ts:ci`, and `go:ci` through the Windows-aware +tasks when validating those surfaces. Do not count the Go +Windows ARM64 race-detector exclusion or POSIX permission-bit skips as security +coverage. SDK test dependencies must remain at their lockfile versions. Its Rust check, Clippy, and test dependencies enter the same MSVC environment -for the native host target and clear inherited `RUSTC_WRAPPER`. Linux glibc +for the native host target and use an inherited compiler wrapper when it is +available. Linux glibc installer tests and Linux service/RPM packaging-asset tests skip explicitly; the Linux build-environment shell-helper test also skips; cross-platform checks continue to run. The blocking Windows Clippy pass excludes unsupported @@ -231,7 +245,7 @@ crypto dependency builds. | `windows:build:arm64` | Release-builds `openshell-gateway.exe` and `openshell.exe` for ARM64. | | `windows:test:x64` | Runs native x64 workspace tests with `--no-fail-fast`, excluding unsupported Windows packages as top-level workspace targets. | | `windows:test:arm64` | Runs native ARM64 workspace tests with `--no-fail-fast` and the same package exclusions. Rejects non-ARM64 hosts. | -| `windows:test:unsupported:x64` | Re-runs focused `openshell-server` tests for unsupported Windows driver behavior. | +| `windows:test:unsupported:x64` | Re-runs focused `openshell-gateway` tests for unsupported Windows driver behavior. | | `windows:test:unsupported:arm64` | Re-runs the same focused contracts natively on ARM64. Rejects non-ARM64 hosts. | | `windows:artifacts` | Reports size and SHA256 for release artifacts that exist. | | `windows:ci` | Runs the full ordered x64-host Windows CI lane, plus ARM64 check/build when not skipped. | @@ -246,22 +260,34 @@ in the gateway build graph, but their Unix-socket standalone binaries do not. Windows must continue to reject unsupported compute drivers clearly. +The gateway's `compute-driver-mxc` feature independently links and registers +MXC on Windows. Each other `compute-driver-*` feature installs its own Windows +rejection stub without linking that driver crate. The default +`in-tree-compute-drivers` alias enables all five features. An MXC-only build +uses `--no-default-features --features compute-driver-mxc` (add `telemetry` +and `openshell-server/prebuilt-z3` as needed). + | Driver | Windows build behavior | Runtime behavior | |---|---|---| -| Docker | Driver crate excluded; server config contract retained. | Gateway construction returns unsupported. | -| Kubernetes | Driver crate excluded; server config contract retained. | Gateway construction returns unsupported. | -| Podman | Driver crate excluded; server config contract retained. | Gateway construction returns unsupported. | -| VM | Driver crate excluded from workspace validation. | Gateway construction returns unsupported. | +| Docker | Driver crate excluded; gateway registration stub retained. | Gateway construction returns unsupported. | +| Kubernetes | Driver crate excluded; gateway registration stub retained. | Gateway construction returns unsupported. | +| Podman | Driver crate excluded; gateway registration stub retained. | Gateway construction returns unsupported. | +| VM | Driver crate excluded; gateway registration stub retained. | Gateway construction returns unsupported. | The focused contract tasks for either native architecture run: ```text windows_builtin_compute_drivers_report_unsupported +default_registry_contains_exactly_the_enabled_compute_drivers ``` -These tests are also included in the full x64 workspace test run; the focused -task intentionally re-runs them so unsupported Windows behavior is visible in -the CI report. +The same tasks also run gateway library tests for protocol-only, MXC-only, +Docker-stub-only, and MXC plus Docker-stub builds. Their logs use +`test--selective-.log`. + +The default-feature tests are also included in the full workspace test run. +The focused task is available for local diagnosis and selective-build +validation; GitHub Actions does not re-run it after the full suite. ## Test Accounting Guidance @@ -291,19 +317,13 @@ Useful log files: | `test-x86_64-pc-windows-msvc-unsupported-*.log` | Focused unsupported-driver contract output. | | `test-aarch64-pc-windows-msvc-unsupported-*.log` | Focused native ARM64 contract output. | -The first bundled-Z3 check or test can spend several minutes in CMake/MSBuild -without much console output because Cargo output is redirected to the log. Look -for native `MSBuild.exe` workers before treating the process as stalled. The -wrapper fetches the pinned Z3 source through Git before Cargo starts. It caches -under an explicitly configured `CARGO_TARGET_DIR`, or under the current user's -local application data directory when Cargo uses its default target tree. -Concurrent commands publish the validated source through an atomic directory -rename, so x64 and ARM64 validation can share the cache safely. The wrapper does -not rely on the rate-limited GitHub Contents API used by `z3-sys`. A failed -fetch reports the partial checkout path for diagnosis. The artifact report -computes SHA256 through .NET directly and does not rely on the -`Get-FileHash` module being available inside the mise-launched Windows -PowerShell process. +The first check downloads the pinned official Z3 archive for the target +architecture through `z3-sys`. GitHub Actions authenticates the lookup with its +read-only workflow token; local users can set `READ_ONLY_GITHUB_TOKEN` if an +unauthenticated lookup is rate-limited. Cargo stores the extracted library in +its target tree, so the Windows target cache reuses it. The artifact report +computes SHA256 through .NET directly and does not rely on the `Get-FileHash` +module being available inside the mise-launched Windows PowerShell process. ## Common Fix Patterns diff --git a/.agents/skills/build-openshell-mxc-windows/reference.md b/.agents/skills/build-openshell-mxc-windows/reference.md index 4a5f2668a1..81aecb9ec1 100644 --- a/.agents/skills/build-openshell-mxc-windows/reference.md +++ b/.agents/skills/build-openshell-mxc-windows/reference.md @@ -9,8 +9,7 @@ maintaining the existing build-only Windows MSVC lane. |---|---| | `tasks/windows.toml` | Mise task definitions for `windows:*`. | | `tasks/scripts/windows-msvc.ps1` | Visual Studio environment discovery, rustup target setup, Cargo invocation, logs, artifact report. | -| `.github/workflows/windows-msvc.yml` | Manual GitHub Actions x64 job and disabled ARM64 scaffold, each with an architecture-specific Rust dependency cache. | -| `architecture/windows-msvc-build.md` | Human-readable design contract. | +| `.github/workflows/windows-msvc.yml` | Opt-in PR lint and test plus `windows` branch cache seeding and dependent binary builds on native x64 and ARM64 runners. | ## Commands @@ -46,8 +45,9 @@ if ($arch -eq [System.Runtime.InteropServices.Architecture]::Arm64) { The native test tasks reject a target that does not match the host architecture. Do not report x64 compatibility-under-emulation coverage from an ARM64 run. -The wrapper adds missing rustup targets and clears inherited -`RUSTC_WRAPPER`. It does not install Visual Studio, Rust, Docker, Kubernetes, +The wrapper adds missing rustup targets and preserves an inherited +`RUSTC_WRAPPER` when the command is available. Otherwise, it warns and clears +the setting. It does not install Visual Studio, Rust, Docker, Kubernetes, Podman, WSL, Hyper-V, or VM tooling. On Windows, `mise run pre-commit` routes `rust:check`, `rust:lint`, and @@ -67,11 +67,10 @@ file. For ARM64, verify the Visual Studio instance contains the ARM64 MSVC tools, ARM64 Spectre-mitigated libraries, Clang tools, CMake tools, and a Windows SDK. Clang supplies host-native `libclang.dll` for `bindgen` and `clang-cl.exe` for -ARM64 crypto dependencies such as `ring` and `aws-lc-sys`. Native ARM64 uses -the normal bundled-Z3 CMake path. An x64-to-ARM64 check/build discovers and -adds host-native Ninja to `PATH`, while the crypto crates select `clang-cl`. -Bundled Z3 uses CMake's Visual Studio ARM64 generator with native MSVC `cl.exe` -because `z3-sys 0.10.9` passes the MSBuild-only `-m` argument. Use a short +ARM64 crypto dependencies such as `aws-lc-sys`. Native and +x64-to-ARM64 builds use the official prebuilt Z3 5.1.0 static library for the +target architecture. An x64-to-ARM64 check/build discovers and adds host-native +Ninja to `PATH`, while the crypto crates select `clang-cl`. Use a short `CARGO_TARGET_DIR` if Windows path-length limits are reached. ## Unsupported Driver Rules @@ -85,7 +84,7 @@ Windows is a build target only. These runtimes remain unsupported: Rules: -- Keep config/library stubs where the gateway needs them. +- Keep registration stubs in the gateway composition crate where the gateway needs them. - Return clear unsupported errors at runtime. - Do not build standalone Windows driver binaries. - Do not add Docker Desktop, WSL, Hyper-V, Podman machine, Podman Desktop, or @@ -112,16 +111,18 @@ top-level workspace targets for check/test: --exclude openshell-driver-vault --exclude openshell-driver-vm --exclude openshell-sandbox ---exclude openshell-supervisor-network +--exclude openshell-supervisor --exclude openshell-supervisor-process --exclude openshell-vfio ``` The gateway keeps platform configuration and unsupported-operation contracts -without depending on the Docker, Kubernetes, Podman, sandbox supervisor, -process supervisor, VM, or VFIO runtime crates. The Kubernetes Secrets and -Vault libraries still compile as gateway dependencies; only their standalone -Unix-socket binaries and package-level tests are excluded as top-level targets. +without depending on the Docker, Kubernetes, Podman, sandbox runtime, +standalone supervisor, supervisor process runtime, VM, or VFIO crates. The MXC +driver does depend on the cross-platform supervisor network library for its host +egress proxy. The Kubernetes Secrets and Vault libraries still compile as +gateway dependencies; only their standalone Unix-socket binaries and +package-level tests are excluded as top-level targets. ## Common Errors @@ -180,7 +181,7 @@ blocked dependency. ### Focused tests report many filtered-out tests This is expected for `windows:test:unsupported:x64`. Cargo runs one named test -and filters the other `openshell-server` tests. Report these as filtered, not +and filters the other `openshell-gateway` tests. Report these as filtered, not ignored. ## Reporting Counts diff --git a/.agents/skills/create-github-issue/SKILL.md b/.agents/skills/create-github-issue/SKILL.md index 609d55ad15..25a9cbc4c9 100644 --- a/.agents/skills/create-github-issue/SKILL.md +++ b/.agents/skills/create-github-issue/SKILL.md @@ -1,6 +1,8 @@ --- name: create-github-issue description: Create GitHub issues using the gh CLI. Use when the user wants to create a new issue, report a bug, request a feature, or create a task in GitHub. Trigger keywords - create issue, new issue, file bug, report bug, feature request, github issue. +metadata: + internal: true --- # Create GitHub Issue @@ -17,7 +19,7 @@ This project uses YAML form issue templates. When creating issues, match the tem ### Bug Reports -Do not add a type label automatically. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, and **Acceptance Criteria**, followed by bug-specific reproduction steps and environment details. Logs are optional and must be concise and redacted. Apply area or topic labels only when they are clearly known. +Do not add a type label automatically. Confirm that the human operator personally uses OpenShell and directly encountered the problem or needs the feature for a specific use case. If that first-hand attestation or concrete use case is missing, ask for it before creating the issue. Frame the issue entirely in terms of OpenShell. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, and **Acceptance Criteria**, followed by bug-specific reproduction steps using only OpenShell deployments and environment details. Do not install third-party tools to demonstrate reproducibility. Logs are optional and must be concise and redacted. If the issue suggests a change to configuration, CLI, SDK, or other user experience, include a notional example of the proposed interaction for human review. Inspect current repository labels before applying any; use only labels whose meaning is clear. ```bash gh issue create \ @@ -25,7 +27,7 @@ gh issue create \ --body "$(cat <<'EOF' ## User Story -As a , I want , so that . +I use OpenShell for . I directly encountered or need so that . ## Problem Statement @@ -50,18 +52,20 @@ As a , I want , so that . - OS: - Runtime, deployment, or integration: +## Suggested UX (if applicable) + + + ## Logs -``` - -``` + EOF )" ``` ### Feature Requests -Do not add a type label automatically. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, **Proposed Design**, **Acceptance Criteria**, and **Alternatives Considered**. The proposed design should define the user-facing workflow and externally observable behavior without prescribing internal implementation. Agent investigation is optional. Apply area or topic labels only when they are clearly known. +Do not add a type label automatically. Confirm that the human operator personally uses OpenShell and directly encountered the problem or needs the feature for a specific use case. If that first-hand attestation or concrete use case is missing, ask for it before creating the issue. Frame the issue entirely in terms of OpenShell. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, **Proposed Design**, **Acceptance Criteria**, and **Alternatives Considered**. The proposed design should define the user-facing workflow and externally observable behavior without prescribing internal implementation. Agent investigation is optional. If the issue suggests a change to configuration, CLI, SDK, or other user experience, include a notional example of the proposed interaction for human review. Inspect current repository labels before applying any; use only labels whose meaning is clear. ```bash gh issue create \ @@ -69,7 +73,7 @@ gh issue create \ --body "$(cat <<'EOF' ## User Story -As a , I want , so that . +I use OpenShell for . I directly encountered or need so that . ## Problem Statement @@ -83,13 +87,17 @@ As a , I want , so that . +## Suggested UX (if applicable) + + + ## Acceptance Criteria - [ ] ## Alternatives Considered - + ## Agent Investigation @@ -100,12 +108,16 @@ EOF ### Tasks -For internal tasks that don't fit bug/feature templates: +For internal tasks that do not fit bug/feature templates, still obtain the operator's first-hand OpenShell use case before creating the issue: ```bash gh issue create \ --title ": " \ --body "$(cat <<'EOF' +## User Story + + + ## Description @@ -123,7 +135,7 @@ EOF GitHub built-in issue types (`Bug`, `Feature`, `Task`) should come from the matching issue template when possible, or be set manually afterward. Do not try to emulate them through labels. -Creating an issue does not accept it or queue agent work. Agents never apply `state:accepted`, the `roadmap` label, add issues to the roadmap project, or apply `agent:plan-requested` or `agent:implementation-requested`. Community issues proceed through `triage-issue`; a human accepts technically validated work with `state:accepted` or roadmap placement. The request labels queue work for unattended agents; a user may instead direct an agent to a specific issue. +Creating an issue does not accept it. Follow Label Discovery in `CONTRIBUTING.md` to retrieve every page of current `state:*` label definitions and resolve unclear meanings, then follow its triage → validation → human acceptance process. Agents may assess facts, but only humans decide whether to accept work or place it on the roadmap. A direct user request authorizes the requested planning or implementation phase without changing issue disposition. ## Useful Options @@ -148,5 +160,5 @@ Created issue [#123](https://github.com/OWNER/REPO/issues/123) Use the issue number to: -- Reference in commits: `git commit -m "Fix validation error (fixes #123)"` -- Create a branch following project convention: `-/` +- Reference in signed-off Conventional Commits: `git commit --signoff -m "fix(cli): validate empty requests (fixes #123)"` +- Create a branch following Branch Names in `CONTRIBUTING.md`. diff --git a/.agents/skills/create-github-pr/SKILL.md b/.agents/skills/create-github-pr/SKILL.md index d98aba37f2..4f06a0818c 100644 --- a/.agents/skills/create-github-pr/SKILL.md +++ b/.agents/skills/create-github-pr/SKILL.md @@ -1,6 +1,8 @@ --- name: create-github-pr description: Create GitHub pull requests using the gh CLI. Use when the user wants to create a new PR, submit code for review, or open a pull request. Trigger keywords - create PR, pull request, new PR, submit for review, code review. +metadata: + internal: true --- # Create GitHub Pull Request @@ -11,7 +13,7 @@ Create pull requests on GitHub using the `gh` CLI. - The `gh` CLI must be authenticated (`gh auth status`) - You must have commits on a branch that's pushed to the remote -- For issue-backed work, the branch should follow `-/`. Exempt issue-less changes may use `/`. +- Every PR must close an existing issue, except automated dependency updates as described in `CONTRIBUTING.md`. Follow Branch Names in `CONTRIBUTING.md` for contributor branch names. ## Before Creating a PR @@ -19,22 +21,20 @@ Create pull requests on GitHub using the `gh` CLI. If the branch changes gateway TOML parsing, `[openshell.gateway]` fields, `[openshell.drivers.]` fields, driver config defaults, or Helm rendering -of `gateway.toml`, verify that `docs/reference/gateway-config.mdx` is updated +of `gateway.toml`, verify that `docs/how-it-works/gateways/configuration.mdx` is updated in the same branch. If the change affects user-facing compute-driver setup, -also update `docs/reference/sandbox-compute-drivers.mdx` or the relevant +also update `docs/how-it-works/sandboxes/runtimes.mdx` or the relevant deployment docs. ### Check Agent Infrastructure Use the `sync-agent-infra` skill's maintenance map to identify related skill updates when the branch changes behavior, commands, or development workflows. Run its full consistency check when the branch adds, removes, or renames skills or crates; changes workflow relationships or skill coverage; modifies issue or PR templates; or changes agent cross-references. Resolve any drift before creating the PR. -### Run Pre-commit Checks +### Verify the Affected Areas -Run the local pre-commit task before opening a PR: +Use the verification guidance in `CONTRIBUTING.md` to select checks for the changed files and behavior. Guidance, skills, and template changes need applicable Markdown, YAML, link, and consistency checks. Run Rust or SDK suites when those components or their dependencies can be affected. Shared APIs, schemas, dependencies, and build changes may require broader checks even when component source files are unchanged. -```bash -mise run pre-commit -``` +`mise run ci` and `mise run pre-commit` are broad convenience tasks, not blanket PR prerequisites. Broaden validation only for a concrete remaining risk or failed check, and report what actually ran. ### Verify Branch State @@ -47,21 +47,13 @@ Before creating a PR, verify: git branch --show-current ``` -2. **Branch follows naming convention** - Use `-/` for issue-backed work or `/` for an exempt issue-less change. +2. **Branch follows naming convention** - Follow Branch Names in `CONTRIBUTING.md`, including the exceptions for generated branches and private security work. ```bash - # Example: 1234-add-pagination/jd + # Example: feat/1234-add-pagination/octocat git branch --show-current ``` -3. **Consider squashing commits** - For cleaner history, squash related commits before pushing: - - ```bash - # Squash last N commits into one - git reset --soft HEAD~N - git commit -m "feat(component): description" - ``` - ### Push Your Branch Ensure your branch is pushed to the remote: @@ -114,19 +106,25 @@ gh pr create --title "PR title" --body "PR description" ### Link to an Issue -Features, user-visible behavior changes, public API changes, architecture changes, and multi-PR efforts must link an accepted issue. Use `Closes #` in the body to auto-close the issue when merged: +Every PR except an automated dependency update must close its own issue. Verify that the issue exists, remains open, and covers the PR scope. Automated dependency updates follow the exception in `CONTRIBUTING.md`. Use `Closes #` in the body so merge closes it: ```bash gh pr create \ - --title "Fix validation error for empty requests" \ - --body "Closes #123 + --title "fix(cli): validate empty requests" \ + --body "## Summary -## Summary -- Added validation for empty request bodies -- Returns 400 instead of 500" +Validate empty request bodies. + +## Related Issue + +Closes #123 + +## Changes + +- Return 400 instead of 500" ``` -Small documentation fixes, mechanical maintenance, and obvious localized bug fixes may omit a separate issue when the PR contains enough context to review the decision and implementation together. In that case, write `No issue required: ` in the Related Issue section. Do not use this exception for security fixes; follow `SECURITY.md`. +If the work needs multiple PRs, create a separate closable issue for each PR. A higher-level tracking issue may link the component issues, but no PR should close that tracking issue until all its work is complete. Follow `SECURITY.md` for vulnerability disclosure. First-time external contributors must be vouched before their PRs are accepted; the vouch check may close unvouched PRs. Check the current vouch process before opening a PR for an external contributor. ### Create as Draft @@ -136,12 +134,6 @@ For work-in-progress that's not ready for review: gh pr create --draft --title "WIP: New feature" ``` -### With Labels - -```bash -gh pr create --title "Title" --label "area:cli" --label "topic:security" -``` - ### Target a Different Branch Default target is `main`. To target a different branch: @@ -159,15 +151,15 @@ PR descriptions must follow the project's [PR template](.github/PULL_REQUEST_TEM ## Related Issue - + ## Changes ## Testing -- [ ] `mise run pre-commit` passes -- [ ] Unit tests added/updated +- [ ] Checks appropriate to the affected code and behavior pass +- [ ] Unit tests added/updated (if applicable) - [ ] E2E tests added/updated (if applicable) ## Checklist @@ -185,7 +177,7 @@ gh pr create \ --body "$(cat <<'EOF' ## Summary -Add `--limit` and `--offset` flags to `openshell sandbox list` for pagination. +Add `--page-size` and `--page-token` flags to `openshell sandbox list` for continuation-token pagination. ## Related Issue @@ -193,13 +185,13 @@ Closes #456 ## Changes -- Added `offset` and `limit` query parameters to the sandbox list API call -- Default limit is 20, max is 100 -- Response includes `total_count` field +- Added `page_size` and `page_token` fields to the sandbox list API call +- Default page size is 100, max is 1,000 +- Structured responses include `next_page_token` ## Testing -- [x] `mise run pre-commit` passes +- [x] Relevant CLI format, lint, and unit checks pass - [x] Unit tests added/updated - [ ] E2E tests added/updated (if applicable) diff --git a/.agents/skills/create-rfc/SKILL.md b/.agents/skills/create-rfc/SKILL.md index f767e47587..6e8cffd428 100644 --- a/.agents/skills/create-rfc/SKILL.md +++ b/.agents/skills/create-rfc/SKILL.md @@ -1,6 +1,8 @@ --- name: create-rfc description: Create OpenShell RFC proposals in rfc/ from a design request. Use when the user asks to write, draft, start, create, or update an RFC, Request for Comments, architecture proposal, API proposal, process proposal, or cross-cutting design proposal that should follow the OpenShell RFC process and template. +metadata: + internal: true --- # Create RFC @@ -31,12 +33,12 @@ Keep the template as the source of truth for section guidance. ## Writing Standards - Prefer concrete design statements over placeholder language. -- Link to relevant issues, prior RFCs, and architecture docs when they provide +- Link to relevant issues, prior RFCs, and published docs when they provide needed context. - Keep rejected or left-out designs in Alternatives, not Proposal. - Use Mermaid diagrams for architecture or data flow when a diagram would make the proposal easier to review. -- Do not update `architecture/` or published docs just because an RFC was +- Do not update published docs just because an RFC was drafted. Those updates belong with implementation or with an accepted RFC when the user asks for them. diff --git a/.agents/skills/create-spike/SKILL.md b/.agents/skills/create-spike/SKILL.md index 63bcffb72b..7fdc98c7d6 100644 --- a/.agents/skills/create-spike/SKILL.md +++ b/.agents/skills/create-spike/SKILL.md @@ -1,316 +1,27 @@ --- name: create-spike -description: Investigate a plain-language problem description by deeply exploring the codebase, then create a structured GitHub issue with technical findings. Prequel to build-from-issue — maps vague ideas to concrete, buildable issues. Trigger keywords - spike, investigate, explore, research issue, technical investigation, create spike, new spike, feasibility, codebase exploration. +description: Investigate an OpenShell problem and create a structured issue with technical findings for human disposition. +metadata: + internal: true --- # Create Spike -Investigate a problem, map it to the codebase, and produce a structured GitHub issue ready for human disposition and roadmap placement. +Investigate a specific OpenShell need and record the findings in a GitHub issue. Use `create-github-issue` for the issue structure and `triage-issue` for the distinction between technical validation and human acceptance. A spike does not authorize implementation or a roadmap decision. -A **spike** is an exploratory investigation. The user has a vague idea — a feature they want, a bug they've noticed, a performance concern — but hasn't mapped it to code, assessed feasibility, or structured it as a buildable issue. This skill does that mapping. +## Before investigating -## Prerequisites +Ask the human operator to attest that they personally use OpenShell and directly encountered the problem or need the feature for a specific use case. If that context is absent, request it before creating the issue. Do not invent a user story or file a generic platform wish as their first-hand need. Search existing issues to avoid duplication. Follow `SECURITY.md` for suspected vulnerabilities instead of filing a public issue. -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote +## Investigate -## Workflow Overview +1. Reconstruct the current OpenShell workflow and the claimed gap. For a bug, use reproduction steps requiring only OpenShell deployments; do not install third-party tools solely to demonstrate it. +2. Explore the relevant code and tests. Separate observed behavior, likely cause, and open questions. If the claim cannot be validated, state the exact missing evidence. +3. For a feature, describe the desired external behavior and evaluate alternatives, including relevant middleware, interceptors, providers, or other extension points. Prefer an applicable extension when it satisfies the use case; the need to run another service alone does not disqualify it. +4. For configuration, CLI, SDK, or other UX changes, include notional commands, configuration, or API examples for human review. Leave internal implementation choices open unless they are essential constraints. -``` -User describes a problem - │ - ├─ Step 1: Gather the problem statement - │ └─ Ask ONE round of clarifying questions if genuinely needed - │ - ├─ Step 2: Deep codebase investigation via principal-engineer-reviewer - │ └─ Map the problem to code, assess feasibility, identify risks - │ - ├─ Step 3: Determine labels from the repo - │ - ├─ Step 4: Create a GitHub issue with structured findings - │ - └─ Step 5: Report to user with issue URL and next steps -``` +## Record the result -## Step 1: Gather the Problem Statement +Create an issue with User Story, Problem Statement, Impact / Why This Matters, Proposed Design when relevant, Acceptance Criteria, Alternatives Considered, and concise Agent Investigation. Include OpenShell-only reproduction and environment details for bugs. Follow Label Discovery in `CONTRIBUTING.md` before applying the assessment state that matches the evidence; retrieve every page and resolve unclear meanings rather than hard-coding label names. Do not apply an acceptance state or add the issue to the roadmap. -The user provides a problem description. This could be: - -- A feature idea: "I want sandboxes to be able to reach private IPs" -- A bug report: "The retry logic in the proxy seems too aggressive" -- A performance concern: "Policy evaluation is slow for large rule sets" -- A refactoring goal: "The config parsing is scattered across too many modules" - -Extract from the user's input: - -1. **What** they want (the desired outcome or observed problem) -2. **Why** they want it (motivation, use case, or trigger) -3. **Constraints** they've mentioned (backwards compatibility, performance targets, etc.) - -### Clarification policy - -If the problem is too vague to determine which area of the codebase to investigate, ask **ONE** round of clarifying questions. Do not over-interrogate. Examples of when to ask: - -- "Make things faster" — ask which component or operation is slow -- "Fix the networking" — ask what specific behavior is wrong - -Examples of when NOT to ask: - -- "The retry logic in the proxy is too aggressive" — clear enough, start investigating -- "Allow sandbox egress to private IP space" — clear enough, start investigating -- "The OPA policy evaluation needs caching" — clear enough, start investigating - -## Step 2: Deep Codebase Investigation - -This is the core of the skill. Use the Task tool with the `principal-engineer-reviewer` sub-agent to perform a thorough codebase investigation. - -``` -Task tool with subagent_type="principal-engineer-reviewer" -``` - -The prompt to the reviewer **must** instruct it to: - -1. **Identify which components/subsystems are involved.** Don't just guess from names — read the code to confirm. - -2. **Read the relevant source files thoroughly.** Not just grep for keywords — actually read and understand the logic. Follow the call chain from entry point through to the relevant behavior. - -3. **Map the current architecture for the affected area.** How do the components interact? What's the data flow? Where are the boundaries? - -4. **Identify the exact code paths that would need to change.** Provide file paths and line numbers. Name the functions, structs, and modules. - -5. **Assess feasibility and complexity:** - - **Low**: Isolated change, < 3 files, clear path forward - - **Medium**: Multiple files/components, some design decisions, but well-scoped - - **High**: Cross-cutting changes, architectural decisions needed, significant unknowns - -6. **Identify risks, edge cases, and design decisions that need human input.** What could go wrong? What trade-offs exist? What decisions shouldn't be made by an agent? - -7. **Check for existing patterns in the codebase that should be followed.** If there's a convention for how similar features are implemented, note it. The implementation should be consistent. - -8. **Look at relevant tests to understand test coverage expectations.** What test patterns exist? What level of coverage is expected for this area? - -9. **Check architecture docs** in the `architecture/` directory for relevant documentation about the affected subsystems. - -10. **Assess gateway config documentation impact.** If the change would add, remove, rename, or change defaults for gateway TOML keys or driver-specific config options, call out that `docs/reference/gateway-config.mdx` must be updated. If the change is surfaced through Helm or compute-driver setup docs, call out the relevant deployment or compute-driver docs too. - -11. **Assess Linux Security Module (LSM) impact.** If the change involves process identity, `/proc` filesystem access, file labeling, binary execution, or inter-process visibility, call out whether it will behave differently on hosts running SELinux (enforcing) or AppArmor. For example: reading `/proc//exe` across an SELinux domain boundary returns ENOENT, not EACCES. Tests that fork+exec into system binaries (different SELinux label) will fail on enforcing hosts. Flag any LSM-sensitive code paths and recommend mitigations. - -12. **Determine the issue type:** `feat`, `fix`, `refactor`, `chore`, `perf`, or `docs`. - -### What makes a good investigation prompt - -Include in the prompt to the reviewer: - -- The user's problem statement (verbatim or lightly paraphrased) -- Any constraints the user mentioned -- A clear instruction to return: component list, file references with line numbers, architecture summary, feasibility assessment, risks, and the issue type - -### What to do with the results - -The reviewer will return a detailed analysis. You'll use this to populate the issue body (Step 4). The issue should contain both the stakeholder-readable summary and the full technical investigation — everything in one place. - -## Step 3: Determine Labels - -Fetch the available labels from the repository: - -```bash -gh label list --limit 100 -``` - -Based on the investigation results, select appropriate labels: - -- **Do not add issue type labels** — GitHub built-in issue types come from issue templates or manual follow-up, not labels -- **Include area labels** if they exist in the repo (e.g., `area:sandbox`, `area:proxy`, `area:policy`, `area:cli`) -- **Do not invent labels** — only use labels that already exist in the repo -- **Add `state:validated` only when the evidence is sufficient for human disposition** — the spike established a coherent problem or proposal and completed the factual assessment needed for a human yes/no decision -- **Add `state:needs-info` instead when material evidence is missing** — identify the exact evidence, reproduction details, or decision input still needed in the issue body -- **Never add `state:accepted`, an `agent:*` label, or the `roadmap` label** — acceptance, roadmap placement, and requests for agent work require a human decision - -## Step 4: Create the GitHub Issue - -Create the issue with a structured body containing both the stakeholder-readable summary and the full technical investigation. The title should follow conventional commit format. - -```bash -gh issue create \ - --title ": " \ - --label "" --label "" \ - --body "$(cat <<'EOF' -## Problem Statement - - - -## Technical Context - - - -## Affected Components - -| Component | Key Files | Role | -|-----------|-----------|------| -| | ``, `` | | -| ... | ... | ... | - -## Technical Investigation - -### Architecture Overview - - - -### Code References - -| Location | Description | -|----------|-------------| -| `:` | | -| `:` | | -| ... | ... | - -### Current Behavior - - - -### What Would Need to Change - - - -### Alternative Approaches Considered - - - -### Patterns to Follow - - - -## Proposed Approach - - - -## Scope Assessment - -- **Complexity:** -- **Confidence:** -- **Estimated files to change:** -- **Issue type:** `` - -## Risks & Open Questions - -- -- -- ... - -## Disposition Readiness - -- **State:** `` -- **Assessment:** -- **Missing evidence:** - -## Test Considerations - -- -- -- -- - ---- -*Created by spike investigation. `state:validated` means the issue is ready for human disposition; `state:needs-info` means specific evidence is still required. A human applies `state:accepted` or places the issue on the roadmap if OpenShell should pursue the work. To queue unattended agent planning, a human applies `agent:plan-requested`; a direct request to an agent does not require that label.* -EOF -)" -``` - -**Do NOT post a follow-up comment on the issue.** All findings must be contained in the issue body itself. - -**Display the issue URL** so it's easily clickable: - -``` -Created issue [#](https://github.com/OWNER/REPO/issues/) -``` - -## Step 5: Report to User - -After creating the issue, report: - -1. The issue URL (as a clickable markdown link) -2. A 2-3 sentence summary of what was found -3. Key risks or decisions that need human attention -4. Next steps: - -For `state:validated`: - -> Review the issue and decide whether OpenShell should pursue it. If yes, apply `state:accepted`, associate it with a roadmap item, or do both. Either action records acceptance; roadmap placement additionally records sequencing. The work may remain human-owned. Apply `agent:plan-requested` to queue planning for an unattended agent, or directly ask an agent to use `build-from-issue`. If no, close it as not planned and record the rationale. - -For `state:needs-info`: - -> Collect the missing evidence identified in the issue. Leave it off the roadmap. Once the evidence is sufficient, replace `state:needs-info` with `state:validated` for human disposition. - -## Design Principles - -1. **Everything goes in the issue body.** Do NOT post follow-up comments. The issue body should contain both the stakeholder-readable summary and the full technical investigation, all in one place. - -2. **Do NOT create an implementation plan.** The spike identifies the problem space and proposes a direction. The implementation plan is `build-from-issue`'s responsibility, created after human review of the spike. - -3. **One round of clarification max.** Don't turn this into an interrogation. If the user provides enough to identify the area of the codebase, start investigating. - -4. **The issue should save `build-from-issue` work.** When `build-from-issue` runs, it reads the issue body as input context. The technical investigation section should contain enough detail that its `principal-engineer-reviewer` can build on the investigation rather than starting from scratch. - -5. **Cross-reference `build-from-issue`.** Mention it as the natural next step in the issue body footer. - -6. **Treat validation as an evidence threshold, not an automatic spike outcome.** Apply `state:validated` only when the investigation supports a human accept/decline decision. Otherwise apply `state:needs-info`, state what is missing, and leave the issue off the roadmap. - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue create --title "..." --body "..." --label "..."` | Create a new issue | -| `gh label list --limit 100` | List available labels in the repo | -| `gh issue edit --add-label "..."` | Add labels to an issue | -| `gh issue view --json number,title,body,state,labels` | Fetch issue metadata | - -## Example Usage - -### Feature spike - -User says: "Allow sandbox egress to private IP space via networking policy" - -1. Problem is clear — no clarification needed -2. Fire `principal-engineer-reviewer` to investigate: - - Finds `is_internal_ip()` SSRF check in `proxy.rs` that blocks RFC 1918 addresses - - Reads OPA policy evaluation pipeline in `opa.rs` and `crates/openshell-sandbox/data/sandbox-policy.rego` - - Reads proto definitions in `sandbox.proto` for `NetworkEndpoint` - - Maps the 4-layer defense model: netns, seccomp, OPA, SSRF check - - Reads `architecture/security-policy.md` and `architecture/sandbox.md` - - Identifies exact insertion points: policy field addition, SSRF check bypass path, OPA rule extension - - Assesses: Medium complexity, High confidence, ~6 files -3. Fetch labels — select `area:sandbox`, `area:proxy`, `area:policy`, `state:validated` -4. Create issue: `feat: allow sandbox egress to private IP space via networking policy` — body includes both the summary and full investigation (code references, architecture context, alternative approaches) -5. Report: "Created issue #59. The investigation found that private IP blocking is enforced at the SSRF check layer in the proxy. The proposed approach adds a policy-level override. A human must now accept or decline it and place it on the roadmap if accepted." - -### Bug investigation spike - -User says: "The proxy retry logic seems too aggressive — I'm seeing cascading failures under load" - -1. Problem is clear enough — investigate retry behavior in the proxy -2. Fire `principal-engineer-reviewer`: - - Finds retry configuration in proxy request handling - - Reads the retry loop, backoff strategy, and timeout settings - - Checks if there's circuit breaker logic - - Maps the failure propagation path - - Identifies that retries happen without backoff jitter, causing thundering herd - - Assesses: Low complexity, High confidence, ~2 files -3. Fetch labels — select `area:proxy`, `state:validated` -4. Create issue: `fix: proxy retry logic causes cascading failures under load` — body includes both the summary and full investigation (retry code references, current behavior trace, comparison to standard backoff patterns) -5. Report: "Created issue #74. The proxy retries without jitter or circuit breaking, which amplifies failures under load. A human must now accept or decline it and place it on the roadmap if accepted." - -### Performance/refactoring spike - -User says: "Policy evaluation is getting slow — can we cache compiled OPA policies?" - -1. Problem is clear — investigate OPA policy evaluation performance -2. Fire `principal-engineer-reviewer`: - - Reads the OPA evaluation pipeline end to end - - Measures where policies are loaded and compiled (per-request vs. cached) - - Checks if there's an existing caching layer - - Reads the policy reload/hot-swap mechanism - - Identifies that policies are recompiled on every evaluation - - Assesses: Medium complexity, Medium confidence (cache invalidation is a design decision), ~4 files -3. Fetch labels — select `area:policy`, `state:validated` -4. Create issue: `perf: cache compiled OPA policies to reduce evaluation latency` — body includes both the summary and full investigation (compilation hot path, per-request overhead, cache invalidation strategies with trade-offs) -5. Report: "Created issue #81. Policies are recompiled per-request with no caching. The main design decision is the cache invalidation strategy. A human must now accept or decline it and place it on the roadmap if accepted." +Report the issue URL, technical findings, uncertainties, and the human disposition needed. For subsequent authorized implementation, use `build-from-issue`. Every eventual PR from this issue-backed workflow must close an issue covering its own scope; split multi-PR efforts into separate closable issues and use a high-level issue only for tracking. diff --git a/.agents/skills/debug-inference/SKILL.md b/.agents/skills/debug-inference/SKILL.md deleted file mode 100644 index 08462a4751..0000000000 --- a/.agents/skills/debug-inference/SKILL.md +++ /dev/null @@ -1,408 +0,0 @@ ---- -name: debug-inference -description: Debug why inference.local, direct external inference, or supervisor-only system inference is failing. Use when the user cannot reach a local model server, has provider base URL issues, sees inference verification failures, hits protocol mismatches, or needs to diagnose inference on local vs remote gateways. Trigger keywords - debug inference, inference.local, system inference, sandbox-system, local inference, ollama, vllm, sglang, trtllm, NIM, inference failing, model server unreachable, failed to verify inference endpoint, host.openshell.internal. ---- - -# Debug Inference - -Diagnose why OpenShell inference is failing and recommend exact fix commands. - -Use `openshell` CLI commands to inspect the active gateway, provider records, managed inference config, and sandbox behavior. Use a short sandbox probe when needed to confirm end-to-end routing. - -## Overview - -OpenShell supports three inference paths. Diagnose the correct one first. - -1. **Managed inference** through `https://inference.local` - - Configured by `openshell inference set` - - Shared by every sandbox on the active gateway - - Credentials and model are injected by OpenShell -2. **Direct external inference** to hosts like `api.openai.com` - - Controlled by `network_policies` - - Requires the application to call the external host directly - - Requires provider attachment and network access to be configured separately -3. **System inference** used by platform functions - - Configured by `openshell inference set --system` - - Uses the `sandbox-system` route - - Consumed in-process by the sandbox supervisor and not exposed to sandbox user code through `inference.local` - -For local or self-hosted engines such as Ollama, vLLM, SGLang, TRT-LLM, and many NIM deployments, the most common managed inference pattern is an `openai` provider with `OPENAI_BASE_URL` pointing at a host the gateway can reach. - -## Prerequisites - -- `openshell` is on the PATH -- The active gateway is running -- You know the failing setup, or can infer it from commands and config - -## Tools Available - -Use these commands first: - -```bash -# Which gateway is active, and can the CLI reach it? -openshell status - -# Show both the user-facing and system inference routes -openshell inference get - -# Show only the supervisor-only system route -openshell inference get --system - -# Inspect the provider record referenced by the relevant route -openshell provider get - -# Inspect gateway topology details when remote/local confusion is suspected -openshell gateway info - -# Run a minimal end-to-end probe from a sandbox -openshell sandbox create -- curl https://inference.local/v1/chat/completions --json '{"messages":[{"role":"user","content":"hello"}],"max_tokens":10}' -``` - -## Workflow - -When the user asks to debug inference, run diagnostics automatically in this order. Stop and report findings as soon as a root cause is identified. - -### Determine Context - -Establish these facts first: - -1. Is sandbox code calling `https://inference.local`, is the application calling a direct external host, or is a platform function using system inference? -2. Which gateway is active, and is it local, remote, or cloud? -3. Which provider, model, and timeout are configured for the relevant route? -4. Is the upstream local to the gateway host, or somewhere else? - -### Step 0: Check the Active Gateway - -Run: - -```bash -openshell status -openshell gateway info -``` - -Look for: - -- Active gateway name and endpoint -- Whether the gateway is local or remote -- Whether `host.openshell.internal` would point to the local machine or a remote host - -Common mistake: - -- **Laptop-local model + remote gateway**: `host.openshell.internal` points to the remote gateway host, not your laptop. A laptop-local Ollama or vLLM server will not be reachable without a tunnel or shared reachable network path. - -### Step 1: Check Whether the Relevant Route Is Configured - -Run: - -```bash -openshell inference get -openshell inference get --system -``` - -Interpretation: - -- `openshell inference get` shows both the user-facing `inference.local` route and the system route. `--system` isolates the system route. -- **The `inference.local` route is `Not configured`**: managed inference has no backend. Configure it without `--system`: - - ```bash - openshell inference set --provider --model - ``` - -- **System inference is `Not configured`**: platform functions have no system backend. Configure it separately: - - ```bash - openshell inference set --system --provider --model - ``` - -- **Provider, model, and timeout shown**: Continue to provider inspection for the relevant route. - -### Step 2: Inspect the Provider Record - -Run: - -```bash -openshell provider get -``` - -Check: - -- Provider type matches the client API shape and is supported for managed inference - - `openai` for OpenAI-compatible engines such as Ollama, vLLM, SGLang, TRT-LLM, and many NIM deployments - - `anthropic` for Anthropic Messages API - - `nvidia` for NVIDIA-hosted OpenAI-compatible endpoints - - `deepinfra` for DeepInfra's OpenAI-compatible endpoint - - `google-vertex-ai` for Vertex AI; Claude models use Anthropic Messages and other models use OpenAI Chat Completions - - `aws-bedrock` only through a configured Bedrock-compatible bridge today -- Required credential key exists -- `*_BASE_URL` override is correct when using a self-hosted endpoint - -Fix examples: - -```bash -openshell provider create --name ollama --type openai --credential OPENAI_API_KEY=empty --config OPENAI_BASE_URL=http://host.openshell.internal:11434/v1 - -openshell provider update ollama --credential OPENAI_API_KEY=empty --config OPENAI_BASE_URL=http://host.openshell.internal:11434/v1 -``` - -`provider update` preserves the provider type and does not accept `--type`. Prefer bare credential keys, such as `--credential OPENAI_API_KEY`, when reading a real secret from the CLI environment. - -### Step 3: Check Local Host Reachability - -For host-backed local inference, confirm the upstream server: - -- Binds to `0.0.0.0`, not only `127.0.0.1` -- Runs on the same machine as the gateway -- Is reachable through `host.openshell.internal`, the host's LAN IP, or another reachable hostname - -Common mistakes: - -- **Base URL uses `127.0.0.1` or `localhost`**: usually wrong for managed inference. Replace with `host.openshell.internal` or the host's LAN IP. -- **Server binds only to loopback**: reconfigure it to bind to `0.0.0.0`. -- **Inference engine runs as a system service**: changing the bind address may require updating the service configuration and restarting the service before the new listener becomes reachable. - -### Step 4: Check Request Shape - -User-facing managed inference only works for `https://inference.local` and supported inference API paths. - -Supported patterns include: - -- `POST /v1/chat/completions` -- `POST /v1/completions` -- `POST /v1/responses` -- `POST /v1/embeddings` -- `POST /v1/messages` -- `GET /v1/models` -- `GET /v1/models/*` -- `POST /model/{modelId}/invoke` for bridge-fronted `aws-bedrock` - -Common mistakes: - -- **Wrong scheme**: `http://inference.local` instead of `https://inference.local` -- **Unsupported path**: request does not match a known inference API -- **Protocol mismatch**: Anthropic client against an `openai` provider, or vice versa -- **Provider-specific mismatch**: Vertex Claude requests must use `/v1/messages`; other Vertex models currently use `/v1/chat/completions`; Bedrock uses its model-in-path invoke shape - -Fix guidance: - -- Use a supported path and provider type -- Point OpenAI-compatible SDKs at `https://inference.local/v1` -- If the SDK requires an API key, pass any non-empty placeholder such as `test` - -### Step 5: Probe from a Sandbox - -This probe validates the user-facing `inference.local` route. It does not exercise supervisor-only system inference. - -Run a minimal request from inside a sandbox: - -```bash -openshell sandbox create -- curl https://inference.local/v1/chat/completions --json '{"messages":[{"role":"user","content":"hello"}],"max_tokens":10}' -``` - -Interpretation: - -- **`cluster inference is not configured`**: set the managed gateway route with `openshell inference set` -- **`connection not allowed by policy`** on `inference.local`: unsupported method or path -- **`no compatible route`**: provider type and client API shape do not match -- **Connection refused / upstream unavailable / verification failures**: base URL, bind address, topology, or credentials are wrong - -For system inference failures, inspect the platform function and sandbox supervisor/network logs after confirming `openshell inference get --system`. User code cannot call the `sandbox-system` route directly. - -### Step 6: Reapply or Repair the Managed Route - -After fixing the provider, use `update` for a partial change or `set` to replace the route: - -```bash -openshell inference set --provider --model -openshell inference update --provider -openshell inference update --model -openshell inference update --timeout 120 -``` - -Add `--system` to target the system route. Without it, these commands target `inference.local`. A timeout of `0` uses the 60-second default; increase it for models with long reasoning or idle streaming phases. - -If the endpoint is intentionally offline and you only want to save the config: - -```bash -openshell inference set --provider --model --no-verify -``` - -Use `--no-verify` only when the endpoint is intentionally offline or the provider protocol cannot be verified, such as the current bridge-fronted Bedrock flow. Inference updates are hot-reloaded to running sandboxes within about 5 seconds by default. - -### Step 7: Diagnose Direct External Inference - -If the application calls `api.openai.com`, `api.anthropic.com`, or another external host directly, this is not a managed inference issue. - -Check instead: - -1. The application is configured to call the external hostname directly -2. A provider with the needed credentials exists -3. The sandbox has that provider attached (`openshell sandbox provider list [name]`) -4. `network_policies` allow that host, port, and HTTP rules - -If the response reports `credential_endpoint_mismatch`, the provider is attached -but its credential profile does not authorize that request recipient. Run -`openshell provider get ` to identify the provider type, then -inspect its profile endpoints with -`openshell provider profile export -o yaml`. That export uses the current -workspace scope; add `--global` when the provider was created with -`--global-profile`. Compare the profile's endpoint host, port, and path with the -direct request. Correct the provider selection or profile endpoint when that -recipient is intentional. Do not widen the sandbox network policy to work around -the mismatch: policy admission and credential endpoint authorization are -separate checks, and the provider profile should authorize only intended -credential recipients. - -If the response reports `request_authority_mismatch`, compare the HTTP request -authority with the CONNECT tunnel endpoint. The host and effective port must -match. For a tunnel to `api.example.com:8443`, send -`Host: api.example.com:8443`; omitting the non-default port makes the request -authority use the transport default and OpenShell rejects it. An absolute-form -request target must use the same authority. - -Attach or detach a provider on an existing sandbox with `openshell sandbox provider attach ` and `openshell sandbox provider detach `. - -Use the `generate-sandbox-policy` skill when the user needs help authoring policy YAML. - -## Fix: Local Host Inference Timeouts (Firewall) - -Use this fix when a sandbox can reach `https://inference.local`, but OpenShell reports an upstream timeout against a host-local backend such as Ollama. - -Example symptom: - -```json -{"error":"request to http://host.docker.internal:11434/v1/models timed out"} -``` - -### When This Happens - -This failure commonly appears on Linux hosts that: - -- Run the OpenShell gateway in Docker -- Route `inference.local` to a host-local OpenAI-compatible endpoint such as Ollama -- Have a host firewall or networking configuration that denies container-to-host traffic by default - -In this case, OpenShell routing is usually working correctly. The failing hop is container-to-host traffic on the backend port. - -### Why CoreDNS Is Not the Cause - -This is not the same issue as the Colima CoreDNS fix. - -OpenShell injects `host.docker.internal` and `host.openshell.internal` into sandbox workloads when the selected compute platform supports it. That path bypasses runtime DNS lookup. If the request still times out, the usual cause is host firewall or network policy, not DNS. - -### Verify the Problem - -1. Confirm the model server works on the host: - - ```bash - curl -sS http://127.0.0.1:11434/v1/models - ``` - -2. Confirm the host gateway address also works on the host: - - ```bash - curl -sS http://172.17.0.1:11434/v1/models - ``` - -3. Test the same endpoint from a gateway or sandbox container on the Docker network: - - ```bash - docker ps --filter name=openshell --format '{{.Names}}' - docker exec wget -qO- -T 5 http://host.docker.internal:11434/v1/models - ``` - -If steps 1 and 2 succeed but step 3 times out, the host firewall or network configuration is blocking the container-to-host path. - -### Fix - -Allow the Docker bridge network used by the OpenShell gateway and sandbox containers to reach the host-local inference port. The exact command depends on your firewall tooling (iptables, nftables, firewalld, UFW, etc.), but the rule should allow: - -- **Source**: the Docker bridge subnet used by OpenShell containers (commonly `172.18.0.0/16`) -- **Destination**: the host gateway IP injected into sandbox workloads for `host.docker.internal` (commonly `172.17.0.1`) -- **Port**: the inference server port (e.g. `11434/tcp` for Ollama) - -To find the actual values on your system: - -```bash -# Docker bridge subnet for the OpenShell network -docker network inspect $(docker network ls --filter name=openshell -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' - -# Host gateway IP visible from inside the container -docker exec cat /etc/hosts | grep host.docker.internal -``` - -Adjust the source subnet, destination IP, or port to match your local Docker network layout. - -### Verify the Fix - -1. Re-run the container network check: - - ```bash - docker exec wget -qO- -T 5 http://host.docker.internal:11434/v1/models - ``` - -2. Re-test from a sandbox: - - ```bash - curl -sS https://inference.local/v1/models - ``` - -Both commands should return the upstream model list. - -### If It Still Fails - -- Confirm the backend listens on a host-reachable address: `ss -ltnp | rg ':11434\b'` -- Confirm the provider points at the host alias path you expect: `openshell provider get ` -- Confirm the active inference route: `openshell inference get` -- Inspect sandbox logs for upstream timeout details: `openshell logs --since 10m` - -## Common Failure Patterns - -| Symptom | Likely cause | Fix | -|---------|--------------|-----| -| `openshell inference get` shows `Not configured` | No managed inference route configured | `openshell inference set --provider --model ` | -| System inference is `Not configured` | Platform-only route has no backend | `openshell inference set --system --provider --model ` | -| `failed to verify inference endpoint` | Bad base URL, wrong credentials, wrong provider type, or upstream not reachable | Fix provider config, then rerun `openshell inference set`; use `--no-verify` only when the endpoint is intentionally offline | -| Base URL uses `127.0.0.1` | Loopback points at the wrong runtime | Use `host.openshell.internal` or another gateway-reachable host | -| Local engine works only when gateway is local | Gateway moved to remote host | Run the engine on the gateway host, add a tunnel, or use direct external access | -| `connection not allowed by policy` on `inference.local` | Unsupported path or method | Use a supported inference API path | -| `no compatible route` | Provider type does not match request shape | Create or select a provider of the matching type, or change the client API | -| `inference.local` works but a platform function fails | User route is configured but `sandbox-system` is missing or wrong | `openshell inference get --system`; configure or update with `--system`; inspect supervisor logs | -| Direct call to external host is denied | Missing policy or provider attachment | Update `network_policies` and launch sandbox with the right provider | -| Direct call returns `credential_endpoint_mismatch` | Attached provider profile does not authorize the request host, port, or path | Inspect the provider profile endpoints; select or update the profile only if it intentionally authorizes that recipient | -| Direct call returns `request_authority_mismatch` | HTTP authority does not match the CONNECT host and effective port | Include the explicit non-default port in `Host` and use the same authority in absolute-form targets | -| SDK fails on empty auth token | Client requires a non-empty API key even though OpenShell injects the real one | Use any placeholder token such as `test` | -| Upstream timeout from container to host-local backend | Host firewall or network config blocks container-to-host traffic | Allow the Docker bridge subnet to reach the inference port on the host gateway IP (see firewall fix section above) | - -## Full Diagnostic Dump - -Run this when you want a compact report before deciding on a fix: - -```bash -echo "=== Gateway Status ===" -openshell status - -echo "=== Gateway Info ===" -openshell gateway info - -echo "=== Managed Inference ===" -openshell inference get - -echo "=== System Inference Only ===" -openshell inference get --system - -echo "=== Providers ===" -openshell provider list - -echo "=== Selected Provider ===" -openshell provider get - -echo "=== Sandbox Probe ===" -openshell sandbox create -- curl https://inference.local/v1/chat/completions --json '{"messages":[{"role":"user","content":"hello"}],"max_tokens":10}' -``` - -When you report back, state: - -1. Which inference path is failing (`inference.local`, direct external, or system inference) -2. Whether gateway topology is part of the problem -3. The most likely root cause -4. The exact fix commands the user should run diff --git a/.agents/skills/debug-openshell-cluster/SKILL.md b/.agents/skills/debug-openshell-cluster/SKILL.md deleted file mode 100644 index 8e51a73932..0000000000 --- a/.agents/skills/debug-openshell-cluster/SKILL.md +++ /dev/null @@ -1,632 +0,0 @@ ---- -name: debug-openshell-cluster -description: Debug why an OpenShell gateway deployment is unhealthy, unreachable, or unable to create sandboxes. Use for gateway health failures, Docker/Podman runtime issues, Helm failures, Kubernetes scheduling, TLS or auth, gateway interceptors, supervisor middleware startup or runtime failures, external compute-driver sockets, VM drivers, or sandbox startup. Trigger keywords - debug gateway, gateway failing, deployment failing, helm install failing, cluster health, gateway health, gateway not starting, health check failed, sandbox pending, docker driver, podman driver, kubernetes driver, external driver, compute driver socket, gateway interceptor, supervisor middleware, middleware failed, vm driver. ---- - -# Debug OpenShell Gateway Deployment - -Diagnose a gateway and its selected compute platform. Do not assume OpenShell provisions Kubernetes or runs a k3s container. OpenShell targets a reachable gateway endpoint backed by Docker, Podman, Kubernetes, the experimental VM driver, or an operator-managed out-of-tree compute driver. - -Use `openshell` first to identify the active endpoint. Then use the platform tools that match the gateway's compute driver: `docker`, `podman`, `kubectl`/`helm`, or VM driver logs. - -## Overview - -The target deployment flow is: - -1. Operator starts or deploys the gateway with system packages, systemd, Helm, or a development task. The CLI does not start, stop, or destroy gateway services. -2. Operator configures the compute driver. -3. Operator provides the CLI and supervisor authentication material required by the deployment mode: edge or OIDC user auth, optional CLI mTLS, and gateway-minted sandbox JWTs. -4. The CLI registers a reachable gateway endpoint with `openshell gateway add`. -5. The gateway creates sandboxes through the selected compute driver. - -The standard gateway binary explicitly installs its compiled Docker, Podman, -Kubernetes, and VM registrations at startup. With no configured driver, the -gateway probes only installed registrations in priority order (Kubernetes, -Podman, then Docker); VM has no probe and remains opt-in. A custom gateway -binary may install a different set, so confirm the binary's registered drivers -when auto-detection reports that no suitable driver is available. - -For local evaluation only, TLS may be disabled and the gateway can be reached through `http://127.0.0.1:`. - -## Prerequisites - -- The `openshell` CLI must be available for endpoint checks. -- Know the active gateway name and endpoint, or be able to inspect local gateway metadata. -- Know the compute platform: Docker, Podman, Kubernetes, VM, or an out-of-tree driver. -- For Kubernetes: `kubectl` must target the cluster that hosts OpenShell and Helm version 3 or later must be available. -- For Docker or Podman: the runtime socket must be reachable from the gateway host. - -## Workflow - -Run diagnostics in order and stop once the root cause is clear. - -### Step 1: Check CLI Reachability - -```bash -openshell gateway list --output json -openshell gateway info -openshell status -``` - -For a one-off endpoint check that bypasses stored gateway selection and metadata: - -```bash -openshell --gateway-endpoint status -``` - -Common findings: - -- `No active gateway`: register one with `openshell gateway add `. -- Connection refused: gateway process is not running, service exposure is wrong, or a port-forward/proxy is not active. -- TLS/certificate errors: the endpoint scheme or trust chain is wrong, a local mTLS bundle does not match the gateway CA, or TLS termination does not match the gateway listener. -- `Unauthenticated` from an edge or OIDC gateway: refresh stored credentials with `openshell gateway login [name]`, then retry. Use `gateway logout` only when intentionally clearing local credentials. -- A direct development endpoint with a private or self-signed certificate can be isolated with `--gateway-endpoint --gateway-insecure`; do not persist or recommend insecure verification for shared gateways. - -### Step 2: Identify the Compute Platform - -Use gateway metadata, deployment values, or the user's setup notes to identify the driver. - -| Platform | Primary checks | -|---|---| -| Docker | Gateway process logs, Docker daemon health, sandbox containers, image pulls. | -| Podman | Podman socket, rootless networking, sandbox containers, image pulls. | -| Kubernetes | Helm release, gateway workload, service, secrets, sandbox pods, events. | -| VM | VM driver logs, rootfs availability, host virtualization support. | -| Extension | External driver process, Unix socket ownership/mode, configured driver name, capability handshake, gateway logs. | - -### Step 3: Check Gateway Startup Dependencies - -Before debugging the compute platform, inspect gateway logs for failures in dependencies initialized before the listener becomes ready. - -For out-of-tree compute drivers, confirm the selected driver name and socket agree across CLI flags or `gateway.toml`, and that the operator-owned driver is running before the gateway starts: - -```bash -rg -n 'compute_drivers|socket_path' /etc/openshell/gateway.toml -stat /run/openshell/.sock -journalctl -u --no-pager --lines=200 -journalctl -u openshell-gateway --no-pager --lines=200 -``` - -Custom names use `[openshell.drivers.].socket_path`. A launch-time `--compute-driver-socket` override may also use `docker`, `podman`, `kubernetes`, or `vm`; the endpoint then takes precedence over built-in construction. First-party standalone drivers require the socket parent directory to be owned by the driver's effective UID, force its mode to `0700`, create the socket with mode `0600`, and accept only peers with that same UID. Check the parent and socket separately with `stat`; a gateway running under a different UID cannot connect even when filesystem permissions or group membership would otherwise allow it. Operator-supplied drivers must provide equivalent access control appropriate to their implementation. Check gateway logs for connection errors, `GetCapabilities` failures, or an unexpected advertised driver name. The advertised name is diagnostic metadata; negotiated features control optional behavior. The gateway does not create or supervise operator-supplied driver processes or sockets. - -For configured gateway interceptors, inspect `[[openshell.gateway.interceptors]]`, their Unix or network endpoints, and gateway startup logs: - -```bash -rg -n 'interceptors|provider_profile_sources|grpc_endpoint|tls_ca_cert_path|audience|allow_insecure_transport|binding_policy|failure_policy|gateway_jwt' /etc/openshell/gateway.toml -stat /run/openshell/interceptors/.sock -journalctl -u --no-pager --lines=200 -journalctl -u openshell-gateway --no-pager --lines=200 -``` - -The gateway calls each interceptor's `Describe` RPC and validates its manifest at startup. Check for unreachable endpoints, invalid RPC/phase bindings, strict `allowlist` or `exact` mismatches, and `post_commit` bindings that resolve to `fail_closed`. If gateway JWT signing is enabled, authenticated network interceptors require HTTPS and a valid bearer token; check the private CA path, endpoint hostname, expected audience, issuer, `kid`, and interceptor logs for token rejection. `allow_insecure_transport = true` explicitly preserves unauthenticated plaintext behavior. If `provider_profile_sources` names an interceptor, that interceptor must advertise provider-profile capability and return a valid, duplicate-free catalog. A selected interceptor-only source is authoritative; include `builtin` or `user` sources explicitly when composition is intended. - -For operator-run supervisor middleware, inspect `[[openshell.supervisor.middleware]]`, service reachability, and both gateway and supervisor logs: - -```bash -rg -n 'supervisor|middleware|grpc_endpoint|tls_ca_cert_path|audience|allow_insecure_transport|max_payload_bytes|timeout|gateway_jwt' /etc/openshell/gateway.toml -journalctl -u --no-pager --lines=200 -journalctl -u openshell-gateway --no-pager --lines=200 -openshell logs --tail --source sandbox -``` - -The middleware service must start before the gateway and be reachable from both the gateway and sandbox supervisors. Gateway startup fails if `Describe` is unavailable, a manifest exposes duplicate operation/phase bindings, the registration claims the reserved `openshell/` namespace, or payload and timeout limits are invalid. Supported V1 bindings are `HTTP_REQUEST/PRE_CREDENTIALS` and `WEBSOCKET_MESSAGE/PRE_CREDENTIALS`. When gateway JWT signing is disabled, supervisors preserve the legacy unauthenticated connector and do not request extension credentials. When signing is enabled, credential acquisition and verification failures are fail closed: check HTTPS trust and hostname validation, audience and issuer agreement, the token `kid`, gateway `RefreshSandboxToken` errors, and middleware logs. Changing a registration requires a gateway restart. A policy update can also fail before persistence if the selected implementation rejects its `network_middlewares` config. - -At request time, distinguish attachment, binding selection, coverage, denial, and failure. A host-matched HTTP-only attachment can inspect the upgrade GET but does not join the WebSocket chain; the connection proceeds under either `on_error` mode and emits `binding_not_selected` coverage. A selected WebSocket stage receives text messages only. Binary messages pass under both modes, emit `unsupported_message_type` coverage, and consume a session sequence without an RPC. An explicit `middleware_denied` result is always enforced. WebSocket preflight returns `INSPECT`, voluntary `SKIP`, or authoritative `DENY`; `DENY` rejects the upgrade before upstream contact under both `on_error` modes. A selected-stage failure follows the policy-local `on_error`: `fail_closed` blocks the HTTP request or closes the WebSocket, while `fail_open` bypasses only that stage and emits a detection finding. A fail-open per-message capacity failure bypasses that message without disabling the stage. A timeout, transport failure, stream closure, missing or invalid response, duplicate or regressed sequence, or other failure that makes an established WebSocket stream unreliable disables that stage for later messages on the connection and emits `openshell.middleware.websocket_stage_disabled`. Confirm preflight, session-start, and session-end in service logs. OpenShell best-effort sends at most one session-end to each still-writable opened stage, including a preflight that terminates before session start; distinguish `MIDDLEWARE_DENIAL` from `MIDDLEWARE_FAILURE`. WebSocket message sequences are allocated session-wide; each stage receives a strictly increasing subset, so gaps are valid when binary messages or other units are not delivered to that stage. Zero, duplicate, or regressed sequences are protocol errors. If a running supervisor cannot install a new registry, it preserves its last-known-good generation and emits a configuration failure event. - -For network policy validation failures, first distinguish a gateway mutation -rejection from a supervisor runtime rejection. Direct policy updates, -incremental merges and approvals, provider attachments, and provider-profile -fanout are validated against the complete effective policy before persistence -when the gateway knows the affected sandbox scope. A `FAILED_PRECONDITION` -ambiguity response means no invalid revision or partial fanout was stored. -Supervisor validation remains defense in depth for startup, races, and policy -sources outside those mutation paths. - -Runtime rejection behavior is configured only in `gateway.toml`: - -```toml -[openshell.gateway] -policy_validation_failure_mode = "fail_closed" -``` - -The default `fail_closed` mode deactivates the previous generation, closes -pinned relays, and quarantines new egress until a valid generation loads. -`retain_last_valid` explicitly keeps the previous valid policy active; without -one it still fails closed. Restart the gateway after changing this field. -Inspect sandbox OCSF configuration and finding events for the validation -rationale, configured and effective modes, active generation, and the explicit -`previous_policy_active` state. - -### Step 4: Check Docker-Backed Gateways - -```bash -docker info -docker ps --filter name=openshell -docker logs --tail=200 -docker run --rm --entrypoint /openshell-sandbox "${OPENSHELL_DOCKER_SUPERVISOR_IMAGE:-ghcr.io/nvidia/openshell/supervisor:latest}" --version -openshell status -``` - -For Docker GPU failures, check CDI support and NVIDIA CDI discovery separately: - -```bash -docker info --format '{{json .CDISpecDirs}}' -docker info --format '{{json .DiscoveredDevices}}' -for dir in /etc/cdi /var/run/cdi; do - if [ -d "$dir" ]; then - find "$dir" -maxdepth 1 -type f \( -name '*.yaml' -o -name '*.json' \) -print - else - echo "$dir missing" - fi -done -systemctl is-enabled nvidia-cdi-refresh.service nvidia-cdi-refresh.path || true -systemctl is-active nvidia-cdi-refresh.service nvidia-cdi-refresh.path || true -systemctl status nvidia-cdi-refresh.service nvidia-cdi-refresh.path --no-pager --lines=50 -journalctl -u nvidia-cdi-refresh.service --no-pager --lines=100 -``` - -When the NVIDIA Container Toolkit CDI refresh units are not enabled or no NVIDIA CDI spec has been generated, enable them and trigger a refresh: - -```bash -sudo systemctl enable --now nvidia-cdi-refresh.path -sudo systemctl enable --now nvidia-cdi-refresh.service -sudo systemctl restart nvidia-cdi-refresh.service -docker info --format '{{json .DiscoveredDevices}}' -``` - -Common findings: - -- Docker daemon unavailable: start Docker Desktop or Docker Engine. -- Gateway process stopped: inspect exit status and logs. -- Sandbox image missing or pull denied: verify image reference and registry credentials. -- Sandbox fails before readiness with an identity-resolution error: inspect the image's OCI `USER` and matching `/etc/passwd` and `/etc/group` entries, or explicitly set both process identity fields in policy. Numeric workload identities `1` through `4294967294` are accepted; root, the invalid identity sentinel, and missing identities are rejected. -- Sandbox fails before readiness with an OCI workspace validation error: inspect the image's `WorkingDir` using the immutable image ID reported by the gateway. Empty, `/`, and explicit `/sandbox` use the managed `/sandbox` compatibility workspace. Any other workdir must be an absolute normalized directory with no symlink components; the final policy UID, primary GID, and supplementary groups must pass the kernel's effective traverse/write checks, including POSIX ACL and LSM decisions. OpenShell does not create, chown, or chmod a non-default image workdir. -- Docker also rejects an image `VOLUME` that covers the workdir or one of its parents because the runtime would mask the immutable path before validation. Move the `VOLUME` below the workspace or remove the declaration. -- A workdir rejected as a special filesystem or OpenShell control-path collision cannot be made valid with permissions. Move the image workdir away from kernel-backed mounts and the concrete supervisor, TLS, token, runtime, and socket paths named in the error. -- Docker driver cannot initialize because it cannot find `openshell-sandbox`: verify `OPENSHELL_DOCKER_SUPERVISOR_BIN`, the sibling binary next to `openshell-gateway`, or the configured supervisor image contains `/openshell-sandbox`. -- Sandbox never registers: check gateway logs and supervisor callback endpoint. -- On macOS, repeated `Policy fetch failed after 5 attempts` messages with a - Homebrew gateway bound to `[::1]:17670` indicate that the Docker - `host-gateway` IPv4 route has no matching callback listener. Current releases - leave `bind_address` unset in the Homebrew config, use the built-in - `127.0.0.1:17670` primary listener, and reuse it for authenticated sandbox - callbacks. On an older release, set `bind_address = "127.0.0.1:17670"` or - upgrade. -- Supervisor image exits before printing `openshell-sandbox --version`: the image should be the scratch supervisor image from `deploy/docker/Dockerfile.supervisor` and must contain a static executable at `/openshell-sandbox`. -- A sandbox with explicit `protocol: tcp` endpoints fails before workload readiness: confirm the Docker or Podman driver supplied the `policy-dns-transparent-tcp` runtime capability and inspect supervisor logs for missing `nft`, synthetic-route overlap, or namespace-local DNS/TCP listener bind failures. Kubernetes, VM, sidecar, and out-of-tree drivers must reject this policy until they provide the complete substrate; use omitted protocol with an explicit proxy on those runtimes. -- `mise run e2e:docker:gpu` fails with `docker info --format json did not report any discovered NVIDIA CDI GPU devices`: Docker may report `CDISpecDirs` while still having no generated NVIDIA CDI specs. Verify `.DiscoveredDevices` contains entries such as `nvidia.com/gpu=all`, verify `/etc/cdi` or `/var/run/cdi` contains a generated NVIDIA spec, and check that `nvidia-cdi-refresh.service` and `nvidia-cdi-refresh.path` from NVIDIA Container Toolkit are enabled and healthy. The service is a one-shot unit, so `inactive (dead)` can be normal after a successful run; use `systemctl status` and `journalctl` to distinguish success from a skipped or failed refresh. NVIDIA recommends enabling the path and service units, and restarting `nvidia-cdi-refresh.service` to regenerate missing or stale CDI specs. If specs are generated but Docker still reports no discovered devices, restart Docker or reload the daemon and re-check `docker info`. - -For source checkout development, restart the local gateway with: - -```bash -mise run gateway:docker -``` - -During a graceful gateway restart, Docker, Podman, and VM sandboxes with -running intent should stop before the gateway exits and restart after it -returns. Check for `Stopped sandbox during gateway shutdown` and `Started -sandbox during gateway startup` in gateway logs. A sandbox explicitly stopped -through the CLI remains stopped. Kubernetes sandboxes are cluster-owned and do -not follow this local gateway lifecycle. Internal and external drivers follow -the same rule: `GetCapabilities.gateway_manages_lifecycle` must be true for the -gateway to run shutdown and startup sweeps. - -### Step 5: Check Podman-Backed Gateways - -```bash -podman info -podman ps --filter name=openshell -podman logs --tail=200 -openshell status -``` - -Common findings: - -- Podman socket unavailable: start or expose the user socket. -- Rootless networking unavailable: inspect Podman network configuration. -- Sandbox image missing or pull denied: verify image reference and registry credentials. -- Sandbox fails before readiness with an identity-resolution error: inspect the image's OCI `USER` and matching `/etc/passwd` and `/etc/group` entries, or explicitly set both process identity fields in policy. Numeric workload identities `1` through `4294967294` are accepted; root, the invalid identity sentinel, and missing identities are rejected. -- Supervisor cannot call back: check callback endpoint and gateway logs. -- A sandbox with explicit `protocol: tcp` endpoints fails before readiness: - inspect supervisor logs for policy DNS port-53 binding, synthetic-route, or - nftables redirect failures. Rootless Podman must provide these primitives - inside the supervisor-owned nested network namespace; setup fails closed. -- Gateway exits before becoming healthy with a callback-listener discovery - error: inspect `podman info --debug`, the configured Podman network, and the - host's IPv4 default route. Rootless pasta uses the private source address - selected by that route; rootful Podman uses the bridge gateway address. -- Current gateways reuse the primary listener when it covers Podman's callback - address. If the primary does not cover that address, inspect the gateway - startup logs for the additional callback-only listener and its provenance. -- Rootless slirp4netns, another named helper, or missing helper metadata - requires an explicitly remote `grpc_endpoint`. An explicit `host_gateway_ip` - cannot bypass slirp4netns host-loopback isolation. Do not work around - discovery failures by broadening the primary gateway listener to `0.0.0.0`. - -When `userns` is configured (e.g. `userns = "auto"` or `userns = "keep-id"`): - -- Supervisor delivery uses bind-mount fallback instead of image volumes because - overlay mounts do not support `idmapped` mounts. The supervisor binary is - extracted from the supervisor image and cached at - `$XDG_DATA_HOME/openshell/podman-supervisor/` (typically - `~/.local/share/openshell/podman-supervisor/`). -- Stale cache: if the supervisor image is updated but the cached binary is not - refreshed, sandbox creation may fail with an ELF validation error or version - mismatch. Remove the cache directory and retry. -- `auto` mode requires subuid/subgid ranges for the current user in - `/etc/subuid` and `/etc/subgid`. If missing, Podman returns a user-namespace - mapping error at container creation. -- `private` mode requires explicit `uidmap` and `gidmap` arrays in the TOML - config. Without both, the gateway rejects the config at startup. - Rootless Podman uses intermediate IDs (e.g. `uidmap = ["0:0:1", "1:1:65535"]`); - rootful Podman uses absolute host IDs (e.g. `uidmap = ["0:1000:1", "1:100000:65536"]`). -- `nomap` (without hyphen) is accepted as input but canonicalized to `no-map` - for Podman's API. - -### Step 6: Check Kubernetes Helm Gateways - -```bash -helm -n openshell status openshell -helm -n openshell get values openshell -kubectl -n openshell get deployment,statefulset,pod,svc,pvc -kubectl -n openshell logs deployment/openshell -c openshell-gateway --tail=200 -kubectl -n openshell logs statefulset/openshell -c openshell-gateway --tail=200 -kubectl -n openshell rollout status deployment/openshell -kubectl -n openshell rollout status statefulset/openshell -``` - -Use the log and rollout commands for the workload kind that exists in the -release. Look for failed installs, unexpected values, missing namespace, wrong -image tag, TLS settings that do not match the registered endpoint, and -scheduling failures. - -`server.telemetryEnabled` renders `OPENSHELL_TELEMETRY_ENABLED` on the gateway -pod, and the gateway propagates the effective value to sandbox supervisors. - -When no external credential driver is enabled, the Helm chart uses the -gateway's default encrypted database credential storage. The chart creates a -retained Kubernetes Secret for the shared KEK, injects it into gateway pods, and -stores encrypted credential envelopes in the OpenShell database. For -`workload.kind=deployment` or multi-replica gateways, confirm -`server.externalDbSecret` points at a shared database. A render/install error -mentioning `server.credentialDrivers` means the values selected multiple -external credential backends. - -For HA or PostgreSQL-backed installs, also check the external database Secret -referenced by `server.externalDbSecret` and the PostgreSQL workload if the test -or operator deployed one in-cluster: - -```bash -kubectl -n openshell get secret openshell-ha-pg -o yaml -kubectl -n openshell get deployment,service,pod -l app.kubernetes.io/name=openshell-e2e-postgres -kubectl -n openshell logs deployment/openshell-e2e-postgres --tail=200 -``` - -Check required Helm deployment secrets: - -```bash -kubectl -n openshell get secret \ - openshell-server-tls \ - openshell-server-client-ca \ - openshell-client-tls \ - openshell-jwt-keys -``` - -In cert-manager installs, `certManager.enabled=true` makes cert-manager own TLS -generation. The Helm chart should still render the `openshell-certgen` -pre-install/pre-upgrade hook in JWT-only mode to create `openshell-jwt-keys`, -even if `pkiInitJob.enabled` remains true. -If the gateway pod is pending with `MountVolume.SetUp failed for volume -"sandbox-jwt"` and `openshell-jwt-keys` is absent, inspect the rendered -`templates/certgen.yaml` output and the hook Job logs; cert-manager creates TLS -Secrets but does not create the sandbox JWT signing Secret. - -If the gateway exits with `failed to read sandbox JWT signing key from -/etc/openshell-jwt/signing.pem`, verify that `openshell-jwt-keys` contains -`signing.pem`, `public.pem`, and `kid`, and that the gateway workload mounts the -`sandbox-jwt` secret at `/etc/openshell-jwt`. The sandbox JWT mount is required -even when local Helm values disable TLS. - -If `certManager.serverIssuerRef` points the server certificate at an external -Issuer or ClusterIssuer (for example an ACME issuer, for a publicly-trusted -cert on an OpenShift `Route` with TLS passthrough — see -`openshiftRoute.enabled`), the chart creates **two** server certificates: an -internal one (chart CA, internal SANs) and an external one (from the configured -issuer, external SANs only). The gateway uses SNI to present the right cert. - -Check the external `Certificate`/`CertificateRequest`/`Challenge` resources -directly when the external secret never becomes Ready: - -```bash -kubectl -n openshell get certificate,certificaterequest,challenge -kubectl -n openshell describe certificate openshell-server-external -oc -n openshell get route -``` - -ACME issuers reject certificate requests that include internal-only names -(`*.svc.cluster.local`, `localhost`, loopback IPs) and require the -`commonName` to also be a SAN — the external `Certificate` only requests the -hostnames in `certManager.serverDnsNames`, for exactly this reason. - -If sandbox supervisors fail their TLS handshake to the gateway with -`UnknownCA` after configuring `serverIssuerRef`, the most likely cause is -`server.grpcEndpoint` set to the external hostname. This forces supervisors -to connect via the external hostname, receiving the ACME cert (via SNI) which -they cannot verify against the chart CA. Remove `server.grpcEndpoint` or set -it to the internal service name so supervisors receive the internal cert: - -```bash -helm -n openshell get values openshell | grep -E 'grpcEndpoint|clientCaFromServerTlsSecret|clientCaSecretName|serverIssuerRef|caSecretName' -# server.grpcEndpoint should be unset or point to internal service name -``` - -Less commonly, `UnknownCA` can occur if the gateway's client-verification CA -is misconfigured. The default `clientCaFromServerTlsSecret=true` is correct -for all configurations — the internal server certificate is always signed by -the chart CA (the same CA that signs the client cert), so its `ca.crt` is -the right trust anchor. Only override this if you intentionally mount a -separate client CA via `server.tls.clientCaSecretName`. Verify the mounted -client CA matches the CA that signed the client certificate: - -```bash -kubectl -n openshell get statefulset openshell -o jsonpath='{.spec.template.spec.volumes[?(@.name=="tls-client-ca")]}' | jq . -# Should show items filter for ca.crt from openshell-server-tls -``` - -If `server.providerTokenGrants.spiffe.enabled=true`, the gateway should still -render `[openshell.gateway.gateway_jwt]` and mount the `sandbox-jwt` Secret. -SPIRE is used only by sandbox pods for dynamic provider token grants. Verify -that SPIRE is installed, the CSI driver is available, and the Kubernetes driver -config includes `provider_spiffe_workload_api_socket_path`: - -```bash -helm -n openshell get values openshell | grep -E 'providerTokenGrants|workloadApiSocketPath' -kubectl get pods -A | grep -E 'spire|spiffe' -kubectl -n openshell get configmap openshell-config -o yaml | grep provider_spiffe_workload_api_socket_path -``` - -Sandbox pods using provider token grants should have an -`openshell.io/sandbox-id` annotation, an `openshell.ai/managed-by=openshell` -label, supervisor env vars `OPENSHELL_K8S_SA_TOKEN_FILE` and -`OPENSHELL_PROVIDER_SPIFFE_WORKLOAD_API_SOCKET`, plus both the projected -`openshell-sa-token` volume and the `spiffe-workload-api` CSI volume. - -Check the image references currently used by the gateway deployment: - -```bash -kubectl -n openshell get deployment openshell -o jsonpath="{.spec.template.spec.containers[*].image}{\"\n\"}{.spec.template.spec.containers[*].env[?(@.name==\"OPENSHELL_SUPERVISOR_IMAGE\")].value}{\"\n\"}" -kubectl -n openshell get statefulset openshell -o jsonpath="{.spec.template.spec.containers[*].image}{\"\n\"}{.spec.template.spec.containers[*].env[?(@.name==\"OPENSHELL_SUPERVISOR_IMAGE\")].value}{\"\n\"}" -helm -n openshell get values openshell | grep -E 'repository|tag|supervisorImage|workload' -``` - -The gateway image built from `deploy/docker/Dockerfile.gateway` and the scratch supervisor image built from `deploy/docker/Dockerfile.supervisor` should use the same build tag in branch and E2E deploys. A stale supervisor image can make sandbox behavior lag behind gateway policy or proto changes. - -For local/external pull mode (the default local path via `mise run cluster`), local images are tagged to the configured local registry base, pushed to that registry, and pulled by k3s via the `registries.yaml` mirror endpoint. The `cluster` task pushes prebuilt local tags (`openshell/*:dev`, falling back to `localhost:5000/openshell/*:dev` or `127.0.0.1:5000/openshell/*:dev`). - -Gateway image builds stage a partial Rust workspace from `deploy/docker/Dockerfile.images`. If cargo fails with a missing manifest under `/build/crates/...`, or an imported symbol exists locally but is missing in the image build, verify that every current gateway dependency crate, including `openshell-driver-docker`, `openshell-driver-kubernetes`, and `openshell-ocsf`, is copied into the staged workspace there. - -For plaintext local evaluation, confirm the chart has: - -```bash -helm -n openshell get values openshell | grep -E 'disableTls|grpcEndpoint' -``` - -Expected shape: - -```yaml -server: - disableTls: true - grpcEndpoint: http://openshell.openshell.svc.cluster.local:8080 -``` - -Check service exposure: - -```bash -kubectl -n openshell get svc openshell -o wide -kubectl -n openshell get endpoints openshell -``` - -For local port-forward testing: - -```bash -kubectl -n openshell port-forward svc/openshell 8080:8080 -openshell gateway add http://127.0.0.1:8080 --local --name local -openshell status -``` - -If the gateway is healthy but sandbox creation fails: - -```bash -kubectl -n openshell get pods -kubectl -n openshell get events --sort-by=.lastTimestamp | tail -n 50 -kubectl -n openshell logs deployment/openshell -c openshell-gateway --tail=200 -kubectl -n openshell logs statefulset/openshell -c openshell-gateway --tail=200 -``` - -Check the configured sandbox namespace: - -```bash -helm -n openshell get values openshell | grep sandboxNamespace -``` - -Then inspect sandbox resources in that namespace. - -Check the configured sandbox service account when TokenReview bootstrap or -sandbox registration fails. Helm creates a dedicated sandbox service account by -default and writes it to `[openshell.drivers.kubernetes].service_account_name`; -the gateway rejects projected tokens from other service accounts. - -```bash -helm -n openshell get values openshell | grep -A3 sandboxServiceAccount -kubectl -n get serviceaccount openshell-sandbox -kubectl -n openshell get configmap openshell-config -o jsonpath='{.data.gateway\.toml}' -kubectl -n get sandbox -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}' -``` - -If `topology = "sidecar"` is rendered under `[openshell.drivers.kubernetes]`, -sandbox pods should have an `openshell-network-init` init container running -`--mode=network-init`, an `agent` container running -`openshell-sandbox --mode=process`, and an `openshell-supervisor-network` -container running `--mode=network`. The init container owns nftables setup and -should be the only sidecar topology container with `NET_ADMIN`. It also needs -`CHOWN`/`FOWNER` to hand shared emptyDir state to the effective sidecar UID. The -default binary-aware network sidecar runs as UID 0 with primary GID -`sandbox_gid` and adds `SYS_PTRACE` plus `DAC_READ_SEARCH`. When -`process_binary_aware_network_policy = false`, it runs as the configured -non-root `proxy_uid` without those inspection capabilities. That dedicated -proxy UID must remain at least `1000` and must not match the workload UID -because the pod egress fence exempts its traffic. The pod `fsGroup` is set to -`sandbox_gid` in both modes. - -In sidecar topology only the network sidecar should mount the gateway bootstrap -credentials (`openshell-sa-token` and `openshell-client-tls`). The process -container should not receive `OPENSHELL_ENDPOINT`, gateway TLS env vars, the -sandbox token file, or those credential mounts. Instead, the network sidecar -serves policy and provider environment state over the Unix control socket from -`OPENSHELL_SIDECAR_CONTROL_SOCKET` (`/run/openshell-sidecar/control.sock` by -default). The process supervisor must be the first and only client. After -validating its peer UID, GID, and PID, the sidecar unlinks the listener. If the -connection later closes, the network sidecar exits non-zero so Kubernetes can -restart it with a fresh listener. If the process supervisor fails before -launching the workload, -inspect both containers for control-socket bind, connect, bootstrap, or update -errors. If new SSH/exec sessions do not pick up refreshed provider environment, -inspect the network sidecar settings-poll logs and the process container logs -for provider environment update handling; the process container should consume -newer provider-env revisions without receiving gateway credentials. - -The process container reports the workload entrypoint PID over the same control -socket, and the network sidecar uses that PID for binary-scoped policy -decisions through `/proc`. If rules with `policy.binaries` are unexpectedly -denied, inspect the sidecar control logs and confirm the pod has -`shareProcessNamespace: true`. -The shared state directory should preserve `sandbox_gid` inheritance -(`02775`). Sidecar SSH uses the Linux abstract socket -`@openshell-sidecar-ssh`; the network sidecar verifies its peer PID before -bridging gateway relay requests. No `ssh.sock` file should appear in the shared -state directory. -Inspect all three when sandbox registration or egress enforcement fails: - -```bash -kubectl -n openshell get configmap openshell-config -o jsonpath='{.data.gateway\.toml}' | grep -E '^\[openshell\.drivers\.kubernetes\]|^topology\s*=' -kubectl -n get pod -o jsonpath='{range .spec.initContainers[*]}{.name}{" "}{.command}{"\n"}{end}' -kubectl -n get pod -o jsonpath='{range .spec.containers[*]}{.name}{" "}{.command}{"\n"}{end}' -kubectl -n logs -c openshell-network-init --tail=200 -kubectl -n logs -c openshell-supervisor-network --tail=200 -kubectl -n logs -c agent --tail=200 -``` - -#### Corporate upstream proxy - -When the deployment routes sandbox egress through a corporate HTTP forward -proxy, the operator-owned settings render under `[openshell.drivers.kubernetes]` -from the Helm `upstreamProxy` values. Absent proxy configuration preserves -direct-dial egress; any present-but-invalid value fails closed at gateway -startup (`validate_upstream_proxy_config`) rather than silently reverting to a -direct connection. Confirm the rendered configuration first: - -```bash -kubectl -n openshell get configmap openshell-config -o jsonpath='{.data.gateway\.toml}' | grep -E 'https_proxy|no_proxy|proxy_auth_secret_(name|key)|proxy_auth_allow_insecure|proxy_connect_by_hostname' -helm -n openshell get values openshell | grep -A8 upstreamProxy -``` - -Only `http://host:port` forward proxies are supported; `https://` proxy URLs and -plain-HTTP egress are out of scope and rejected. Proxy credentials require -`topology = "sidecar"` — combined topology shares the credential mount with the -workload, so the gateway rejects credentials there. The credential Secret named -by `proxy_auth_secret_name` must exist in the sandbox namespace with the key -named by `proxy_auth_secret_key`, and Kubernetes will not create keys longer -than 253 bytes or named `.`/`..`. - -The proxy arguments and credential mount are injected only into the container -that runs network supervision (the `agent` container in combined topology, the -`openshell-supervisor-network` sidecar in sidecar topology). The one-shot -`openshell-network-init` container and the process `agent` container in sidecar -topology must never receive them. The credential is projected read-only as the -`openshell-upstream-proxy-auth` volume at `/run/openshell/upstream-proxy-auth` -and passed as `--upstream-proxy-auth-file`; it must never appear in env, -annotations, or command arguments. - -```bash -kubectl -n get secret -o jsonpath='{.data}' >/dev/null && echo "secret present" -kubectl -n get pod -o jsonpath='{range .spec.containers[*]}{.name}{" "}{.command}{"\n"}{end}' | grep -- '--upstream-' -kubectl -n get pod -o jsonpath='{range .spec.containers[*]}{.name}{": "}{range .volumeMounts[*]}{.name}{" "}{end}{"\n"}{end}' | grep upstream-proxy-auth -kubectl -n get events --sort-by=.lastTimestamp | grep -Ei 'secret|MountVolume' | tail -n 20 -``` - -A missing Secret or wrong key leaves the pod stuck with a -`MountVolume.SetUp failed` / `secret ... not found` event. If the pod starts but -egress still fails, the corporate proxy itself is the next suspect: policy- -approved TLS CONNECT requests that time out after policy evaluation usually mean -the proxy URL is unreachable from the sandbox namespace, or a cluster-internal -destination that should be direct is missing from `no_proxy`. Inspect the -network supervisor logs for CONNECT and upstream-proxy decisions: - -```bash -kubectl -n logs -c openshell-supervisor-network --tail=200 | grep -Ei 'upstream|connect|proxy' -``` - -### Step 7: Check VM-Backed Gateways - -Use the VM driver logs and host diagnostics available in the user's environment. Verify: - -- The VM driver process is running and reachable by the gateway. -- The runtime rootfs exists and matches the expected architecture. -- Host virtualization support is enabled. -- The sandbox supervisor can establish its callback connection to the gateway. - -Then run: - -```bash -openshell status -openshell logs -``` - -## Common Failure Patterns - -| Symptom | Likely cause | Check | -|---|---|---| -| `openshell status` fails | Gateway endpoint unreachable or auth mismatch | `openshell gateway info`, gateway logs | -| Gateway starts but sandbox create fails | Compute driver cannot reach runtime | Docker/Podman/Kubernetes/VM driver logs | -| Gateway exits while resolving compute-driver listener requirements | Callback alias topology is unsupported, the Podman network cannot be inspected, or the selected address is not private/authorized | Gateway startup error, `podman info --debug`, Podman network inspection, host IPv4 default route | -| Admin, health, reflection, or HTTP request is denied on an additional Docker/Podman callback-only listener | Additional callback listeners intentionally expose only sandbox-callable gRPC methods | Retry through the gateway's primary endpoint; inspect the listener-purpose startup log if the address was unexpected | -| Docker or Podman sandbox never registers | Wrong callback endpoint or supervisor startup failure | Gateway logs and sandbox container logs | -| Docker GPU e2e fails before GPU sandbox comparison | NVIDIA CDI specs are missing or Docker has not discovered them | `docker info --format '{{json .DiscoveredDevices}}'`, `/etc/cdi`, `/var/run/cdi`, `nvidia-cdi-refresh.service` | -| Kubernetes gateway pod pending | PVC unbound, taint, selector, or insufficient resources | `kubectl -n openshell describe pod ` | -| Kubernetes sandbox pod stuck pending, workspace PVC unbound | Cluster has no default `StorageClass` and OpenShell does not set `storageClassName` on the workspace PVC (clusters with a default `StorageClass` bind fine without it) | `kubectl -n openshell describe pvc`; set `server.workspaceStorageClass` (gateway config `workspace_storage_class`) to a valid `StorageClass` | -| Kubernetes gateway pod crash loops | Missing secret, bad DB URL, bad TLS config | `kubectl -n openshell logs deployment/openshell -c openshell-gateway` or `kubectl -n openshell logs statefulset/openshell -c openshell-gateway` | -| CLI TLS error | Local mTLS bundle does not match server cert/CA | Check `~/.config/openshell/gateways//mtls/` | -| Edge or OIDC gateway returns `Unauthenticated` | Stored login expired, audience/scopes mismatch, or gateway auth configuration changed | `openshell gateway info`, `openshell gateway login `, gateway auth logs | -| Gateway fails before serving health after enabling an interceptor | Interceptor endpoint unavailable or manifest/binding validation failed | Gateway and interceptor logs; interceptor socket; `binding_policy`, phases, and failure policy | -| Authenticated interceptor or middleware rejects gateway calls | Private CA or hostname mismatch, expected audience or issuer mismatch, stale/unknown `kid`, or malformed extension token | `tls_ca_cert_path`, registration `audience`, service verifier config and logs; fetch well-known metadata only through the already-trusted gateway TLS endpoint | -| Provider profiles disappear after enabling an interceptor catalog | `provider_profile_sources` selected only an authoritative interceptor or returned invalid/duplicate IDs | Inspect source list and interceptor `Describe`/catalog logs; include `builtin` and `user` when intended | -| Gateway fails after registering supervisor middleware | Service unavailable, invalid manifest, duplicate binding, reserved name, or invalid payload/timeout limit | Middleware service and gateway logs; `[[openshell.supervisor.middleware]]`; `Describe` response | -| Policy update rejects `network_middlewares` | Unknown middleware name, implementation-owned config invalid, duplicate order, broad/invalid host selector, or fail-closed coverage of `tls: skip` | Policy error, gateway logs, middleware `ValidateConfig`, selector and order fields | -| Policy mutation returns `FAILED_PRECONDITION` for endpoint ambiguity | Equally specific effective endpoint selectors disagree on connection or request-processing metadata | CLI error, base and provider-composed policy, affected profile attachments; confirm no new revision was stored | -| Supervisor enters policy quarantine | A runtime candidate failed validation while `policy_validation_failure_mode = "fail_closed"` | Sandbox OCSF config/finding events, validation rationale, active generation, `previous_policy_active` | -| HTTP request returns `middleware_failed` or `middleware_denied`, or WebSocket closes with `1008` | Selected stage failed or explicitly denied admitted traffic | Sandbox OCSF logs; policy-local middleware config; service availability; binding operation; `on_error` | -| WebSocket upgrades but a host-matched middleware receives no preflight or message RPC | The implementation did not advertise `WEBSOCKET_MESSAGE/PRE_CREDENTIALS` | `WEBSOCKET_MIDDLEWARE_COVERAGE state=binding_not_selected`; service `Describe`; the upgrade GET may still have used its HTTP binding | -| Binary WebSocket message passes without a middleware RPC | Binary is unsupported by the V1 text-message binding under both `on_error` modes | `WEBSOCKET_MIDDLEWARE_COVERAGE state=unsupported_message_type`; the next text RPC may have a valid sequence gap | -| WebSocket messages stop reaching middleware after one failure | A fail-open stage stream was disabled for the rest of the connection | `openshell.middleware.websocket_stage_disabled`; middleware timeout/stream/protocol logs. A per-message capacity bypass alone leaves the stage active. Reconnect to create a fresh stream after a genuine stream failure | -| Supervisor repeatedly fails to install middleware after enabling gateway JWT signing | Extension credential minting, distribution, or authenticated service connection failed; last-known-good registry remains active | Gateway `RefreshSandboxToken` logs, sandbox configuration events, service token-verification logs, registration TLS/audience settings | -| Custom compute driver is unavailable | Driver process/socket missing, inaccessible, or selected name does not match its endpoint/config key | Socket ownership/mode, driver service logs, gateway `GetCapabilities` logs | -| Sandbox remains `Stopping` or `Starting` | Driver stop/start failed, retained resource is missing, or a fresh supervisor has not connected | Gateway and driver logs; `docker inspect`, `podman inspect`, Agent Sandbox status/PVC, or VM state marker and launcher process | -| Image pull failure | Gateway or sandbox image cannot be pulled | Runtime events and image pull credentials | -| `K8s namespace not ready` with `envoy-gateway-openshell.yaml: the server could not find the requested resource` | Optional Gateway API manifest was applied without Envoy Gateway CRDs, or k3s Helm controller startup exceeded the namespace wait | Apply `deploy/kube/manifests/envoy-gateway-openshell.yaml` manually only after Envoy Gateway is installed and `grpcRoute` is enabled | -| HTTPS ingress (`grpcRoute.gateway.listener.protocol=HTTPS`) connection resets or TLS handshake hangs | Envoy terminates TLS but the gateway pod still expects TLS, so the plaintext backend hop fails | Set `server.disableTls=true` so Envoy forwards plaintext to the pod; verify the listener `certificateRefs` Secret exists in the release namespace and `openshell status` over `https://` | -| HTTPS ingress returns `Unauthenticated` after connecting | TLS terminates at Envoy, so the gateway never sees a client cert; no OIDC issuer is configured for identity | Configure `server.oidc.issuer` and register with `openshell gateway add https:// --oidc-issuer `, or set `server.auth.allowUnauthenticatedUsers=true` for a trusted-proxy/dev cluster | -| External server `Certificate` never becomes Ready with `certManager.serverIssuerRef` set | ACME issuer rejected internal-only SANs, a loopback IP, or a `commonName` absent from the SANs | `kubectl -n openshell describe certificate openshell-server-external`; confirm `certManager.serverDnsNames` lists only real, externally-resolvable hostnames | -| Sandbox supervisors fail TLS handshake with `UnknownCA` after configuring `certManager.serverIssuerRef` | `server.grpcEndpoint` is set to the external hostname, forcing supervisors to receive the ACME cert (via SNI) which they can't verify against chart CA | Remove `server.grpcEndpoint` or set it to the internal service name; supervisors should connect via internal service name to receive the internal cert | - -## Reporting - -When handing results back to the user, include: - -- Active gateway endpoint and auth mode. -- Compute platform and driver. -- Gateway process or workload status. -- Recent gateway log summary. -- Missing or malformed TLS, OIDC/mTLS, or sandbox JWT material. -- Service exposure status. -- Sandbox workload status. -- The exact command that failed and the shortest fix. diff --git a/.agents/skills/fix-security-issue/SKILL.md b/.agents/skills/fix-security-issue/SKILL.md index 4e6610c8a1..83a145c216 100644 --- a/.agents/skills/fix-security-issue/SKILL.md +++ b/.agents/skills/fix-security-issue/SKILL.md @@ -1,319 +1,18 @@ --- name: fix-security-issue -description: Implement a fix for a reviewed security issue. Takes a directly requested issue number or scans for issues labeled `topic:security` and `agent:implementation-requested`. Reads the security review from the issue comments and implements the remediation plan. Trigger keywords - fix security issue, remediate security, implement security fix, patch vulnerability. +description: Implement an authorized fix for a reviewed security issue and open a PR that closes its issue. +metadata: + internal: true --- # Fix Security Issue -Implement a code fix for a security issue that has already been reviewed by the `review-security-issue` skill. +Use this skill after an authorized `review-security-issue` review identifies an actionable concern. Follow `SECURITY.md`; do not disclose vulnerability details in a public issue. A direct user request to fix a specific reviewed issue authorizes implementation. For unattended work, inspect current `state:*` label descriptions, maintainer assignments, and comments to verify that remediation is authorized. Do not infer approval from a state that only records technical validation. -## Prerequisites +1. Fetch the issue and its comments with `gh issue view --json number,title,body,state,labels,comments`. Follow Label Discovery in `CONTRIBUTING.md` and confirm this is a security issue; resolve unclear meanings before interpreting authorization. Find the review marked `> **🔒 security-review-agent**` and its remediation plan. If the review is missing or found the issue not actionable, stop and report that result. +2. Verify the review against current code. Adapt the plan when code has changed, and record material deviations. Check for an existing owner, branch, or PR. +3. Create a `fix` branch or worktree following Branch Names in `CONTRIBUTING.md`, preserving unrelated changes and disclosure boundaries. Implement the smallest safe fix and add regression tests for the security boundary. Avoid logging secrets or adding public exploit detail. +4. Follow the verification guidance in `CONTRIBUTING.md`. Run format, lint, compile or type checks, and regression tests for the affected security boundary and dependent components, plus the relevant E2E lane for sandbox or policy changes. Broaden verification when the fix spans components or a concrete risk remains; do not require unaffected Rust or SDK suites solely to create a signed-off commit or PR. +5. Follow `create-github-pr` and use `Closes #` for the reviewed issue. Every PR from this issue-backed remediation workflow must close its own issue; split multi-PR remediations into separate issues in the authorized security workflow. Keep the PR description appropriately scoped to its disclosure venue. -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote -- The issue must have `topic:security`. In unattended scan mode it must also have `agent:implementation-requested`; a direct user request to fix a specific issue does not require that label. -- The issue must have a prior security review comment (posted by `review-security-issue`) with a **Legitimate concern** determination and a remediation plan - -## Agent Comment Marker - -All PR descriptions and comments posted by this skill **must** begin with the following marker line: - -``` -> **🔧 security-fix-agent** -``` - -This distinguishes fix-agent content from review-agent comments (`🔒 security-review-agent`) and human comments. - -## Step 1: Identify the Issue - -The user may provide an issue number directly, or ask the agent to find issues to fix. - -### If an issue number is provided - -Strip any leading `#` and proceed to Step 2 with that issue ID. The user's explicit fix request authorizes implementation; do not refuse solely because `agent:implementation-requested` is absent. - -### If no issue number is provided - -Scan for open issues labeled `topic:security` and `agent:implementation-requested`: - -```bash -gh issue list --label "topic:security" --label "agent:implementation-requested" --state open --json number,title,labels,updatedAt -``` - -- **If no issues are found**, report to the user that there are no security issues ready for fixing and stop. -- **If one issue is found**, proceed to Step 2 with that issue. -- **If multiple issues are found**, list them for the user and ask which one to work on. If the user said to handle all of them, process them sequentially (one full fix cycle per issue). - -## Step 2: Fetch the Issue and Validate Labels - -Fetch the issue details: - -```bash -gh issue view --json number,title,body,state,labels,author -``` - -### Validate the Security Label and Invocation Mode - -Check the issue's `labels` array from the response above: - -- `topic:security` is required because this specialized skill handles security issues. -- `agent:implementation-requested` is required only when an unattended agent discovered the issue by scanning the queue. - -If `topic:security` is missing, report that this skill only handles security issues and stop. If queue mode selected an issue without `agent:implementation-requested`, report that it is not ready for unattended pickup and stop. - -Never apply `agent:implementation-requested` yourself. Its absence does not block a direct user request to fix a specific issue. - -### Validate the security review - -Once labels are confirmed, fetch the comments to find the security review: - -```bash -gh issue view --json comments --jq '[.comments[] | select(.body | contains("security-review-agent"))]' -``` - -- **If no `security-review-agent` comment is found**, report to the user that this issue has not been reviewed yet. Suggest running the `review-security-issue` skill first. Stop. -- **If the review determination is "Not actionable"**, report to the user that the review found no actionable concern. There is nothing to fix. Stop. -- **If the review determination is "Legitimate concern"**, extract the **Remediation Plan** and **Severity Assessment** sections from the review comment. Proceed to Step 3. - -## Step 3: Plan the Implementation - -Before writing code, analyze the remediation plan from the review comment: - -1. Identify all files and components mentioned in the remediation plan. -2. Read those files to understand the current code. -3. Determine if the remediation plan is still accurate given the current state of the code (the codebase may have changed since the review). -4. Break the fix into discrete, testable changes. - -If the remediation plan references files or components that no longer exist or have changed significantly, adapt the plan accordingly and note the deviations. - -## Step 4: Create a Branch - -Create a working branch for the fix: - -```bash -git checkout -b fix/security-- -``` - -Follow the project's branch naming conventions. The branch name should reference the issue ID. - -In queue mode, replace the human request and ready-plan labels with the agent execution state. For an unlabeled direct invocation, do not add an agent-workflow label: - -```bash -gh issue edit --remove-label "agent:implementation-requested" --remove-label "agent:plan-ready" --add-label "agent:in-progress" -``` - -## Step 5: Implement the Fix - -Implement the changes described in the remediation plan. Follow these principles: - -- **Minimal scope**: Only change what is necessary to address the security concern. Avoid unrelated refactors. -- **Defense in depth**: Where appropriate, add multiple layers of protection (input validation, output encoding, access checks, etc.). -- **No regressions**: Ensure existing tests still pass after the fix. - -After implementing, run the project's pre-commit checks: - -```bash -mise run pre-commit -``` - -Fix any issues that arise before proceeding. - -## Step 6: Write Tests - -Every security fix **must** include tests that verify the vulnerability is resolved. Choose the appropriate test level(s) based on the nature of the fix: - -### Unit tests - -Add unit tests when the fix changes a specific function, method, or module in isolation. Place them alongside the existing tests for that module (e.g., same `tests/` directory or `#[cfg(test)]` block for Rust, `test_*.py` for Python). - -Unit tests should cover: -- The previously-vulnerable code path now rejects malicious input or behaves correctly -- Edge cases around the security boundary (empty input, oversized input, special characters, etc.) -- That legitimate inputs continue to work as before - -### Integration / E2E tests - -Add integration or end-to-end tests when the vulnerability spans multiple components or is triggered via an API endpoint, CLI command, or network boundary. Place them in the project's existing integration or e2e test directories. - -Integration tests should cover: -- The full attack scenario described in the security review is no longer exploitable -- The fix holds under realistic conditions (authenticated vs. unauthenticated, different roles, etc.) - -### Test naming - -Name tests descriptively to document the security concern: -- `test_rejects_sql_injection_in_search_query` -- `test_blocks_path_traversal_in_file_upload` -- `test_enforces_auth_on_admin_endpoint` - -### Verify - -Run the full relevant test suite to confirm both the new tests pass and no existing tests regress: - -```bash -# Run tests relevant to the changed components -# The specific command depends on the project area affected -``` - -If the review identified a specific exploit scenario, verify that it is no longer possible with the fix in place. - -## Step 7: Update Documentation - -Review the documentation requirements in `AGENTS.md` and update any affected -docs as part of the security fix. If the fix is purely internal, such as -switching to parameterized queries with no external behavior change, -documentation updates may not be needed. - -## Step 8: Commit, Push, and Open PR - -### Commit - -Commit all changes (implementation, tests, and documentation) using conventional commit format: - -```bash -git add -git commit -m "$(cat <<'EOF' -fix(security): - -Closes # - - -EOF -)" -``` - -### Push - -```bash -git push -u origin HEAD -``` - -### Create the PR - -Create a PR that closes the security issue. Put the full fix summary in the PR description rather than commenting on the issue -- the `Closes #` directive will auto-close the issue when merged. - -```bash -gh pr create \ - --title "fix(security): " \ - --label "topic:security" \ - --body "$(cat <<'EOF' -> **🔧 security-fix-agent** - -Closes # - -## Security Fix - -### Summary -<1-3 sentences describing the security issue and how it was fixed> - -### Severity Assessment -- **Impact:** -- **Exploitability:** -- **Affected components:** - -### Changes Made -- ``: -- ``: - -### Tests Added -- **Unit:** -- **Integration/E2E:** - -### Documentation Updated -- ``: - -### Verification - -EOF -)" -``` - -**Display the PR URL** so it's easily clickable: - -``` -Created PR [#](https://github.com/OWNER/REPO/pull/) -``` - -In queue mode, replace `agent:in-progress` with `agent:pr-opened` after the PR is created. For an unlabeled direct invocation, do not add an agent-workflow label: - -```bash -gh issue edit --remove-label "agent:in-progress" --add-label "agent:pr-opened" -``` - -## Step 9: Report to User - -Summarize what was done: - -1. Which issue was addressed and link to it -2. What the vulnerability was -3. What changes were made (files, approach) -4. What tests were added and at which level (unit, integration, e2e) -5. What documentation was updated -6. Link to the PR - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue list --label "topic:security" --label "agent:implementation-requested" --state open` | Find security issues whose fixes a human requested | -| `gh issue view --json number,title,body,state,labels,author` | Fetch full issue metadata | -| `gh issue view --json comments` | Fetch all comments on an issue | -| `gh pr create --title "..." --body "..."` | Create a pull request | -| `gh api user --jq '.login'` | Get current GitHub username | -| `gh issue view ` | View issue details | -| `mise run pre-commit` | Run pre-commit checks | - -## Example Usage - -### Fix a specific issue - -User says: "Fix security issue #42" - -1. Fetch issue #42 and its comments -2. Find the `security-review-agent` review with determination "Legitimate concern" -3. Extract the remediation plan (e.g., add input sanitization to API handler) -4. Create branch `fix/security-42-input-sanitization` -5. Implement the fix -6. Add unit tests for the sanitization function and an integration test for the endpoint -7. Update affected documentation per `AGENTS.md`, if needed -8. Commit, push, and open PR with `Closes #42` -9. Report the PR link and changes to the user - -### Scan and fix requested security issues - -User says: "Fix any ready security issues" - -1. Query for open issues with labels `topic:security` + `agent:implementation-requested` -2. Find issue #78: "SQL injection in search endpoint" -3. Fetch the review comment -- determination is "Legitimate concern" -4. Implement parameterized queries -5. Add `test_rejects_sql_injection_in_search_query` unit test and e2e test for the search endpoint -6. Update affected documentation per `AGENTS.md`, if needed -7. Commit, push, open PR with `Closes #78`, report to user - -### Issue with non-actionable review - -User says: "Fix security issue #99" - -1. Fetch issue #99 and its comments -2. Find the `security-review-agent` review with determination "Not actionable" -3. Report to the user: "Issue #99 was reviewed and determined to be not actionable. No fix is needed." -4. Stop - -### Directly requested issue without `agent:implementation-requested` - -User says: "Fix security issue #55" - -1. Fetch issue #55 metadata -2. Labels are `["topic:security"]` -- missing `agent:implementation-requested` -3. Confirm that a legitimate security review and remediation plan exist -4. Proceed because the user's direct request authorizes implementation - -### Issue without a review - -User says: "Fix security issue #60" - -1. Fetch issue #60 metadata -- `topic:security` is present and the user directly requested the fix -2. Fetch comments -- no `security-review-agent` comment found -3. Report to the user: "Issue #60 has not been reviewed yet. Run the review-security-issue skill first." -4. Stop +Begin any fix comments with `> **🔧 security-fix-agent**`. Do not change human disposition or introduce `agent:*` workflow labels. diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 2dad568c79..9fdf315c3c 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -1,6 +1,8 @@ --- name: helm-dev-environment description: Start up, tear down, and configure the local Kubernetes development environment for OpenShell. Uses k3d (Docker-backed k3s) + Skaffold + Helm. Covers cluster lifecycle, optional add-ons (Keycloak OIDC, Envoy Gateway), HA testing, and port mappings. Trigger keywords - local k8s, local cluster, k3d, skaffold, helm dev, start cluster, stop cluster, tear down cluster, delete cluster, create cluster, helm:k3s, helm:skaffold, local dev environment, dev cluster, k8s dev, envoy gateway local, keycloak local, high availability, HA. +metadata: + internal: true --- # Helm Dev Environment @@ -26,14 +28,18 @@ mise run helm:k3s:create ``` Creates a k3d cluster and merges its kubeconfig into the worktree-local `kubeconfig` file. +When the named cluster already exists, the task starts any stopped containers and refreshes +same-named kubeconfig entries so a recreated load balancer's current API port takes effect. Also applies the upstream agent-sandbox CRDs/controller (pinned via `AGENT_SANDBOX_VERSION` in `tasks/scripts/helm-k3s-local.sh`, fetched from `github.com/kubernetes-sigs/agent-sandbox` -releases) and preloads the default community sandbox image into k3d so the first sandbox -create does not wait on a large registry pull. Traefik is disabled at cluster creation time. +releases), enables its OTLP tracing on v0.5 and later, installs an OTLP trace +collector and UI in the `observability` namespace, +and preloads the default sandbox image into k3d so the first sandbox create +does not wait on a large registry pull. Traefik is disabled at cluster creation time. **Multi-worktree support:** the cluster name is derived from the last component of the -current git branch (e.g. branch `kube-support/local-dev/tmutch` → cluster -`openshell-dev-tmutch`). Each worktree therefore gets its own isolated cluster and its +current git branch (e.g. branch `chore/1234-local-dev/octocat` → cluster +`openshell-dev-octocat`). Each worktree therefore gets its own isolated cluster and its own `kubeconfig` file. Override with `HELM_K3S_CLUSTER_NAME` to force a specific name or share one cluster across worktrees. @@ -46,7 +52,10 @@ Port mappings created at cluster time (cannot be changed without recreating): Override with env vars before running `helm:k3s:create`: - `HELM_K3S_LB_HOST_PORT` (default: `8080`) - `HELM_K3S_PRELOAD_SANDBOX_IMAGE` (default: - `ghcr.io/nvidia/openshell-community/sandboxes/base:latest`; set to an empty value to skip) + `nvcr.io/nvidia/base/ubuntu:24.04`; set to an empty value to skip) +- `HELM_K3S_COLLECTOR_IMAGE` (default: + `mcr.microsoft.com/dotnet/aspire-dashboard:latest`) +- `HELM_K3S_COLLECTOR_HEALTH_TIMEOUT` (default: `120` seconds) ### 2. Deploy OpenShell @@ -60,58 +69,111 @@ mise run helm:skaffold:dev mise run helm:skaffold:run ``` -**Supervisor sidecar topology** (build once and leave running): +Resource admission defaults to enabled and caller driver config to disabled. +Driver-config scenarios need an explicit `allowDriverConfig` opt-in; external +attachments also need administrator-controlled approval labels in the target +namespace. GPU attachments and operator-selected image-pull Secrets are exempt +from labels. Managed workspace image-pull Secrets are copied from the configured +source in the gateway namespace; do not grant approval to the gateway database +PVC or disable admission to make tests pass. + +The Skaffold flow builds distinct `gateway`, `sandbox`, and `supervisor` images +and deploys the OpenShell Helm chart. The Kubernetes driver creates a +capability-free workload Pod and a directly managed capability-free supervisor +Pod. One namespace-wide NetworkPolicy denies direct egress from every OpenShell +workload Pod. The +`pkiInitJob` hook (a pre-install Job that runs `openshell-gateway generate-certs`) +generates gateway and CLI TLS secrets on first install. Supervisor Pods project +only `ca.crt` and authenticate gateway RPCs with sandbox bearer tokens. User +client certificates and private keys remain outside supervisor and workload Pods. +The default Skaffold values export +gateway and Kubernetes-driver traces to the collector service installed by +`helm:k3s:create`. Envoy Gateway is opt-in; see the Optional Add-ons section. + +The gateway Service uses ClusterIP. Access is via Envoy Gateway (port `8080`) or +the unified local forwarding task: + ```bash -mise run helm:skaffold:run:sidecar +mise run helm:k3s:forward ``` -**Supervisor sidecar topology with TLS/mTLS enabled** (build once and leave running): +The task forwards OTLP/gRPC to `http://127.0.0.1:4317` and the trace UI to +`http://127.0.0.1:18888`. When Skaffold has deployed a Kubernetes gateway, it +also forwards the gateway to `http://127.0.0.1:8090`; otherwise it continues +with the collector ports only. A successful plaintext `helm:skaffold:run` +registers the gateway under the worktree-specific k3d +cluster name and selects it as the active gateway. Keep the forwarding +task running while using those endpoints. + +### Viewing local traces + +The gateway exports OTLP/gRPC to +`http://openshell-collector.observability.svc.cluster.local:4317` through the +default Skaffold values. Forward OTLP/gRPC and the trace UI to the host: + ```bash -mise run helm:skaffold:run:sidecar-mtls +mise run helm:k3s:forward ``` -Both commands build the `gateway` and `supervisor` images and deploy the OpenShell Helm -chart. The sidecar profile renders an `openshell-network-init` init container for -nftables setup and an `openshell-supervisor-network` runtime sidecar for proxying. -Binary-aware policy mode runs that sidecar as UID 0 with `SYS_PTRACE` and -`DAC_READ_SEARCH`; relaxed mode can run it as the configured proxy UID, which -must be at least `1000` and distinct from the workload UID. The -sidecar-mTLS profile reuses `ci/values-sidecar.yaml` and restores -`server.disableTls=false` inline for Skaffold. The `pkiInitJob` hook (a pre-install -Job that runs `openshell-gateway generate-certs`) generates mTLS secrets on first -install. Envoy Gateway opt-in; see the Optional Add-ons section below. +Open `http://127.0.0.1:18888` and exercise the gateway to inspect gateway and +Kubernetes compute-driver spans under their distinct service names, along with +Agent Sandbox controller reconciliation spans linked through the Sandbox +trace-context annotation. The same command exposes OTLP/gRPC on +`http://127.0.0.1:4317` and, when deployed, the Kubernetes gateway on +`http://127.0.0.1:8090`. The local `gateway:docker`, `gateway:podman`, and +`gateway:vm` tasks detect the collector listener at startup and enable trace +export only while it is reachable. + +The Skaffold profile for HA reverse-proxy development is available from +`deploy/helm/openshell/`: -The gateway Service uses ClusterIP. Access is via Envoy Gateway (port `8080`) or `kubectl port-forward`. +```bash +# Two gateway replicas + external PostgreSQL Secret + Envoy Gateway + Gateway API route. +KUBECONFIG=../../../kubeconfig skaffold run -p high-availability +``` + +The `high-availability` profile expects a Secret named `openshell-ha-pg` in the `openshell` +namespace with a `uri` key. For local manual testing, either create your own +PostgreSQL Secret or use the e2e PostgreSQL fixture manifest in +`e2e/kubernetes/postgres-fixture.yaml`. + +For the `high-availability` profile, return to the repository root and apply the +GatewayClass and BackendTrafficPolicy manifest after Skaffold has installed +Envoy Gateway: + +```bash +KUBECONFIG=kubeconfig mise run helm:gateway:apply +``` -**HA test deploy** (two gateway replicas + external PostgreSQL Secret): uncomment -`#- ci/values-high-availability.yaml` in `deploy/helm/openshell/skaffold.yaml`, -create the Secret named `openshell-ha-pg` with a `uri` key, then run -`mise run helm:skaffold:run` or `mise run helm:skaffold:dev`. +The BackendTrafficPolicy disables Envoy request and stream-duration timeouts for +OpenShell's `GRPCRoute`. Keep that policy in `deploy/kube/manifests/envoy-gateway-openshell.yaml`, +not in the Helm chart; it is required for long-lived gRPC create/watch/exec/relay +streams during gateway rollouts and scale events. ### TLS behaviour `ci/values-skaffold.yaml` sets `server.disableTls: true`, so Skaffold-based deploys run -plaintext by default. To test sidecar topology with TLS enabled, use -`mise run helm:skaffold:run:sidecar-mtls`. +plaintext by default. Override `server.disableTls=false` to exercise TLS/mTLS. | Mode | `server.disableTls` | Gateway scheme | |------|---------------------|----------------| | Skaffold dev (default) | `true` | `http://` | | TLS enabled | `false` (or omitted) | `https://` | -### Connecting via port-forward +### Connecting through the forwarding task -Port `8080` is already bound by the k3d load balancer when Envoy Gateway is active, so -the port-forward uses local port `8090` to avoid a collision: +Port `8080` is already bound by the k3d load balancer when Envoy Gateway is +active, so the forwarding task uses local port `8090` for the gateway. In a +second terminal, confirm that the gateway is registered and active: ```bash -KUBECONFIG=kubeconfig kubectl port-forward -n openshell svc/openshell 8090:8080 +openshell gateway list ``` **Plaintext (default Skaffold deploy):** ```bash -openshell sandbox list --gateway-endpoint http://localhost:8090 +openshell sandbox list ``` **With mTLS enabled** — extract the client cert the PKI hook wrote to the cluster, @@ -144,12 +206,6 @@ openshell sandbox list --gateway-endpoint https://localhost:8090 mise run helm:skaffold:delete ``` -For a sidecar-profile deployment: - -```bash -mise run helm:skaffold:delete:sidecar -``` - ### Delete the cluster entirely ```bash @@ -163,35 +219,131 @@ but will point to a deleted cluster — safe to ignore or clean up manually. ## Optional Add-ons -Each add-on requires uncommenting the corresponding `valuesFiles` entry in -`deploy/helm/openshell/skaffold.yaml` before running `helm:skaffold:dev` or `helm:skaffold:run`. +Some add-ons can be enabled by uncommenting values in `skaffold.yaml`, but prefer +the dedicated Skaffold profiles when they exist. Profiles avoid leaving local +manual edits in the worktree. ### Envoy Gateway (Gateway API / GRPCRoute) -Envoy Gateway is already installed by Skaffold (the `envoy-gateway` Helm release in -`skaffold.yaml`). To activate routing: +Use the `high-availability` Skaffold profile for HA reverse-proxy testing. The +profile intentionally includes Envoy Gateway so multi-replica behavior is +exercised through the same Gateway API path used by reverse-proxy deployments: -1. Uncomment `#- values-gateway.yaml` in `skaffold.yaml` -2. Redeploy: `mise run helm:skaffold:run` -3. Apply the GatewayClass: `mise run helm:gateway:apply` -4. Access: `http://127.0.0.1:8080` +```bash +cd deploy/helm/openshell +KUBECONFIG=../../../kubeconfig skaffold run -p high-availability +cd ../../.. +KUBECONFIG=kubeconfig mise run helm:gateway:apply +``` + +`values-gateway.yaml` creates the `openshell` `Gateway` (listener on port 80, +class `eg`) and an `openshell` `GRPCRoute` in the `openshell` namespace. The +`high-availability` profile +installs the Envoy Gateway Helm chart and layers both +`values-high-availability.yaml` and `values-gateway.yaml` onto the OpenShell +release. + +`deploy/kube/manifests/envoy-gateway-openshell.yaml` creates: + +- `GatewayClass/eg` +- `BackendTrafficPolicy/openshell-grpc-timeouts` -`values-gateway.yaml` creates a `Gateway` (listener on port 80, class `eg`) and a -`GRPCRoute` in the `openshell` namespace. Envoy Gateway provisions a LoadBalancer -service for the proxy; klipper-lb binds it to hostPort 80, reachable via the -`8080:80` load balancer port mapping. +The Envoy Gateway proxy Service is usually exposed through the k3d load balancer +at `http://127.0.0.1:8080`. If the cluster was created with a different +`HELM_K3S_LB_HOST_PORT`, use that host port instead. + +For manual tests against an existing cluster, prefer forwarding the Envoy proxy +Service rather than `svc/openshell`. That keeps client traffic on the same path +as a real reverse proxy while gateway pods rotate behind it: + +```bash +KUBECONFIG=kubeconfig kubectl get svc -A \ + -l gateway.envoyproxy.io/owning-gateway-name=openshell +KUBECONFIG=kubeconfig kubectl -n port-forward \ + svc/ 8080:80 +openshell gateway add http://127.0.0.1:8080 --name openshell --local +``` + +When running e2e tests manually through Envoy, register gateway metadata (as +above) instead of relying only on `OPENSHELL_GATEWAY_ENDPOINT`; some tests call +`openshell gateway info` and expect metadata for the active gateway. + +### Kubernetes E2E Notes + +Use `mise run e2e:kubernetes` for the standard Helm-backed Kubernetes suite. +The kube e2e wrapper creates only one port-forward, to `svc/openshell`; it no +longer forwards the unauthenticated health listener or runs a `/readyz` e2e +target. `/readyz` remains covered by server unit/integration tests. + +Use `mise run e2e:kubernetes:ha-rebalancing` for full-suite HA coverage. The +task creates an external PostgreSQL fixture, installs Envoy Gateway, applies +`deploy/kube/manifests/envoy-gateway-openshell.yaml`, enables the chart +`GRPCRoute`, and runs the full Kubernetes e2e suite, including +`kubernetes_ha_rebalancing`. That coverage validates sandbox create/watch and +exec through the Envoy proxy while gateway replicas scale up, scale down, and +rotate. It also keeps a long-running sandbox alive and runs upload/download +operations while gateway pods roll, so file sync exercises the same relay retry +path as interactive sessions. + +If you reuse an existing Skaffold cluster for the full kube suite, make sure the +chart has `server.hostGatewayIP` set so sandbox pods can resolve +`host.openshell.internal` back to the test host. The e2e wrapper detects this on +chart installs; manual reuse may require: + +```bash +HOST_GATEWAY_IP="${OPENSHELL_E2E_HOST_GATEWAY_IP:?set host gateway IP}" +KUBECONFIG=kubeconfig helm upgrade openshell deploy/helm/openshell \ + --namespace openshell --reuse-values \ + --set "server.hostGatewayIP=${HOST_GATEWAY_IP}" \ + --wait --timeout 5m +``` + +Use the IP that pods in that cluster use to reach listeners on the test host. + +### BackendTLSPolicy (end-to-end TLS) + +To enable end-to-end TLS between the Gateway proxy and the gateway pod, add +BackendTLSPolicy values to the Helm install: + +```bash +helm upgrade --install openshell deploy/helm/openshell \ + --set grpcRoute.enabled=true \ + --set grpcRoute.backendTLSPolicy.enabled=true \ + --set server.tls.enableMtls=false \ + ... +``` + +This requires `server.tls.enableMtls=false` because ingress proxies cannot +present client certificates to the backend. The certgen hook creates a backend +CA ConfigMap from the server Secret's `ca.crt` key. With cert-manager, a +separate post-install Job polls for the cert-manager-issued certificate (up to +`pkiInitJob.timeoutSeconds`); with built-in PKI the ConfigMap is created in the +same pre-install hook. The ConfigMap is reconciled on every upgrade so CA +rotations propagate automatically. + +Key Helm values: +- `grpcRoute.backendTLSPolicy.enabled`: create the BackendTLSPolicy resource +- `grpcRoute.backendTLSPolicy.caCertificateConfigMapName`: override ConfigMap name +- `grpcRoute.backendTLSPolicy.hostname`: override backend validation hostname +- `server.tls.enableMtls`: must be `false` for BackendTLSPolicy +- `pkiInitJob.timeoutSeconds`: polling duration for cert-manager mode +- `pkiInitJob.failOnTimeout`: fail install if cert-manager times out ### Keycloak OIDC -One-time setup — only needed once per cluster lifetime: +Initial setup — rerun it whenever you want to rotate the development CA: ```bash mise run keycloak:k8s:setup ``` This deploys Keycloak (`quay.io/keycloak/keycloak:24.0`) into the `keycloak` namespace, -imports the openshell realm from `scripts/keycloak-realm.json`, and prints a port-forward -command for acquiring tokens from the CLI. +imports the openshell realm from `scripts/keycloak-realm.json`, generates a short-lived +development TLS certificate, and publishes its trust anchor as the +`openshell-keycloak-ca` ConfigMap in the OpenShell namespace. The command prints a +port-forward command for acquiring tokens from the CLI. Rerunning setup rotates the +development certificate and trust anchor; redeploy the gateway afterward so it reloads +the mounted CA bundle. Then activate OIDC in the OpenShell Helm chart: 1. Uncomment `#- ci/values-keycloak.yaml` in `skaffold.yaml` @@ -215,9 +367,34 @@ SPIFFE JWT-SVIDs for dynamic provider token grants: `openshell.local` and adds a `ClusterSPIFFEID` that maps sandbox pod annotations to `spiffe://openshell.local/openshell/sandbox/`. OpenShell mounts the SPIFFE CSI Workload API socket at -`/spiffe-workload-api/spire-agent.sock` into sandbox pods for provider token +`/spiffe-workload-api/spire-agent.sock` only into supervisor Pods for provider token grants. Supervisor-to-gateway authentication remains on the Kubernetes -ServiceAccount bootstrap and gateway-minted sandbox JWT path. +ServiceAccount bootstrap and gateway-minted sandbox JWT path; the selected +Kubernetes compute driver validates the projected token before the gateway +returns the current generation-bound session JWT. The driver also returns the +runtime identity recorded during provisioning. Restart preserves its namespace +and Sandbox CR UID, rejects ambiguous label matches, and rotates only the +supervisor Pod UID; bootstrap fails when the live identity does not match the +durable sandbox record. + +### Vault Credential Driver + +The `credential-driver-vault` Skaffold profile applies +`ci/values-credential-driver-vault.yaml`. Its external OpenBao/Vault backend +must expose HTTPS at the configured service DNS name and publish the issuing CA +certificate as the `ca.crt` key in the `openbao-ca` ConfigMap. Local e2e uses +OpenBao dev TLS and an `openbao-0` DNS alias matching its generated certificate. +The Helm value +`server.credentialDrivers.vault.caConfigMapName` mounts that key into the +gateway and renders the driver's `ca_bundle` setting. Non-loopback HTTP +addresses fail gateway startup, and hostname verification requires the service +DNS name in the server certificate SANs. + +```bash +cd deploy/helm/openshell +skaffold run -p credential-driver-vault +kubectl -n openshell logs statefulset/openshell -c openshell-gateway --tail=200 +``` --- @@ -273,11 +450,12 @@ for dependencies still declared in `Chart.yaml`. | `deploy/helm/openshell/ci/values-cert-manager.yaml` | cert-manager PKI overlay (opt-in; disables pkiInitJob) | | `deploy/helm/openshell/ci/values-gateway.yaml` | Envoy Gateway GRPCRoute + Gateway overlay | | `deploy/helm/openshell/ci/values-high-availability.yaml` | HA test overlay (`replicaCount: 2` with external PostgreSQL Secret) | +| `deploy/helm/openshell/ci/values-autoscaling.yaml` | Render-only overlay for the optional gateway HorizontalPodAutoscaler (helm lint and helm-unittest) | | `deploy/helm/openshell/ci/values-keycloak.yaml` | Keycloak OIDC overlay | -| `deploy/helm/openshell/ci/values-sidecar.yaml` | Supervisor sidecar topology overlay for Kubernetes e2e/dev | | `deploy/helm/openshell/ci/values-spire.yaml` | SPIFFE/SPIRE provider token grant overlay | | `deploy/helm/openshell/ci/values-spire-stack.yaml` | SPIRE hardened chart values for local dev | | `deploy/helm/openshell/ci/values-tls-disabled.yaml` | Lint-only: TLS + auth disabled (reverse-proxy edge termination) | -| `deploy/kube/manifests/envoy-gateway-openshell.yaml` | GatewayClass for Envoy Gateway (`mise run helm:gateway:apply`) | +| `deploy/helm/openshell/ci/values-credential-driver-vault.yaml` | Vault credential-driver validation overlay with HTTPS and private-CA trust | +| `deploy/kube/manifests/envoy-gateway-openshell.yaml` | GatewayClass and BackendTrafficPolicy for Envoy Gateway (`mise run helm:gateway:apply`) | | `tasks/scripts/helm-k3s-local.sh` | k3d cluster create/delete/start/stop/status | -| `tasks/scripts/keycloak-k8s-setup.sh` | Keycloak deploy + realm import | +| `tasks/scripts/keycloak-k8s-setup.sh` | Keycloak deploy, realm import, and development TLS trust anchor | diff --git a/.agents/skills/launch-openshell-gator/SKILL.md b/.agents/skills/launch-openshell-gator/SKILL.md index 8b25760698..2a516754ee 100644 --- a/.agents/skills/launch-openshell-gator/SKILL.md +++ b/.agents/skills/launch-openshell-gator/SKILL.md @@ -1,6 +1,8 @@ --- name: launch-openshell-gator description: Launch and supervise OpenShell gator agents. Use when starting gator on issues or PRs, checking gator sandboxes, building the gator sandbox image, restarting stuck gators, inspecting gator logs, or experimenting with gator harness/model overrides. Trigger keywords - launch gator, start gator, run gator, gator sandbox, supervised gator, gator logs, restart gator. +metadata: + internal: true --- # Launch OpenShell Gator @@ -11,7 +13,7 @@ For gator's PR/issue validation policy, load `gator-gate` inside the launched sa ## Non-Negotiable Rules -- Keep normal gator launches supervised: use `--watch --background` and let the in-sandbox supervisor own sleeping and relaunching bounded cycles. +- Keep normal gator launches supervised: use `--watch` and let the in-sandbox supervisor own sleeping and relaunching bounded cycles. - Do not add passive `sleep` loops in the operator session to watch gator. Check logs or status once, then report the current state or launch a proper watcher outside the model session only when explicitly asked. - Do not change the default gator model in `scripts/agents/gator/agent.yaml` for experiments. Use `CODEX_MODEL=...` and, if needed, a temporary `--from` Docker context or `--codex-bin` override. - Do not push to contributor branches, approve, merge, post `/ok to test`, or broaden gator scope unless the operator explicitly authorized that action. @@ -24,14 +26,14 @@ For gator's PR/issue validation policy, load `gator-gate` inside the launched sa |---|---| | `scripts/agents/run.sh` | Manifest-driven OpenShell agent launcher. | | `scripts/agents/gator/agent.yaml` | Gator manifest: immutable payload version, default gateway, harness, providers, runtime, skills, and subagents. | -| `scripts/agents/gator/Dockerfile` | Gator sandbox image source. Local launches build this image through OpenShell. | +| `scripts/agents/gator/Dockerfile` | Gator sandbox image source. Local launches build it in gateway's Docker or Podman image store. | | `scripts/agents/gator/policy.yaml` | Sandbox policy for the gator agent. | | `scripts/agents/gator/bin/gh` | Gator-specific `gh` wrapper and same-SHA duplicate-post guard. | | `scripts/agents/gator/bin/review-feedback-ledger` | Builds tree-aware review scope, durable findings, convergence telemetry, and review-budget state. | +| `scripts/agents/gator/bin/resolve-gator-review-threads` | Resolves addressed Gator-owned inline review threads by ledger finding ID. | | `scripts/agents/gator/bin/validate-review-findings` | Enforces the blocker evidence schema and downgrades unsupported hypotheses. | | `scripts/agents/gator/prompts/gator.md` | Rendered top-level prompt template baked into the payload. | | `scripts/agents/gator/skills/gator-gate/SKILL.md` | In-sandbox gator state-machine skill. | -| `scripts/agents/gator/logs/` | Background launch and supervisor logs. | ## Preflight @@ -78,6 +80,8 @@ jq -e '.tokens.access_token and .tokens.refresh_token and .tokens.account_id' "$ If this fails, run the local Codex login flow outside the gator launch. If Codex was recently reauthenticated and gateway refresh fails later, relaunch with `--reset-refresh` once. +The launcher passes the non-secret host account ID as literal `CODEX_ACCOUNT_ID` for Codex's local workspace routing. The harness must not use the opaque `CODEX_AUTH_ACCOUNT_ID` credential placeholder for that comparison. Access tokens remain placeholders, and refresh material remains gateway-only. + ### Step 5: Verify Gateway Is Registered And Alive Use the target gateway from the operator request or current session context. Do not assume a gateway name. If the operator did not specify one, list registered gateways and ask before launching when the correct target is ambiguous. @@ -153,11 +157,12 @@ sandbox_name="gator-pr-${pr_number}-supervised" --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - --background \ "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}." ``` -The launcher builds the gator sandbox image when needed, stages the immutable payload, imports provider profiles, configures provider credentials and refresh, creates the sandbox, and writes a background log under `scripts/agents/gator/logs/`. +The launcher queries the gateway's selected compute driver, builds the gator image in the matching Docker or Podman image store, stages the immutable payload, imports provider profiles, configures provider credentials and refresh, and starts the agent supervisor as the sandbox's canonical main process. The detached main process survives loss of the host CLI connection and reconnects to a restarted gateway. Unless `--keep` is set, the sandbox is marked ephemeral so the gateway deletes it after the canonical main process exits and its terminal result is finalized. `CONTAINER_ENGINE`, when set, must match the gateway driver. + +The launcher streams image-build and provisioning output until the detached workload is ready, then exits. Use `openshell logs ` or the TUI for runtime output. ### Launch An Issue Or Issue/PR Pair @@ -174,7 +179,6 @@ sandbox_name="gator-issue-${issue_number}-supervised" --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - --background \ "Run gator on issue #${issue_number}. Scope this invocation only to issue #${issue_number}." ``` @@ -195,7 +199,6 @@ sandbox_name="gator-pr-${pr_number}-supervised" --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - --background \ "Review and monitor PR #${pr_number} with linked issue #${issue_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number} and issue #${issue_number}." ``` @@ -216,8 +219,7 @@ sandbox_name="gator-pr-${pr_number}-supervised" --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - --background \ - "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}. The operator explicitly authorizes applying the test:e2e label, posting /ok to test for the current head SHA, and rerunning the relevant current-head workflow when the E2E Label Help bot says that is required." + "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}. The operator explicitly authorizes applying the test:e2e label, posting /ok to test with the full 40-character current head SHA, and rerunning the relevant current-head workflow when the E2E Label Help bot says that is required." ``` ## Model Or Image Experiments @@ -238,7 +240,6 @@ CODEX_MODEL=gpt-5.6-sol \ --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - --background \ "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}. This launch is intentionally testing Codex model gpt-5.6-sol via the CLI launcher." ``` @@ -263,7 +264,6 @@ CODEX_MODEL=gpt-5.6-sol \ --name "$sandbox_name" \ --from "$tmp_context" \ --watch \ - --background \ "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}." ``` @@ -271,21 +271,26 @@ CODEX_MODEL=gpt-5.6-sol \ ### Read The Launch Result -The launcher prints the log path when `--background` is used: - -```text -Started in background. Log: scripts/agents/gator/logs/.log -``` - -Read that file directly. Important markers: +The launcher streams image-build and provisioning output to the terminal. Important markers: - `Built image ...` means the local image build completed. - `Created sandbox: ` means OpenShell accepted the sandbox. - `openshell-agent: starting watch cycle` means the in-sandbox supervisor began a bounded cycle. - `OpenAI Codex v...` plus `model: ...` confirms the Codex CLI and model actually used. - `OPENSHELL_AGENT_RESULT {...}` is the bounded-cycle sentinel. In watch mode, the supervisor sleeps and relaunches after this line. +- `/sandbox/.openshell-agent/status.json` is the atomic current state snapshot. Its `result.notes` field is Gator's plain-language diagnosis and next action for that cycle. +- `/sandbox/.openshell-agent/history.jsonl` contains the latest 100 supervisor transitions, including active-cycle starts and completed cycle results. - `openshell-agent: still running watch cycle ...` is a heartbeat during long active model cycles. - `review_feedback_lookup_failed` means Gator could not build the required cross-SHA feedback ledger and deliberately skipped a context-free review. +- Large PRs use local Git trees for patch identity instead of GitHub's + size-limited full-diff endpoint. An unavailable patch ID alone does not block + the ledger; it disables rebase-equivalence shortcuts. Reviewers should inspect + oversized changes file-by-file locally. +- Security regressions introduced or newly exposed by an unmerged PR belong + in its normal review findings. Private follow-up under `SECURITY.md` is for + pre-existing vulnerabilities independent of the PR; security classification + alone is not a private-triage or test-dispatch gate. Gator must not initiate + external security reporting without explicit operator authorization. ### Inspect Active Sandboxes @@ -311,16 +316,21 @@ If `sandbox get` is not supported by the local CLI shape, use `openshell sandbox | `status=terminal_failure` | Unrecoverable or stale immutable payload. | Inspect the reason; rebuild/relaunch for `stale_gator_payload`. | | `status=complete` | Target closed, merged, or one-shot complete. | Delete sandbox if no longer needed. | +Prefer the state snapshot over scraping transient `/tmp` cycle output. Use the +history file to tell whether a failure is repeating or whether the supervisor +has begun a fresh cycle. Runtime logs remain useful for full command output and +transport diagnostics. + ## Restarting A Gator Restart when the payload must change, the sandbox is wedged without a sentinel, the model/tooling version changed, or a transient failure repeats past the useful retry point. Increment `payload_version` in `scripts/agents/gator/agent.yaml` whenever a merged change alters the Gator prompt, gate skill, reviewer contract, write -guard, ledger, or bundled validator. Existing immutable watchers cannot replace -their own payload. New-version watchers detect later published versions and -stop with `stale_gator_payload`; relaunch every still-active older watcher after -the version bump is published. +guard, ledger, thread resolver, or bundled validator. Existing immutable +watchers cannot replace their own payload. New-version watchers detect later +published versions and stop with `stale_gator_payload`; relaunch every +still-active older watcher after the version bump is published. Before deleting, check that the sandbox is truly stale or that the operator asked for a restart. If a bounded review cycle is actively running and still producing useful output, prefer leaving it alone. @@ -336,7 +346,6 @@ openshell --gateway "$gateway_name" sandbox delete "$sandbox_name" --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - --background \ "" ``` @@ -368,7 +377,9 @@ Symptoms: host `gh` auth fails, Codex refresh fails, in-sandbox GitHub calls rep Actions: - Re-run the GitHub and Codex preflight checks. +- Existing refresh-managed providers are reused without an ordinary credential update; the launcher rotates their gateway-managed credential instead. - If host Codex auth changed, relaunch with `--reset-refresh` once. +- `--reset-refresh` removes the old refresh ownership before rediscovering host credentials, then configures and rotates the replacement refresh state. - If Entra or Microsoft auth is involved in a future provider, use the relevant auth skill. Gator's default providers are GitHub and Codex. ### Unsupported `gh pr view --json` Field @@ -396,7 +407,6 @@ When you launch or inspect gator, report: - Sandbox name. - Gateway name. -- Log path. - Target issue/PR scope. - Harness and model when relevant. - Whether image build and sandbox creation succeeded. diff --git a/.agents/skills/openshell-cli/SKILL.md b/.agents/skills/openshell-cli/SKILL.md deleted file mode 100644 index 69a889a8e2..0000000000 --- a/.agents/skills/openshell-cli/SKILL.md +++ /dev/null @@ -1,792 +0,0 @@ ---- -name: openshell-cli -description: Guide agents through using the OpenShell CLI (openshell) for sandbox management, gateway registration, provider configuration and refresh, policy iteration, settings, service exposure, BYOC workflows, and inference routing. Covers basic through advanced multi-step workflows. Trigger keywords - openshell, sandbox create, sandbox exec, sandbox connect, logs, provider create, provider profile, provider refresh, policy set, policy get, settings, service expose, forward, port forward, BYOC, bring your own container, inference, use openshell, run openshell, CLI usage, manage sandbox, manage provider, gateway add, gateway select. ---- - -# OpenShell CLI - -Guide agents through using the `openshell` CLI for sandbox and platform management -- from basic operations to advanced multi-step workflows. - -## Overview - -The OpenShell CLI (`openshell`) is the primary interface for managing sandboxes, providers, policies, settings, exposed services, inference routes, and gateway registrations. Gateway service lifecycle is handled outside the CLI by packages, systemd, Helm, or development tasks. This skill teaches agents how to orchestrate CLI commands for common and complex workflows. - -**Companion skill**: For creating or modifying sandbox policy YAML content (network rules, L7 inspection, access presets), use the `generate-sandbox-policy` skill. This skill covers the CLI *commands* for the policy lifecycle; `generate-sandbox-policy` covers policy *content authoring*. - -**Self-teaching**: The CLI has comprehensive built-in help. When you encounter a command or option not covered in this skill, walk the help tree: - -```bash -openshell --help # Top-level commands -openshell --help # Subcommands in a group -openshell --help # Flags for a specific command -``` - -This is your primary fallback. Use it freely -- the CLI's help output is authoritative and always up-to-date. - -## Prerequisites - -- `openshell` is on the PATH (install via `cargo install --path crates/openshell-cli`) -- A reachable OpenShell gateway backed by Docker, Podman, Kubernetes, or the experimental VM driver -- Docker is running only when using BYOC local builds or Docker-backed development workflows -- For Kubernetes deployments: `kubectl` and Helm access to the target cluster - -## Command Reference - -See [cli-reference.md](cli-reference.md) for the current command tree and commonly used flags. Use it as a quick-reference, then confirm uncommon or security-sensitive options with `--help`. - ---- - -## Workflow 1: Getting Started - -Use this workflow when the user has a gateway endpoint and wants to get a sandbox running for the first time. - -### Step 1: Register a gateway - -```bash -openshell gateway add http://127.0.0.1:8080 --local --name local -``` - -Use an `http://` endpoint only for trusted local port-forwarding or a protected private path. For a gateway behind an authenticated reverse proxy, register its HTTPS endpoint with `openshell gateway add https://gateway.example.com`. - -### Step 2: Verify the gateway - -```bash -openshell status -openshell whoami -``` - -Confirm the gateway is reachable, authentication is valid or not required, and -the output shows a version. `Status: Connected` only proves the public health -endpoint is reachable; inspect the separate `Authentication` line before -running protected commands. `openshell whoami` reports the identity validated -by the gateway, including the subject an administrator uses for workspace -membership. Add `--output json` for automation. - -### Step 3: Create a sandbox - -The simplest way to get a sandbox running: - -```bash -openshell sandbox create -``` - -This creates a sandbox whose canonical main process is `/bin/bash -l` and -attaches your terminal to that retained process. Add `--detach` to return after -the sandbox becomes ready without attaching. - -When supplying `--name`, use a portable DNS-1123 label: at most 63 lowercase alphanumeric or `-` characters, beginning and ending with an alphanumeric character. The Kubernetes driver rejects uppercase letters, underscores, dots, and other names that cannot become Kubernetes resource labels. - -**Shortcut for known tools**: When the trailing command is a recognized tool, the CLI auto-creates the required provider from local credentials: - -```bash -openshell sandbox create -- claude # Auto-creates claude provider -openshell sandbox create -- codex # Auto-creates codex provider -``` - -The agent will be prompted interactively if credentials are missing. - -### Step 4: Exit and clean up - -Exit the sandbox shell (`exit` or Ctrl-D), then: - -```bash -openshell sandbox delete -``` - ---- - -## Workflow 2: Provider Management - -Providers supply credentials and provider-specific configuration to sandboxes. Provider types come from built-in and custom profiles; do not rely on a hard-coded type list. Discover the profiles available on the selected gateway: - -```bash -openshell provider list-profiles -openshell provider list-profiles --output json -``` - -### Create a provider from local credentials - -```bash -openshell provider create --name my-github --type github --from-existing -``` - -The `--from-existing` flag discovers credentials from local state (e.g., `gh auth` tokens, Claude config files). - -### Create a provider with explicit credentials - -```bash -openshell provider create --name my-api --type generic \ - --credential API_KEY \ - --config base_url=https://api.example.com -``` - -Bare `KEY` reads the value from the environment variable of that name and avoids placing the secret in shell history. Use `KEY=VALUE` only when the user explicitly accepts that exposure. - -Other credential sources are `--from-gcloud-adc` for compatible profiles and `--runtime-credentials` when the gateway or sandbox resolves the required credentials at runtime. - -Static provider credentials resolve only for hosts, ports, and paths declared by -the provider profile. Use `provider profile export` to inspect that boundary -when a placeholder is present but requests receive -`credential_endpoint_mismatch`. A profileless static provider fails closed -because the gateway cannot construct a binding. - -When an inspected request receives `request_authority_mismatch`, compare its -HTTP authority with the CONNECT tunnel endpoint. The host and effective port -must match. For a tunnel to `api.example.com:8443`, send -`Host: api.example.com:8443`; `Host: api.example.com` omits the non-default -port and is rejected. An absolute-form request target must use the same -authority. - -Profile-backed provider policy composition is controlled by the gateway-global -`providers_v2_enabled` setting. Static credential endpoint binding remains -active even when policy composition is disabled: - -```bash -openshell settings get --global -openshell settings set --global --key providers_v2_enabled --value true -``` - -### Inspect and manage provider profiles - -```bash -openshell provider profile export github --output yaml -openshell provider profile lint --file ./my-profile.yaml -openshell provider profile import --file ./my-profile.yaml -``` - -### List, inspect, update, delete - -```bash -openshell provider list -openshell provider list --output json -openshell provider get my-github -openshell provider update my-github --from-existing -openshell provider delete my-github -``` - -`provider update` does not take `--type`. It updates credentials, config, or credential expiry on the existing provider. - -### Configure credential refresh - -Use refresh commands only when the provider profile and gateway support refreshable credentials: - -```bash -openshell provider refresh status my-outlook -openshell provider refresh configure my-outlook \ - --credential-key MS_GRAPH_ACCESS_TOKEN \ - --strategy oauth2-refresh-token \ - --secret-material-env REFRESH_TOKEN=MS_GRAPH_REFRESH_TOKEN \ - --credential-expires-at 2026-07-16T00:00:00Z -openshell provider refresh rotate my-outlook --credential-key MS_GRAPH_ACCESS_TOKEN -``` - -Prefer `--secret-material-env KEY[=ENVVAR]` for secret refresh material. `--material KEY=VALUE` is for non-secret material; `--secret-material-key` marks supplied material keys as secret. - -The gateway stores secret refresh material through its active credential driver. -With Vault selected, refresh tokens, client secrets, and private keys live in -Vault alongside injectable provider credentials; refresh state contains only -opaque handles. A credential-backend read or write failure makes refresh fail -closed rather than falling back to inline storage. Before OpenShell 0.1.0, the -gateway does not migrate legacy inline refresh material or move secrets between -credential backends. Reconfigure affected grants after upgrading, and remove or -reconfigure credentials while the original backend remains available before -changing backends. Do not run mixed gateway versions against the same refresh -records. - -Gateway-managed refresh credentials use an identity-stable workload handle. -Routine automatic refresh and `provider refresh rotate` update the access token -behind that handle, so long-running processes do not need to restart. Running -processes must be restarted once when upgrading from revision-scoped -placeholders. A later `provider refresh configure` call is an explicit -reauthorization boundary: it revokes the previous handle, and processes holding -that handle fail closed until restarted. - -While gateway-managed refresh is configured, `provider update --credential` -cannot replace or delete the refresh-owned primary credential or any co-minted -output. Use `provider refresh rotate`, reconfigure refresh, or delete refresh -before returning those keys to manual management. Unrelated provider fields -remain updateable. - ---- - -## Workflow 3: Sandbox Lifecycle - -### Create with options - -```bash -openshell sandbox create \ - --name my-sandbox \ - --provider my-github \ - --provider my-claude \ - --policy ./my-policy.yaml \ - --upload .:/workspace \ - --label team=agents \ - -- claude -``` - -Key flags: -- `--provider`: Attach configured credential providers for API keys, tokens, and other secrets (repeatable) -- `--policy`: Custom policy YAML (otherwise uses built-in default or `OPENSHELL_SANDBOX_POLICY` env var) -- `--gpu [COUNT]`: Request the driver's default GPU selection or a specific GPU count -- `--cpu`, `--memory`: Set per-sandbox compute sizing. Docker/Podman apply limits; Kubernetes applies matching requests and limits. -- `--driver-config-json`: Pass experimental driver-specific sandbox configuration -- `--label KEY=VALUE`: Add labels for later selection (repeatable) -- `--env KEY=VALUE`: Set non-secret sandbox environment variables (repeatable); use `--provider` for credentials -- `--tty`: Allocate a retained PTY for the canonical main process -- `--approval-mode manual|auto`: Control handling of agent-authored policy proposals; `manual` is the default -- `--upload [:]`: Upload local files into the container working directory or an explicit destination -- `--no-git-ignore`: Disable `.gitignore` filtering for uploads -- `--no-keep`: Delete the sandbox after the initial command or shell exits -- `--detach`: Start the canonical main process without attaching -- `--forward [BIND_ADDRESS:]PORT`: Forward a local port and keep the sandbox alive -- `--editor vscode|cursor`: Open a remote editor after creation and keep the sandbox alive - -Do not combine `--upload` with a trailing main command. Uploads currently finish -after the canonical process starts; create a scratch sandbox and use -`sandbox exec`, or build the files into the image. - -### List and inspect sandboxes - -```bash -openshell sandbox list -openshell sandbox list --selector team=agents --output json -openshell sandbox get my-sandbox -``` - -Most commands with an optional sandbox name use the last-used sandbox. Pass an explicit name in automation. - -### Connect to a running sandbox - -```bash -openshell sandbox connect my-sandbox -openshell sandbox connect my-sandbox --editor vscode -``` - -Attaches to the sandbox's existing canonical main process. Disconnecting leaves -that process running; reconnecting targets the same process instance and replays -recent output. Use `sandbox exec --tty -- /bin/bash -l` for a new shell. Press -`Ctrl-P`, then `Ctrl-Q` to disconnect without terminating main. `Ctrl-C` retains -its normal terminal behavior and interrupts the foreground process. Configure -VS Code Remote-SSH with: - -```bash -openshell sandbox ssh-config my-sandbox >> ~/.ssh/config -``` - -### Upload and download files - -```bash -# Upload local files to the sandbox working directory -openshell sandbox upload my-sandbox ./src - -# Download a path relative to the sandbox working directory -openshell sandbox download my-sandbox output ./local-output -``` - -Uploads honor `.gitignore` by default. Add `--no-git-ignore` only when ignored files are intentionally in scope. - -Uploads preserve symlinks, including dangling symlinks, instead of dereferencing their targets. A symlink source bypasses Git-aware filtering so the link itself is archived. - -When the upload destination is omitted, the CLI discovers the remote working -directory. Uploading a named directory merges it into an existing directory of -the same name, overwriting matching entries without deleting unrelated entries. -Downloads accept paths relative to that working directory or absolute paths -within it. - -### Execute a non-interactive command - -```bash -openshell sandbox exec --name my-sandbox --workdir /workspace -- ls -la -openshell sandbox exec --name my-sandbox --env MODE=test -- cargo test -``` - -`sandbox exec` starts an independent sibling process, streams output, and exits -with the remote command's exit code. Use `sandbox connect` to attach to the -canonical main process. -Use `--env` only for non-secret values. Attach credentials to the sandbox with a -provider instead of passing API keys, tokens, or other secrets to `sandbox exec`. - -### Change attached providers - -```bash -openshell sandbox provider list my-sandbox -openshell sandbox provider attach my-sandbox my-github -openshell sandbox provider detach my-sandbox my-github -``` - -### View logs - -```bash -# Recent logs -openshell logs my-sandbox - -# Stream live logs -openshell logs my-sandbox --tail - -# Filter by source and level -openshell logs my-sandbox --tail --source sandbox --level warn - -# Logs from the last 5 minutes -openshell logs my-sandbox --since 5m -``` - -### Delete sandboxes - -```bash -openshell sandbox delete my-sandbox -openshell sandbox delete sandbox-1 sandbox-2 sandbox-3 # Multiple at once -openshell sandbox delete --all -``` - -### Stop and start sandboxes - -Use stop to halt compute while retaining the sandbox and its persistent -workspace: - -```bash -openshell sandbox stop [name] -openshell sandbox start [name] -``` - -Both commands default to the last-used sandbox. Stop stops background -forwards and waits for `Stopped`; start waits for `Ready`. Connect, exec, -file transfer, forwarding, and exposed services are unavailable while -stopped. Delete remains the operation that removes retained state. - ---- - -## Workflow 4: Policy Iteration Loop - -This is the most important multi-step workflow. It enables a tight feedback cycle where sandbox policy is refined based on observed activity. - -**Key concept**: Policies have static fields (immutable after creation: `filesystem_policy`, `landlock`, `process`) and two dynamic fields: `network_policies` and `network_middlewares`. Both dynamic fields can be updated without recreating the sandbox. - -An endpoint with omitted `protocol` retains explicit-proxy behavior. Explicit -`protocol: tcp` requests policy DNS and transparent TCP and currently requires -the Docker or Podman runtime; unsupported runtimes reject the policy before starting the -workload rather than activating only part of the network contract. - -``` -Create sandbox with initial policy - │ - ▼ - Monitor logs ◄──────────────────┐ - │ │ - ▼ │ - Observe denied actions │ - │ │ - ▼ │ - Pull current policy │ - │ │ - ▼ │ - Modify policy YAML │ - (use generate-sandbox-policy) │ - │ │ - ▼ │ - Push updated policy │ - │ │ - ▼ │ - Verify reload succeeded ─────────┘ -``` - -### Step 1: Create sandbox with initial policy - -```bash -openshell sandbox create --name dev --policy ./initial-policy.yaml -- claude -``` - -Sandboxes stay alive by default for iteration. Add `--no-keep` only when the sandbox should be deleted automatically after the initial session. - -### Step 2: Monitor logs for denied actions - -In a separate terminal or as the agent: - -```bash -openshell logs dev --tail --source sandbox -``` - -Look for log lines with `action: deny` -- these indicate blocked network requests. The logs include: -- **Destination host and port** (what was blocked) -- **Binary path** (which process attempted the connection) -- **Deny reason** (why it was blocked) - -### Step 3: Pull the current policy - -```bash -openshell policy get dev --full > current-policy.yaml -``` - -The `--full` flag includes the effective policy, including provider-composed entries. Use `--base` instead when the editable base policy is needed without provider-composed entries. Before resubmitting a `--full` result, review composed entries and prefer incremental updates or the base policy when appropriate. - -### Step 4: Modify the policy - -Edit `current-policy.yaml` to allow the blocked actions. **For policy content authoring, delegate to the `generate-sandbox-policy` skill.** That skill handles: -- Network endpoint rule structure -- L4 vs REST, WebSocket, JSON-RPC, MCP, and SQL L7 policy decisions -- Access presets (`read-only`, `read-write`, `full`) -- TLS termination configuration -- Enforcement modes (`audit` vs `enforce`) -- Binary matching patterns -- Ordered `network_middlewares`, host selection, HTTP and WebSocket bindings, and `fail_open` or `fail_closed` behavior - -`network_policies` and `network_middlewares` can be modified at runtime. If `filesystem_policy`, `landlock`, or `process` need changes, the sandbox must be recreated. Built-in middleware such as `openshell/regex` needs no gateway registration. An operator-run middleware must already be registered under `[[openshell.supervisor.middleware]]`; changing that static registration requires a gateway restart. - -Middleware can inspect parsed HTTP request bodies and complete client-to-upstream WebSocket text messages over both `ws://` and `wss://` when the implementation advertises the matching binding. The built-in `openshell/regex` advertises both bindings and applies its fixed patterns to UTF-8 text. A host-matched HTTP-only attachment can inspect the upgrade GET but does not join the WebSocket chain; look for `binding_not_selected` coverage. Binary messages pass under both `on_error` modes and active stages emit `unsupported_message_type` coverage; upstream-to-client messages remain uninspected. A broken fail-open WebSocket stage is disabled for the rest of that connection; inspect sandbox OCSF logs for `openshell.middleware.websocket_stage_disabled`. - -### Step 5: Push the updated policy - -```bash -openshell policy set dev --policy current-policy.yaml --wait -``` - -The gateway validates the complete effective candidate—including attached -provider-profile policy—before it stores a direct update, incremental merge, -approved proposal, provider attachment, or profile update that affects attached -sandboxes. An ambiguity failure returns `FAILED_PRECONDITION`; the rejected -candidate does not create a policy revision or partially update affected -sandboxes. The same fail-closed response applies when `credential_signing` -does not have an attached AWS profile whose credential boundary covers the -endpoint, or an explicit binding to an endpointless AWS profile. Fix the -conflicting endpoint selectors or credential source and submit again. - -The `--wait` flag blocks until the sandbox confirms the policy is loaded (polls every second). Exit codes: -- **0**: Policy loaded successfully -- **1**: Policy load failed -- **124**: Timeout (default 60 seconds) - -### Step 6: Verify the update - -```bash -openshell policy list dev -``` - -Check that the latest revision shows status `loaded`. If `failed`, check the error column for details. - -### Step 7: Repeat - -Return to Step 2. Continue monitoring logs and refining the policy until all required actions are allowed and no unnecessary permissions exist. - -### Policy revision history - -View all revisions to understand how the policy evolved: - -```bash -openshell policy list dev --limit 50 -``` - -Fetch a specific historical revision: - -```bash -openshell policy get dev --rev 3 --full -``` - -Gateway-global policy commands use `--global` and require confirmation unless `--yes` is supplied: - -```bash -openshell policy get --global --full -openshell policy set --global --policy ./global-policy.yaml -openshell policy list --global -openshell policy delete --global -``` - -Avoid `--yes` during interactive work. A global policy locks policy control for all sandboxes on the gateway. - -### Review agent-authored rule proposals - -Sandboxes created with `--approval-mode manual` place every proposal in the review inbox. `auto` approves only valid effective-policy candidates with an empty prover delta; findings still require review. The CLI binds approval to the candidate's current review token. If live policy, provider, or credential inputs change, approval leaves the chunk pending with a refreshed candidate and requires a fresh review. - -```bash -openshell rule get dev --status pending -openshell rule approve dev --chunk-id -openshell rule reject dev --chunk-id --reason "too broad" -openshell rule history dev -``` - -Review the proposed scope, candidate hash, prover findings, and application errors before approval. Treat `rule approve-all --include-security-flagged` as a high-risk bulk action. - ---- - -## Workflow 5: BYOC (Bring Your Own Container) - -Build a custom container image and run it as a sandbox. - -### Create a sandbox from a Dockerfile - -```bash -openshell sandbox create --from ./Dockerfile --name my-app -``` - -The `--from` flag accepts a Dockerfile path, a directory containing a Dockerfile, a full image reference such as `myregistry.com/img:tag`, or a community sandbox name such as `ollama`. - -Local Dockerfile and directory builds require a local gateway because the CLI builds through the local Docker daemon. Use a registry image reference for remote gateways. Bare community names resolve under `ghcr.io/nvidia/openshell-community/sandboxes` unless `OPENSHELL_COMMUNITY_REGISTRY` overrides the prefix. - -For Docker and Podman gateways, custom images should declare a non-root OCI -`USER`. Each explicit `process.run_as_user` or `process.run_as_group` policy -field wins independently; omitted fields fall back to the image declaration. -An image with no `USER` fails before readiness unless policy supplies both -fields. Explicit numeric fields may use any UID/GID from `1` through -`4294967294`; `0` is root and `4294967295` is the invalid identity sentinel. -Warn users that low IDs can inherit permissions from matching accounts, image -files, mounted volumes, or devices. - -### Forward ports - -```bash -# Foreground (blocks) -openshell forward start 8080 my-app - -# Background (returns immediately) -openshell forward start 8080 my-app -d -``` - -The service is now reachable at `localhost:8080`. - -Manage or iterate on the sandbox: - -```bash -openshell forward list -openshell forward stop 8080 my-app -openshell sandbox delete my-app -openshell sandbox create --from ./Dockerfile --name my-app --forward 8080 -``` - -Create and forward in one command: - -```bash -openshell sandbox create --from ./Dockerfile --forward 8080 -- ./start-server.sh -``` - -The `--forward` flag starts a background port forward before the command runs. - -## Workflow 6: Agent-Assisted Sandbox Session - -Support a human working in a sandbox while an agent monitors activity and refines the policy in parallel. - -Create the sandbox and keep it alive: - -```bash -openshell sandbox create \ - --name work-session \ - --provider github \ - --provider claude \ - --policy ./dev-policy.yaml -``` - -Tell the user to connect in another shell: - -```bash -openshell sandbox connect work-session -openshell sandbox connect work-session --editor vscode -``` - -Monitor denied activity: - -```bash -openshell logs work-session --tail --source sandbox --level warn -``` - -When denied actions appear: - -1. Prefer incremental updates for additive network changes: - `openshell policy update work-session --add-endpoint api.github.com:443:read-only:rest:enforce --binary /usr/bin/gh --wait` - `openshell policy update work-session --add-allow 'api.github.com:443:POST:/repos/*/issues' --wait` - - A rule authorizes every binary it lists to reach every endpoint it lists, so - an update that adds a binary or an endpoint to an existing rule must declare - that rule's whole binary and endpoint scope. The gateway rejects an update - that would grant a binary-to-endpoint pair the update never asked for, and - the error names the binaries still missing. To grant one binary access to - only part of a rule's endpoints, send the narrow authorization under its own - `--rule-name`; it stays on its own rule instead of folding into the broader - one. - - `--add-allow` and `--add-deny` select an endpoint by host and port alone. If - that host and port appears in more than one rule, or twice in one rule under - different paths, the update is rejected as ambiguous. Fall back to full YAML - replacement for those endpoints. -2. Use full YAML replacement for broad changes or non-network fields, including - any change that would otherwise require restating a large existing scope: - `openshell policy get work-session --full > policy.yaml` - Modify the policy with the `generate-sandbox-policy` skill. - `openshell policy set work-session --policy policy.yaml --wait` -3. Verify with `openshell policy list work-session`. - -The user does not need to disconnect. Policy updates are hot-reloaded; `--wait` blocks until the sandbox confirms the revision or the timeout expires. Delete the sandbox when the session ends: - -```bash -openshell sandbox delete work-session -``` - -## Workflow 7: Managed Inference - -Configure the user-facing `inference.local` route or the system inference route used by platform functions. - -Ensure the provider exists, then set the route: - -```bash -openshell provider list -openshell inference set \ - --provider nvidia \ - --model nvidia/nemotron-3-nano-30b-a3b -``` - -This updates the managed `inference.local` route. Endpoint verification runs before the route is saved. Use `--no-verify` only when verification is intentionally impossible, and use `--timeout SECONDS` to configure the request timeout. Add `--system` to `set` or `update` for the platform-only system route. - -Inspect both configurations: - -```bash -openshell inference get -openshell inference get --system -``` - -Agents send HTTPS requests to `inference.local`; the sandbox intercepts them and routes them through the configured inference route. Sandbox policy remains separate from inference route configuration. - -## Workflow 8: Gateway Management - -List, switch, and verify gateways: - -```bash -openshell gateway select -openshell gateway list --output json -openshell gateway select production -openshell gateway info --name production -openshell status -``` - -Register or remove gateways: - -```bash -openshell gateway add http://127.0.0.1:8080 --local --name local -openshell gateway add https://gateway.example.com --name production -openshell gateway remove local -``` - -`https://` registrations default to edge authentication. Use `gateway login` and `gateway logout` to refresh or clear stored authentication. For an OIDC gateway, supply `--oidc-issuer` and, when needed, `--oidc-client-id`, `--oidc-audience`, and `--oidc-scopes`. For remote mTLS gateways, use `--remote USER@HOST` or an `ssh://` endpoint. - -For one-off automation, `--gateway-endpoint URL` connects directly without stored metadata. Limit `--gateway-insecure` to explicitly trusted development endpoints. - -Inspect a Kubernetes deployment: - -```bash -helm -n openshell status openshell -kubectl -n openshell get deployment,statefulset,pods,svc -kubectl -n openshell logs deployment/openshell -c openshell-gateway --tail=100 -kubectl -n openshell logs statefulset/openshell -c openshell-gateway --tail=100 -``` - -For Docker, Podman, and VM-backed gateways, inspect the gateway process or container logs and the selected runtime directly. - -## Workflow 9: Settings Management - -Manage sandbox-scoped or gateway-global settings: - -```bash -openshell settings get work-session -openshell settings set work-session --key ocsf_json_enabled --value true -openshell settings delete work-session --key ocsf_json_enabled - -openshell settings get --global --json -openshell settings set --global --key providers_v2_enabled --value true -``` - -Global mutations prompt for confirmation. Use `--yes` only in reviewed automation. - -`policy_validation_failure_mode` is gateway startup configuration, not a -mutable `openshell settings` key. Set it under `[openshell.gateway]` in -`gateway.toml` and restart the gateway. The security-first default is -`fail_closed`; `retain_last_valid` is an explicit availability tradeoff. OCSF -configuration events state whether the previous generation is active after a -runtime validation failure. - -## Workflow 10: Service Access - -Use `forward` for local access and `service` for a gateway-managed HTTP endpoint: - -```bash -# SSH-based same-port forwarding; optional bind address is accepted. -openshell forward start 127.0.0.1:8080 my-app -d - -# gRPC relay to a loopback TCP service, with an optional dynamic local port. -openshell forward service my-app --target-port 8000 --local 127.0.0.1:0 - -# Expose and manage an HTTP service through the gateway. -openshell service expose my-app 8080 web -openshell service list my-app -openshell service get my-app web -openshell service delete my-app web -``` - -Prefer loopback binds unless the user explicitly needs LAN-visible local access. - ---- - -## Self-Teaching via `--help` - -When you encounter a command or option not covered in this skill: - -1. **Start broad**: `openshell --help` to see all command groups. -2. **Narrow down**: `openshell --help` to see subcommands (e.g., `openshell sandbox --help`). -3. **Get specific**: `openshell --help` for flags and usage (e.g., `openshell sandbox create --help`). - -The CLI help is always authoritative. If the help output contradicts this skill, follow the help output -- the CLI may have been updated since this skill was written. - -### Example: discovering an unfamiliar command - -```bash -$ openshell sandbox --help -# Shows: create, get, list, stop, start, delete, exec, connect, upload, download, ssh-config, provider - -$ openshell sandbox upload --help -# Shows: positional arguments (name, path, dest), usage examples -``` - ---- - -## Quick Reference - -| Task | Command | -|------|---------| -| Register local port-forwarded gateway | `openshell gateway add http://127.0.0.1:8080 --local --name local` | -| Check gateway health and authentication | `openshell status` | -| Show authenticated identity and subject | `openshell whoami` | -| List/switch gateways | `openshell gateway select [name]` | -| Connect directly to a gateway | `openshell --gateway-endpoint status` | -| Create sandbox (interactive) | `openshell sandbox create` | -| Create sandbox with tool | `openshell sandbox create -- claude` | -| Create sandbox with GPUs | `openshell sandbox create --gpu 1` | -| Create with custom policy | `openshell sandbox create --policy ./p.yaml` | -| Connect to sandbox | `openshell sandbox connect ` | -| Stop sandbox compute | `openshell sandbox stop [name]` | -| Start sandbox compute | `openshell sandbox start [name]` | -| Execute in sandbox | `openshell sandbox exec --name -- ` | -| Stream live logs | `openshell logs --tail` | -| Incremental policy update | `openshell policy update --add-endpoint host:443:read-only:rest:enforce --binary /usr/bin/curl --wait` | -| Pull current policy | `openshell policy get --full > p.yaml` | -| Push updated policy | `openshell policy set --policy p.yaml --wait` | -| Policy revision history | `openshell policy list ` | -| View global policy | `openshell policy get --global --full` | -| Review proposed rules | `openshell rule get --status pending` | -| Create sandbox from Dockerfile | `openshell sandbox create --from ./Dockerfile` | -| Forward a port | `openshell forward start -d` | -| Expose an HTTP service | `openshell service expose [service]` | -| Upload files to sandbox | `openshell sandbox upload ` | -| Download files from sandbox | `openshell sandbox download ` | -| Create provider | `openshell provider create --name N --type T --from-existing` | -| List providers | `openshell provider list` | -| Discover provider profiles | `openshell provider list-profiles` | -| List attached providers | `openshell sandbox provider list ` | -| View settings | `openshell settings get [name]` | -| Configure managed inference | `openshell inference set --provider P --model M` | -| View managed inference | `openshell inference get` | -| Delete sandbox | `openshell sandbox delete ` | -| Remove gateway registration | `openshell gateway remove ` | -| Self-teach any command | `openshell --help` | - -## Companion Skills - -| Skill | When to use | -|-------|------------| -| `generate-sandbox-policy` | Creating or modifying policy YAML content (network rules, L7 inspection, access presets, endpoint configuration, and network middleware) | -| `debug-openshell-cluster` | Diagnosing gateway deployment, runtime, or health failures | -| `debug-inference` | Diagnosing `inference.local`, host-backed local inference, and provider base URL issues | -| `tui-development` | Developing features for the OpenShell TUI (`openshell term`) | diff --git a/.agents/skills/openshell-cli/cli-reference.md b/.agents/skills/openshell-cli/cli-reference.md deleted file mode 100644 index 695aceedfe..0000000000 --- a/.agents/skills/openshell-cli/cli-reference.md +++ /dev/null @@ -1,612 +0,0 @@ -# OpenShell CLI Reference - -Quick-reference for the `openshell` command-line interface. For workflow guidance, see [SKILL.md](SKILL.md). - -> **Self-teaching**: If a command or flag is not listed here, use `openshell --help` to discover it. The CLI has comprehensive built-in help at every level. - -## Global Options - -| Flag | Description | -|------|-------------| -| `-v`, `--verbose` | Increase verbosity (`-v` = info, `-vv` = debug, `-vvv` = trace) | -| `-g`, `--gateway ` | Gateway to operate on. Also settable via `OPENSHELL_GATEWAY` env var. Falls back to active gateway in `~/.config/openshell/active_gateway`. | -| `--gateway-endpoint ` | Connect directly to a gateway endpoint without looking up stored metadata. Also settable via `OPENSHELL_GATEWAY_ENDPOINT`. | -| `--gateway-insecure` | Skip TLS certificate verification. Also settable via `OPENSHELL_GATEWAY_INSECURE`; use only for trusted development endpoints. | - -## Environment Variables - -| Variable | Description | -|----------|-------------| -| `OPENSHELL_GATEWAY` | Override active gateway name (same as `--gateway`) | -| `OPENSHELL_GATEWAY_ENDPOINT` | Connect directly to a gateway endpoint (same as `--gateway-endpoint`) | -| `OPENSHELL_GATEWAY_INSECURE` | Skip TLS verification when set (same as `--gateway-insecure`) | -| `OPENSHELL_SANDBOX_POLICY` | Path to default sandbox policy YAML (fallback when `--policy` is not provided) | -| `OPENSHELL_COMMUNITY_REGISTRY` | Override the community sandbox image registry prefix used by `sandbox create --from ` | -| `OPENSHELL_THEME` | TUI theme: `auto`, `dark`, or `light` | - ---- - -## Complete Command Tree - -``` -openshell -├── gateway -│ ├── add [opts] -│ ├── login [name] -│ ├── logout [name] -│ ├── remove [name] -│ ├── info [--name] -│ ├── list -│ └── select [name] -├── status -├── whoami [--output ] -├── inference -│ ├── set --provider --model -│ ├── update [--provider] [--model] -│ └── get -├── sandbox -│ ├── create [opts] [-- CMD...] -│ ├── get [name] -│ ├── list [opts] -│ ├── stop [name] -│ ├── start [name] -│ ├── delete [name]... [--all] -│ ├── exec [--name ] [opts] -- CMD... -│ ├── connect [name] [--editor ] -│ ├── upload [dest] -│ ├── download [dest] -│ ├── ssh-config [name] -│ └── provider -│ ├── list [name] -│ ├── attach -│ └── detach -├── forward -│ ├── start [name] [-d] -│ ├── stop [name] -│ ├── list -│ └── service [name] --target-port [opts] -├── service -│ ├── expose [service] -│ ├── list [sandbox] -│ ├── get [service] -│ └── delete [service] -├── logs [name] [opts] -├── policy -│ ├── set [name] --policy [--global] [--wait] -│ ├── update [name] [opts] -│ ├── get [name] [--full|--base] [--global] -│ ├── list [name] [--global] -│ ├── delete --global -│ └── prove --policy --credentials [opts] -├── settings -│ ├── get [name] [--global] -│ ├── set [name] --key --value [--global] -│ └── delete [name] --key [--global] -├── rule (advanced; hidden from top-level help) -│ ├── get [name] [--status ] -│ ├── approve [name] --chunk-id -│ ├── reject [name] --chunk-id [--reason ] -│ ├── approve-all [name] [--include-security-flagged] -│ ├── clear [name] -│ └── history [name] -├── provider -│ ├── create --name --type [opts] -│ ├── refresh -│ │ ├── status [opts] -│ │ ├── configure [opts] -│ │ ├── rotate --credential-key -│ │ └── delete --credential-key -│ ├── get -│ ├── list [opts] -│ ├── list-profiles [opts] -│ ├── profile -│ │ ├── export [opts] -│ │ ├── import (--file |--from ) -│ │ ├── update --file -│ │ ├── lint (--file |--from ) -│ │ └── delete -│ ├── update [opts] -│ └── delete ... -├── doctor -│ └── check -├── term -├── completions -└── ssh-proxy [opts] -``` - ---- - -## Gateway Commands - -### `openshell gateway add ` - -Register an existing gateway endpoint. - -| Flag | Description | -|------|-------------| -| `--name ` | Gateway name | -| `--local` | Register a local mTLS gateway; with HTTP, store a local plaintext registration | -| `--remote ` | Register a remote mTLS gateway over SSH; with HTTP, store a remote plaintext registration | -| `--oidc-issuer ` | Register an OIDC-authenticated gateway | -| `--oidc-client-id ` | OIDC client ID (default: `openshell-cli`; requires `--oidc-issuer`) | -| `--oidc-audience ` | OIDC API audience (requires `--oidc-issuer`) | -| `--oidc-scopes ` | Space-separated OAuth2 scopes (requires `--oidc-issuer`) | - -Examples: - -- `openshell gateway add http://127.0.0.1:8080 --local --name local` -- `openshell gateway add https://gateway.example.com --name production` -- `openshell gateway add ssh://user@gateway.example.com:8080 --name remote` - -An `http://` endpoint is direct plaintext. A plain `https://` endpoint uses edge authentication. `--local` and `--remote` select mTLS registration modes when used with HTTPS; required certificates must already exist. An `ssh://` endpoint is shorthand for a remote gateway. - -### `openshell gateway remove [name]` - -Remove a local gateway registration. This removes CLI metadata and stored auth tokens only; package managers, systemd, Helm, Docker, and other platform tools still own the gateway process. - -### `openshell gateway login [name]` - -Refresh browser-based authentication for an edge-authenticated or OIDC gateway. - -### `openshell gateway logout [name]` - -Clear locally stored OIDC or edge credentials for a gateway. - -### `openshell gateway info` - -Show gateway details: endpoint, auth mode, and remote host metadata when present. - -| Flag | Description | -|------|-------------| -| `--name ` | Gateway name (defaults to active) | - -### `openshell gateway select [name]` - -Set the active gateway. Writes to `~/.config/openshell/active_gateway`. Without a name, opens an interactive chooser on a TTY or lists gateways in non-interactive mode. - -### `openshell gateway list` - -List registered gateways and mark the active one. `--output table|yaml|json` selects the format. - ---- - -## Doctor Commands - -### `openshell doctor check` - -Validate local Docker prerequisites for standalone gateway development. For -package-managed or Helm gateways, use `systemctl`, `journalctl`, `kubectl`, and -`helm` directly. - ---- - -## Status Command - -### `openshell status` - -Show server connectivity, authentication status, and version for the active -gateway. Connectivity uses the public health RPC; authentication is checked -with the protected gateway-info capability query and can fail while the gateway -remains connected. - -### `openshell whoami` - -Show the authenticated user identity: subject, display name, roles, scopes, and -identity provider. Requires an authenticated gateway connection. - -| Flag | Description | -|------|-------------| -| `--output ` | Output format: `table` (default), `json`, or `yaml` | - ---- - -## Sandbox Commands - -### `openshell sandbox create [OPTIONS] [-- COMMAND...]` - -Create a sandbox through the selected gateway and launch its canonical main -process. By default, the CLI attaches to that retained process after the -sandbox becomes ready. A trailing command defines the canonical main process; -without one, the default is `/bin/bash -l` with a PTY. - -| Flag | Description | -|------|-------------| -| `--name ` | Sandbox name (auto-generated if omitted) | -| `--from ` | Community name, Dockerfile path, directory, or image reference (BYOC) | -| `--no-keep` | Delete the sandbox after the initial command or shell exits | -| `--detach` | Start the canonical main process without attaching | -| `--editor vscode|cursor` | Launch a remote editor and keep the sandbox alive | -| `--gpu [COUNT]` | Request the driver's default GPU selection or a specific count | -| `--cpu ` | CPU limit (for example: `500m`, `1`, `2.5`) | -| `--memory ` | Memory limit (for example: `512Mi`, `4Gi`, `8G`) | -| `--driver-config-json ` | Experimental driver-keyed configuration object | -| `--provider ` | Provider to attach (repeatable) | -| `--policy ` | Custom policy YAML; overrides the built-in default and `OPENSHELL_SANDBOX_POLICY` | -| `--forward <[BIND:]PORT>` | Start a local port forward and keep the sandbox alive | -| `--tty`, `--no-tty` | Force or disable pseudo-terminal allocation | -| `--auto-providers` | Auto-create missing providers from local credentials | -| `--no-auto-providers` | Never auto-create providers; error if a required provider is missing | -| `--label ` | Attach a label (repeatable) | -| `--env ` | Inject an environment variable (repeatable) | -| `--approval-mode manual|auto` | Handle agent-authored policy proposals; default: `manual` | -| `--upload [:]` | Upload local files to the working directory or an explicit destination (repeatable) | -| `--no-git-ignore` | Disable `.gitignore` filtering for `--upload` | -| `[-- COMMAND...]` | Canonical main command (defaults to `/bin/bash -l`) | - -`--upload` cannot be combined with a trailing main command because uploads -currently complete after the canonical process starts. Create the default -scratch sandbox, upload files, then use `sandbox exec`, or build the files into -the image. - -### `openshell sandbox get [name]` - -Show sandbox details and the active policy. Metadata identifies sandbox or global policy source and the corresponding revision. The name defaults to the last-used sandbox. - -| Flag | Description | -|------|-------------| -| `--policy-only` | Print only the active policy YAML to stdout | - -### `openshell sandbox list` - -| Flag | Default | Description | -|------|---------|-------------| -| `--limit ` | 100 | Maximum sandboxes | -| `--offset ` | 0 | Pagination offset | -| `--ids` | false | Print only sandbox IDs | -| `--names` | false | Print only sandbox names | -| `--selector ` | none | Filter by `key1=value1,key2=value2` | -| `--output table|yaml|json` | `table` | Output format | - -### `openshell sandbox delete [NAME]...` - -Delete one or more named sandboxes, or use `--all`. Deletion stops background port forwards. - -### `openshell sandbox stop [name]` - -Stop sandbox compute while retaining the sandbox and persistent workspace. The -name defaults to the last-used sandbox. The command stops background forwards -and waits for the `Stopped` phase. - -### `openshell sandbox start [name]` - -Start a stopped sandbox and wait for `Ready`. The name defaults to the -last-used sandbox. - -### `openshell sandbox exec [OPTIONS] -- COMMAND...` - -Execute a command through the gRPC exec endpoint, stream its output, and exit with the remote command's exit code. - -| Flag | Default | Description | -|------|---------|-------------| -| `-n`, `--name ` | last-used | Sandbox name | -| `--workdir ` | none | Working directory in the sandbox | -| `--timeout ` | 0 | Command timeout; `0` disables it | -| `--tty`, `--no-tty` | auto | Force or disable a pseudo-terminal | -| `--env ` | none | Command environment variable (repeatable) | - -### `openshell sandbox connect [name]` - -Attach to the sandbox's retained canonical main process. Disconnecting leaves -the process running. Reconnecting targets the same process instance and -replays recent output. Use `sandbox exec --tty -- /bin/bash -l` when you need a -new shell. The name defaults to the last-used sandbox. - -`--editor vscode|cursor` launches a supported remote editor instead of -attaching to the canonical main process. - -### `openshell sandbox upload [dest]` - -Upload files using tar-over-SSH. The CLI discovers the canonical remote working directory when the destination is omitted. A named directory merges into an existing directory of the same name, overwriting matching entries without deleting unrelated entries. `.gitignore` filtering is enabled unless `--no-git-ignore` is passed. - -### `openshell sandbox download [dest]` - -Download files using tar-over-SSH. The sandbox source may be relative to the canonical remote working directory or an absolute path within it. The local destination defaults to `.`. - -### `openshell sandbox ssh-config [name]` - -Print an SSH config `Host` block. The name defaults to the last-used sandbox. - -### `openshell sandbox provider` - -Manage providers on an existing sandbox: - -- `openshell sandbox provider list [name]` -- `openshell sandbox provider attach ` -- `openshell sandbox provider detach ` - ---- - -## Port Forwarding Commands - -### `openshell forward start [name]` - -Start forwarding a local port to a sandbox. - -| Flag | Description | -|------|-------------| -| `` | `[bind_address:]port`; the port is used locally and remotely | -| `[name]` | Sandbox name (defaults to last-used) | -| `-d`, `--background` | Run in background | - -### `openshell forward stop [name]` - -Stop a background port forward. When the sandbox name is omitted, it is inferred from active forwards. - -### `openshell forward list` - -List all active port forwards (sandbox, port, PID, status). - -### `openshell forward service [name] --target-port ` - -Forward a local TCP port to a loopback service inside a sandbox over the gRPC relay. - -| Flag | Default | Description | -|------|---------|-------------| -| `--target-port ` | required | Service port inside the sandbox | -| `--target-host ` | `127.0.0.1` | Loopback service host | -| `--local <[BIND:]PORT>` | target port | Local bind; port `0` requests dynamic assignment | - ---- - -## Service Commands - -Gateway-managed HTTP service endpoints: - -- `openshell service expose [service]` -- `openshell service list [sandbox] [--limit N] [--offset N]` -- `openshell service get [service]` -- `openshell service delete [service]` - ---- - -## Logs Command - -### `openshell logs [name]` - -View sandbox logs. Supports one-shot and streaming. - -| Flag | Default | Description | -|------|---------|-------------| -| `-n ` | 200 | Number of log lines | -| `--tail` | false | Stream live logs | -| `--since ` | none | Only show logs from this duration ago (e.g., `5m`, `1h`) | -| `--source ` | `all` | Filter: `gateway`, `sandbox`, or `all` (repeatable) | -| `--level ` | none | Minimum level: `error`, `warn`, `info`, `debug`, `trace` | - -The sandbox name defaults to the last-used sandbox. - ---- - -## Policy Commands - -### `openshell policy update [name]` - -Incrementally merge live network policy changes into the current sandbox policy. Multiple flags in one invocation are applied as one atomic batch and create at most one new revision. - -| Flag | Default | Description | -|------|---------|-------------| -| `--add-endpoint ` | repeatable | `host:port[:access[:protocol[:enforcement[:options]]]]`. Adds or merges an endpoint. | -| `--remove-endpoint ` | repeatable | `host:port`. Removes the endpoint or just the requested port from a multi-port endpoint. | -| `--add-allow ` | repeatable | `host:port:METHOD:path_glob`. Adds REST or WebSocket allow rules. | -| `--add-deny ` | repeatable | `host:port:METHOD:path_glob`. Adds REST or WebSocket deny rules. | -| `--remove-rule ` | repeatable | Deletes a named network rule. | -| `--binary ` | repeatable | Adds binaries to each `--add-endpoint` rule. Valid only with `--add-endpoint`. | -| `--rule-name ` | none | Overrides the generated rule name. Valid only when exactly one `--add-endpoint` is provided. | -| `--dry-run` | false | Preview the merged policy locally without sending an update to the gateway. | -| `--wait` | false | Wait for the sandbox to confirm the new policy revision is loaded. | -| `--timeout ` | 60 | Timeout for `--wait`. | - -Notes: - -- The sandbox name defaults to the last-used sandbox. -- `--add-endpoint` options are comma-separated: `allowed-ip=`, `websocket-credential-rewrite`, `request-body-credential-rewrite`, and `allow-uninspected-credentials`. The last option is a security-sensitive exception for provider-credentialed L4-only, `tls: skip`, or otherwise uninspectable traffic. -- `protocol` accepts `tcp` for explicit L4-only host/port policy. It has the - same payload-handling behavior as omitting the protocol, but it requires a - valid DNS hostname and rejects hostless `allowed_ips` or literal-IP - selectors. It cannot be combined with `access`, `rules`, or L7 enforcement - options. -- `--add-allow` and `--add-deny` operate on REST and WebSocket endpoints. Use full YAML for JSON-RPC, MCP, SQL, or other policy structure. -- `--wait` cannot be combined with `--dry-run`. -- Use `policy set` when replacing the full policy or changing static sections. - -### `openshell policy set [name] --policy ` - -Replace the full policy on a live sandbox. Only the dynamic `network_policies` field can be changed at runtime. - -| Flag | Default | Description | -|------|---------|-------------| -| `--policy ` | -- | Path to policy YAML (required) | -| `--global` | false | Apply as the gateway-global policy | -| `--yes` | false | Skip confirmation for a global update | -| `--wait` | false | Wait for sandbox to confirm policy is loaded | -| `--timeout ` | 60 | Timeout for `--wait` | - -Exit codes with `--wait`: 0 = loaded, 1 = failed, 124 = timeout. - -### `openshell policy get [name]` - -Show the current effective sandbox policy or stored global policy. - -| Flag | Default | Description | -|------|---------|-------------| -| `--rev ` | 0 | Show a stored revision; `0` shows the current effective policy | -| `--full` | false | Include the effective policy payload and provider-composed entries | -| `--base` | false | Include the base policy payload without provider-composed entries | -| `--output table|json` | `table` | Output format | -| `--global` | false | Show the global policy revision | - -### `openshell policy list [name]` - -List policy revision history (version, hash, status, created, error). - -| Flag | Default | Description | -|------|---------|-------------| -| `--limit ` | 20 | Max revisions to return | -| `--global` | false | List global policy revisions | - -### `openshell policy delete --global` - -Delete the global policy lock and restore sandbox-level policy control. `--yes` skips confirmation. - -### `openshell policy prove` - -Prove policy properties or find counterexamples. - -| Flag | Description | -|------|-------------| -| `--policy ` | Policy YAML (required) | -| `--credentials ` | Credential descriptor YAML (required) | -| `--registry ` | Capability registry directory (defaults to bundled) | -| `--accepted-risks ` | Accepted-risks YAML | -| `--compact` | One-line-per-finding output | - -### `openshell rule` (advanced) - -Review agent-authored network rule proposals. This command group is intentionally hidden from top-level help but is part of the policy-advisor workflow. - -- `openshell rule get [name] [--status pending|approved|rejected]` -- `openshell rule approve [name] --chunk-id ` -- `openshell rule reject [name] --chunk-id [--reason ]` -- `openshell rule approve-all [name] [--include-security-flagged]` -- `openshell rule clear [name]` -- `openshell rule history [name]` - -Sandbox names default to the last-used sandbox. The CLI fetches and submits each proposal's current review token; a changed live candidate remains pending until it is reviewed again. Bulk approval of security-flagged proposals requires explicit `--include-security-flagged`. - ---- - -## Settings Commands - -Settings support sandbox and gateway-global scopes: - -- `openshell settings get [name] [--global] [--json]` -- `openshell settings set [name] --key --value [--global] [--yes]` -- `openshell settings delete [name] --key [--global] [--yes]` - -Sandbox names default to the last-used sandbox. Global mutations prompt unless `--yes` is passed. - ---- - -## Provider Commands - -Provider types are defined by built-in and custom provider profiles. Use `openshell provider list-profiles` to discover the selected gateway's current inventory. - -### `openshell provider create --name --type ` - -Create a provider configuration. - -| Flag | Description | -|------|-------------| -| `--name ` | Provider name (required) | -| `--type ` | Provider type (required) | -| `--from-existing` | Load credentials and config from local state | -| `--credential KEY[=VALUE]` | Credential pair. Bare `KEY` reads from env var. Repeatable. | -| `--from-gcloud-adc` | Load a compatible credential from gcloud Application Default Credentials | -| `--runtime-credentials` | Resolve required credentials at runtime in the gateway or sandbox | -| `--config KEY=VALUE` | Config key/value pair. Repeatable. | - -Exactly one credential source is required. Credential-source flags conflict with one another. - -### `openshell provider get ` - -Show provider details (id, name, type, credential keys, config keys). - -### `openshell provider list` - -List providers in a table. - -| Flag | Default | Description | -|------|---------|-------------| -| `--limit ` | 100 | Max providers | -| `--offset ` | 0 | Pagination offset | -| `--names` | false | Print only names | -| `--output table|yaml|json` | `table` | Output format | - -### `openshell provider update ` - -Update an existing provider without changing its type. - -| Flag | Description | -|------|-------------| -| `--from-existing` | Rediscover local credentials and config | -| `--credential KEY[=VALUE]` | Update a credential (repeatable) | -| `--config KEY=VALUE` | Update config (repeatable) | -| `--credential-expires-at KEY=TIMESTAMP` | Set or clear credential expiry; accepts epoch milliseconds or RFC3339, and `0` clears | - -### `openshell provider delete ...` - -Delete one or more providers by name. - -### Provider profiles - -- `openshell provider list-profiles [--output table|yaml|json]` -- `openshell provider profile export [--output table|yaml|json]` -- `openshell provider profile import (--file |--from )` -- `openshell provider profile update --file ` -- `openshell provider profile lint (--file |--from )` -- `openshell provider profile delete ` - -### Provider credential refresh - -- `openshell provider refresh status [--credential-key ]` -- `openshell provider refresh rotate --credential-key ` -- `openshell provider refresh delete --credential-key ` - -`provider refresh configure ` accepts: - -| Flag | Description | -|------|-------------| -| `--credential-key ` | Injectable credential key (required) | -| `--strategy ` | `oauth2-refresh-token`, `oauth2-client-credentials`, or `google-service-account-jwt` | -| `--material KEY=VALUE` | Non-secret refresh material (repeatable) | -| `--secret-material-env KEY[=ENVVAR]` | Secret refresh material read from the CLI environment (repeatable) | -| `--secret-material-key KEY` | Mark a supplied material key secret (repeatable) | -| `--credential-expires-at TIMESTAMP` | Current credential expiry in epoch milliseconds or RFC3339 | - ---- - -## Inference Commands - -### `openshell inference set` - -Configure the gateway's user-facing `inference.local` route or the platform-only system route. Provider and model are required. - -| Flag | Default | Description | -|------|---------|-------------| -| `--provider ` | -- | Provider record name (required) | -| `--model ` | -- | Model identifier to use for generation requests (required) | -| `--system` | false | Configure the system inference route | -| `--no-verify` | false | Skip endpoint verification before saving | -| `--timeout ` | 0 | Request timeout; `0` uses the 60-second default | - -### `openshell inference update` - -Partially update the selected inference route. - -| Flag | Default | Description | -|------|---------|-------------| -| `--provider ` | unchanged | Provider record name | -| `--model ` | unchanged | Model identifier | -| `--system` | false | Target the system inference route | -| `--no-verify` | false | Skip endpoint verification before saving | -| `--timeout ` | unchanged | Request timeout; `0` uses the 60-second default | - -### `openshell inference get` - -Show both inference routes. `--system` shows only the system route. - ---- - -## Other Commands - -### `openshell term` - -Launch the OpenShell interactive TUI. `--theme auto|dark|light` overrides `OPENSHELL_THEME`. - -### `openshell completions ` - -Generate shell completion scripts. Supported shells: `bash`, `fish`, `zsh`, `powershell`. - -### `openshell ssh-proxy` - -SSH proxy used as a `ProxyCommand`. Not typically invoked directly. diff --git a/.agents/skills/review-github-pr/SKILL.md b/.agents/skills/review-github-pr/SKILL.md index 1058bfb3e9..3903e03271 100644 --- a/.agents/skills/review-github-pr/SKILL.md +++ b/.agents/skills/review-github-pr/SKILL.md @@ -1,6 +1,8 @@ --- name: review-github-pr description: Review a GitHub pull request by summarizing its diff and key design decisions. Use when the user wants to review a PR, understand changes in a branch, or get a code review summary. Trigger keywords - review PR, review pull request, summarize PR, summarize diff, code review, review branch, PR summary, diff summary. +metadata: + internal: true --- # Review GitHub Pull Request @@ -102,7 +104,9 @@ Read through the full diff (and the PR description if available). Produce a summ ### Potential Concerns <- omit if none -- +- **** — Before this PR, + experienced . With this PR, , so + . Details: `:`. ``` **Guidelines for the summary:** @@ -110,7 +114,28 @@ Read through the full diff (and the PR description if available). Produce a summ - **Overview**: State what changed and why. Pull context from the PR description if available. - **Key Design Decisions**: Focus on _why_ something was done a particular way, not _what_ changed. Include `file_path:line_number` references. Examples: choice of algorithm, new abstraction introduced, API contract change, migration strategy. - **Notable Code**: Include only the most instructive or surprising snippets. Keep each snippet under 15 lines. Always include the file path above the code block. -- **Potential Concerns**: Only include if there are genuine risks — missing error handling, breaking changes, performance implications, security issues. Do not fabricate concerns. +- **Potential Concerns**: Only include genuine risks that warrant a change or a + deliberate accept/reject decision. Describe each concern in terms of observable + behavior for the affected persona, such as a sandbox creator, sandbox user, + operator, administrator, SDK consumer, or developer maintaining the system. + Always compare the previous behavior with the new concerning behavior and state + the resulting user-visible impact. Prefer the compact form: "Before this PR, + `` experienced ``. With this PR, ``, so + ``." Add only the minimum file and line references needed to substantiate + the finding. + - Use the PR base as the normal previous-behavior baseline. Review older history + only when the change is fixing or extending an earlier feature and that history + is necessary to explain the behavioral contract. In that case, describe the + relevant transitions explicitly: "Before ``, ... After ``, ... + With this PR, ...". + - Translate internal failure modes and race conditions into what the affected + person would observe. Internal implementation details belong in the trailing + file and line references, not in place of the behavior description. + - Do not assign P0/P1/P2 or similar priority labels. The behavioral comparison + and impact should give maintainers enough context to accept or reject the + suggested change. + - Do not fabricate concerns or claim a behavioral regression without evidence + for both the prior and proposed behavior. - **Agent infrastructure**: When the PR changes behavior, commands, or development workflows, use the `sync-agent-infra` maintenance map to check that related skills were updated. When it adds, removes, or renames skills or crates; changes workflow relationships or skill coverage; modifies issue or PR templates; or changes agent cross-references, apply the full consistency checklist. Report missing companion updates or drift under **Potential Concerns**. ## Step 5: Output @@ -148,9 +173,9 @@ User says: "Review PR #456" ### Review by branch name -User says: "Review branch `feature/add-pagination`" +User says: "Review branch `feat/1234-add-pagination/octocat`" -1. Look up PR with `gh pr list --head "feature/add-pagination"` +1. Look up PR with `gh pr list --head "feat/1234-add-pagination/octocat"` 2. If found, fetch PR metadata and diff 3. If not found, diff against main locally 4. Produce summary diff --git a/.agents/skills/review-security-issue/SKILL.md b/.agents/skills/review-security-issue/SKILL.md index b84e8b597a..cc0a3627dc 100644 --- a/.agents/skills/review-security-issue/SKILL.md +++ b/.agents/skills/review-security-issue/SKILL.md @@ -1,194 +1,19 @@ --- name: review-security-issue -description: Given a GitHub issue, review the issue for security implications. You'll make a determination if the claim in the issue is legitimate and should be addressed or will be a "won't fix." Trigger keywords - security issue, review security ticket, review security issue. +description: Review an authorized security issue for validity, severity, and a remediation plan. +metadata: + internal: true --- # Review Security Issue -Review an issue that outlines a security, vulnerability, or privacy concern. +Review a security concern through its authorized private workflow. Do not file or expand a vulnerability in a public issue; follow `SECURITY.md`. A direct request to review authorizes review only, not remediation. For unattended review, inspect current `state:*` label descriptions, maintainer assignments, and comments to verify that review is authorized. -## Prerequisites +## Assess -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote -- The issue must have `topic:security`. In unattended queue mode it must also have `agent:plan-requested`; a direct user request to review a specific issue does not require that label. +1. Fetch the issue and comments with `gh issue view --json title,body,state,labels,comments`. Follow Label Discovery in `CONTRIBUTING.md` rather than assuming exact label names; resolve unclear meanings before interpreting authorization. Verify that this is an authorized security issue and that a prior review does not already answer the request. +2. Inspect affected code and verify the claim. Assess impact, exploitability, prerequisites, affected surface, and a concrete attack scenario. Separate evidence from assumptions and give a severity with rationale. +3. If actionable, propose a remediation plan with code areas, safe rollout, and focused tests. If not actionable, explain the evidence and recommended disposition. Do not decide product acceptance or silently close the issue. +4. Post the review only when the request authorizes posting. Begin the comment with `> **🔒 security-review-agent**` so later reviews can detect it. Keep sensitive details in the authorized private venue. -## Agent Comment Marker - -All comments posted by this skill **must** begin with the following marker line so that prior reviews can be detected and human comments can be distinguished from agent comments: - -``` -> **🔒 security-review-agent** -``` - -This marker is used in Step 2 to detect prior reviews and in Step 5 to distinguish agent comments from human comments. - -## Step 1: Fetch the Issue - -The user will provide an issue ID (e.g., `#42` or `42`). Strip any leading `#` and fetch the issue contents. - -```bash -gh issue view -``` - -To also retrieve the full issue body as JSON (useful for parsing): - -```bash -gh issue view --json title,body,state,labels,author -``` - -## Step 2: Check if Review is Needed - -First, check the issue's labels from the metadata fetched in Step 1. - -- **If the issue has `agent:implementation-requested`**, the issue has already been reviewed and a human authorized remediation. There is no review to perform. Suggest using `fix-security-issue` and stop. -- **If `topic:security` is missing**, report that this specialized skill only reviews security issues and stop. -- **If this is queue mode and `agent:plan-requested` is missing**, report that the issue is not ready for unattended pickup and stop. -- **If the user directly requested review of this issue**, proceed even when `agent:plan-requested` is absent. Never add or offer to add the human-only request label. - -Next, fetch existing comments on the issue: - -```bash -gh issue view --json comments --jq '.comments[].body' -``` - -Search the comments for the agent marker (`> **🔒 security-review-agent**`). - -- **If the marker is found** and no subsequent human comments exist that ask follow-up questions or challenge the review, you are done. Report to the user that a review already exists. -- **If the marker is found** but there are newer human comments with questions or objections, proceed to Step 5 to address them. -- **If the marker is not found**, proceed to Step 3. - -## Step 3: Analyze the Issue - -Pass the issue title, description, and any relevant code references to the `principal-engineer-reviewer` sub-agent for analysis. Use the Task tool: - -``` -Task tool with subagent_type="principal-engineer-reviewer" -``` - -In the prompt, instruct the reviewer to approach the issue with a security-focused lens, specifically evaluating: - -- **Validity**: Is this a real security, vulnerability, or privacy concern? -- **Severity**: What is the potential impact (data exposure, privilege escalation, denial of service, etc.)? -- **Exploitability**: How easy is it to exploit? Does it require authentication, specific conditions, or access? -- **Attack scenario**: What are the concrete steps an attacker would take to exploit this, from their perspective? -- **Affected surface**: Which components, endpoints, or code paths are affected? -- **Recommendation**: Should this be fixed, mitigated, accepted as risk, or closed as not actionable? - -## Step 4: Post the Review - -Based on the analysis from Step 3, post a comment on the issue. - -### If the concern is legitimate - -Post a comment with a remediation plan: - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🔒 security-review-agent** - -## Security Review - -**Determination:** Legitimate concern - -### Summary -<1-3 sentences describing the security issue and its impact> - -### Severity Assessment -- **Impact:** -- **Exploitability:** -- **Affected components:** - -### Attack Scenario -Step-by-step from the attacker's perspective: -1. -2. -3. - -### Remediation Plan -1. -2. -3. ... - -### Additional Notes - -EOF -)" -``` - -### If the concern is not actionable - -Post a comment with a rationale: - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🔒 security-review-agent** - -## Security Review - -**Determination:** Not actionable - -### Rationale - - -### References - -EOF -)" -``` - -## Step 5: Mark the Security Plan Ready - -After posting a legitimate-concern review with a remediation plan, replace `agent:plan-requested` with `agent:plan-ready` only when the request label was present: - -```bash -gh issue edit --remove-label "agent:plan-requested" --add-label "agent:plan-ready" -``` - -This signals that an unattended agent produced a remediation plan that awaits human review. For an unlabeled direct invocation, leave the `agent:*` labels unchanged. A later direct request can authorize remediation without `agent:implementation-requested`; unattended remediation still requires that label. For a not-actionable determination, remove `agent:plan-requested` if present, do not add another `agent:*` label, and report that a human should close the issue or record the risk decision. - -## Step 6: Address Follow-up Comments - -After posting (or if a prior review exists with new human comments), review all comments that do **not** contain the `> **🔒 security-review-agent**` marker. These are human comments. - -For each unanswered human comment: - -1. Read the question or objection. -2. Formulate a response based on the codebase and the prior security analysis. -3. Post a reply that begins with the agent marker. - -**Important:** The authenticated user posting these comments may be a real person's account. Humans may reply to your comments directly. Always use the agent marker to distinguish your comments from theirs. - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue view ` | View issue details | -| `gh issue view --json title,body,state,labels,author` | Fetch full issue metadata as JSON | -| `gh issue view --json comments --jq '.comments[].body'` | Fetch all comments on an issue | -| `gh issue comment --body "..."` | Post a comment on an issue | -| `gh issue edit --remove-label "agent:plan-requested" --add-label "agent:plan-ready"` | Mark a remediation plan ready for human review | - -## Example Usage - -### Review a security issue - -User says: "Review security issue #42" - -1. Fetch issue #42 via `gh issue view 42` -2. Fetch comments and check for the `security-review-agent` marker -3. No prior review found -- pass issue to `principal-engineer-reviewer` with security lens -4. Reviewer determines it's a legitimate XSS vulnerability in the API response handler -5. Post a comment with severity assessment and remediation plan -6. If `agent:plan-requested` was present, replace it with `agent:plan-ready`; otherwise leave the direct invocation unlabeled -7. Report the finding and posted comment to the user - -### Re-review with new comments - -User says: "Check on security issue #42 again" - -1. Fetch issue #42 and its comments -2. Find existing `security-review-agent` review from a prior run -3. Detect two new human comments asking about scope of the vulnerability -4. Post responses to each, prefixed with the agent marker -5. Report to the user what was addressed +A human decides whether to authorize remediation. Route an authorized fix to `fix-security-issue`. Do not introduce `agent:*` workflow labels. diff --git a/.agents/skills/sbom/SKILL.md b/.agents/skills/sbom/SKILL.md index 5d62dde0ba..0848363c82 100644 --- a/.agents/skills/sbom/SKILL.md +++ b/.agents/skills/sbom/SKILL.md @@ -1,6 +1,8 @@ --- name: sbom description: Generate and manage Software Bill of Materials (SBOMs) for the OpenShell project. Covers SBOM generation with Syft, license resolution via public registries, and CSV export for compliance review. Trigger keywords - SBOM, sbom, bill of materials, license audit, license resolution, generate sbom, sbom csv, dependency license, supply chain, license scan. +metadata: + internal: true --- # SBOM Generation and License Resolution @@ -9,15 +11,52 @@ Generate CycloneDX SBOMs, resolve missing licenses, and export to CSV for compli ## Overview -The OpenShell SBOM tooling produces CycloneDX JSON SBOMs using Syft, resolves missing or hash-based licenses by querying public registries (crates.io, npm, PyPI), and exports the results to CSV for stakeholder review. +The OpenShell SBOM tooling produces source-tree CycloneDX JSON SBOMs using Syft, resolves missing or hash-based licenses by querying public registries (crates.io, npm, PyPI), and exports the results to CSV for stakeholder review. SBOMs are **release artifacts only** -- they are generated on demand and not committed to the repository. Output lands in `deploy/sbom/output/` (gitignored). +Pushed gateway, sandbox, and supervisor images carry an SPDX SBOM and minimal SLSA provenance as OCI attestations. Branch E2E, Release Dev, and Release Tag image binaries embed cargo-auditable metadata, so their image SBOMs include linked Rust crates. + ## Prerequisites - `mise install` has been run (installs Syft and other tools) - The repository is checked out at the root +## Inspecting an Image SBOM + +BuildKit uses its default Syft scanner and attaches one SPDX document per platform. Read one without pulling the image: + +```bash +docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest \ + --format '{{ json (index .SBOM "linux/amd64").SPDX }}' +``` + +Validate the final attestation, requiring a Cargo package for an auditable image: + +```bash +tasks/scripts/verify-image-sbom.sh ghcr.io/nvidia/openshell/gateway:latest --require-cargo +``` + +## Inspecting an Auditable Image Binary + +Opt into auditable metadata when staging a local image binary: + +```bash +OPENSHELL_AUDITABLE=1 PREBUILT_ARCH=amd64 \ + tasks/scripts/stage-prebuilt-binaries.sh gateway +``` + +Scan the staged binary rather than the source tree: + +```bash +mise x -- syft \ + "file:deploy/docker/.build/prebuilt-binaries/amd64/openshell-gateway" \ + -o cyclonedx-json +``` + +This output is limited to packages Syft discovers from that binary. Use +`mise run sbom` for the broader source-tree license-compliance inventory. + ## Workflow 1: Full SBOM Generation (One Command) ```bash diff --git a/.agents/skills/sync-agent-infra/SKILL.md b/.agents/skills/sync-agent-infra/SKILL.md index bd01bfebde..8a07921e62 100644 --- a/.agents/skills/sync-agent-infra/SKILL.md +++ b/.agents/skills/sync-agent-infra/SKILL.md @@ -1,192 +1,48 @@ --- name: sync-agent-infra -description: Detect and fix drift across agent-first infrastructure files. Ensures skill inventories, workflow chains, architecture tables, issue/PR templates, and cross-references stay consistent when skills, crates, or workflows change. Run after adding, removing, or renaming skills or components. Trigger keywords - sync agent infra, sync skills, update agent docs, check agent consistency, agent infra drift, sync contributing, sync agents. +description: Reconcile contributor skills, AGENTS.md, CONTRIBUTING.md, issue and PR templates, and workflow references after repository workflow changes. +metadata: + internal: true --- # Sync Agent Infrastructure -Detect and fix drift across the agent-first infrastructure files. These files reference each other and must stay consistent: +Keep contributor guidance consistent without copying procedural workflows into `AGENTS.md`. That file holds repository coding conventions and pointers; the relevant skills hold issue, PR, and maintenance procedures. `CONTRIBUTING.md` explains the human workflow. -| File | What it tracks | -|------|---------------| -| `AGENTS.md` | Project identity, workflow chains, architecture overview, issue/PR conventions, skill maintenance pointer | -| `CONTRIBUTING.md` | Skills table, workflow chains, "When to Open an Issue" guidance, skill references | -| `docs/resources/issue-lifecycle.mdx` | Human-facing issue states, roadmap decisions, and direct-versus-queued agent ownership | -| `README.md` | "Built With Agents" section, "Explore with your agent" skill references | -| `.github/ISSUE_TEMPLATE/bug_report.yml` | Skill name references in diagnostic guidance | -| `.github/ISSUE_TEMPLATE/feature_request.yml` | Skill name references in investigation guidance | -| `.github/ISSUE_TEMPLATE/config.yml` | Contact link text referencing skills | -| `.github/workflows/issue-triage.yml` | Comment text referencing skills | -| `.agents/skills/triage-issue/SKILL.md` | Skill name references in gate check and diagnosis steps | -| `.agents/skills/openshell-cli/SKILL.md` | Companion skills table | -| `.agents/skills/create-github-pr/SKILL.md` | Pre-PR agent infrastructure check | -| `.agents/skills/review-github-pr/SKILL.md` | Review-time agent infrastructure check | -| `.agents/skills/build-from-issue/SKILL.md` | Label awareness and pre-commit agent infrastructure check | -| `.claude/agents/principal-engineer-reviewer.md` | Shared review-time agent infrastructure check | +## When to run -## When to Run +Run after adding, removing, or renaming a skill or crate; changing issue or PR conventions; changing development workflows; or modifying templates or agent cross references. Run before opening a PR that touches these areas. -- After adding, removing, or renaming a skill in `.agents/skills/` -- After adding, removing, or renaming a crate in `crates/` -- After changing workflow chain relationships between skills -- After changing which product or development areas a skill covers -- After modifying issue or PR templates -- Before opening a PR that touches any of the above +## Maintenance map -## Skill Maintenance Map - -Use this map when product behavior, commands, or development workflows change. It is a routing aid, not an exhaustive dependency list. Search `.agents/skills/` for the changed command, field, component, or workflow before concluding that no other skill needs an update. - -| Change area | Skills to review | +| Change | Skills to inspect | |---|---| +| Issues, triage, labels, or feature proposals | `create-github-issue`, `triage-issue`, `create-spike`, `build-from-issue` | +| PR template, vouch behavior, or review conventions | `create-github-pr`, `review-github-pr`, `build-from-issue` | +| Security assessment or remediation | `review-security-issue`, `fix-security-issue` | +| Published docs workflow | `update-docs-from-commits` | | CLI commands, flags, defaults, or workflows | `openshell-cli` | -| Sandbox policy schema, presets, or enforcement behavior | `generate-sandbox-policy`, `openshell-cli` | -| Supervisor middleware policy, registrations, runtime, or failure behavior | `generate-sandbox-policy`, `openshell-cli`, `debug-openshell-cluster` | -| Gateway deployment, Helm, runtime drivers, or health checks | `debug-openshell-cluster`, `helm-dev-environment` | -| Inference routing, providers, or `inference.local` behavior | `debug-inference`, `openshell-cli` | -| TUI architecture, navigation, data fetching, or UX | `tui-development` | -| Release artifacts or post-publish smoke coverage | `test-release-canary` | -| GitHub Actions workflows, required checks, or CI diagnostics | `watch-github-actions`; also `test-release-canary` for release smoke coverage | -| Gator harness, sandbox image, supervision, or model overrides | `launch-openshell-gator` | -| SBOM generation, dependency metadata, or license workflows | `sbom` | -| Issue templates, labels, contribution gates, or spike/build workflow | `triage-issue`, `create-spike`, `build-from-issue`, `create-github-issue` | -| PR template, review conventions, or vouch behavior | `create-github-pr`, `review-github-pr`, `build-from-issue` | -| Security review or remediation workflow | `review-security-issue`, `fix-security-issue` | -| RFC template, numbering, or lifecycle | `create-rfc` | -| Documentation structure, navigation, or doc-update workflow | `update-docs` | -| Skills, crates, workflow chains, issue/PR templates, or agent cross-references | `sync-agent-infra` | - -## Prerequisites - -You must be in the OpenShell repository root. - -## Step 1: Inventory Current State - -Gather the source of truth for each category. - -### Skills - -List all skill directories: - -```bash -ls -1 .agents/skills/ -``` - -This is the canonical skill list. Every other file must agree with it. - -### Crates - -List all crate directories: - -```bash -ls -1 crates/ -``` - -### Workflow Chains - -The canonical workflow chains are defined in `AGENTS.md` under "## Workflow Chains". Read that section — it is the source of truth for skill pipelines. - -### Labels - -The canonical label set is used by skills and templates. The key labels are: `state:triage-needed`, `state:needs-info`, `state:validated`, `state:accepted`, `agent:plan-requested`, `agent:plan-ready`, `agent:implementation-requested`, `agent:in-progress`, `agent:pr-opened`, `roadmap`, `topic:security`, `good first issue`, `help wanted`, `spike`, and the relevant `area:*`, `topic:*`, `integration:*`, and `test:*` labels. The `agent:*` request labels control unattended queue pickup; they are not prerequisites when a user directly asks an agent to work on a specific issue. - -## Step 2: Check Each File for Drift - -For each file in the table above, check for the following inconsistencies: - -### `CONTRIBUTING.md` - -1. **Skills table** — Every skill in `.agents/skills/` must appear in the "Agent Skills for Contributors" table. No skill in the table should reference a directory that doesn't exist. -2. **Workflow chains** — Must match `AGENTS.md` workflow chains exactly. -3. **Skill references in prose** — Any skill mentioned by name in "Before You Open an Issue", "When to Open an Issue", or "When NOT to Open an Issue" must exist in `.agents/skills/`. - -### `AGENTS.md` - -1. **Architecture overview** — Every crate in `crates/` must appear in the architecture table. The `python/`, `proto/`, `deploy/`, `.agents/` rows must also be present. -2. **Workflow chains** — Verify each skill named in a chain exists in `.agents/skills/`. -3. **Issue/PR conventions** — Verify referenced skills (`create-github-issue`, `create-github-pr`, `build-from-issue`) exist. -4. **Skill maintenance pointer** — Verify it still points to `sync-agent-infra` and does not duplicate the maintenance map from this skill. - -### Issue Lifecycle Documentation - -1. **`docs/resources/issue-lifecycle.mdx`** — State, roadmap, and agent-workflow meanings must match `AGENTS.md` and `CONTRIBUTING.md`. -2. **Invocation modes** — The `agent:*` request labels must control unattended queue pickup without being presented as prerequisites for a direct user request to a specific agent. - -### `README.md` - -1. **"Explore with your agent"** — Skill names referenced must exist in `.agents/skills/`. -2. **"Built With Agents"** — Skill names referenced must exist. Workflow descriptions should be consistent with `AGENTS.md` chains. - -### Issue Templates - -1. **`bug_report.yml`** — Must collect a User Story, Problem Statement, Impact / Why This Matters, Acceptance Criteria, Reproduction Steps, and Environment. Logs are optional and bug-specific; reporter diagnostics must not be required. -2. **`feature_request.yml`** — Must collect a User Story, Problem Statement, Impact / Why This Matters, Proposed Design, Acceptance Criteria, and Alternatives Considered. The design describes workflow and observable behavior without prescribing internal implementation; agent investigation is optional. -3. **`config.yml`** — Skill category descriptions in contact links should be accurate. - -### Issue Triage Workflow - -1. **`issue-triage.yml`** — Skill names in the redirect comment must exist. - -### Skill Cross-References - -1. **`triage-issue`** — Skills referenced in gate check and diagnosis steps must exist. -2. **`openshell-cli`** — Companion skills table entries must exist. -3. **`build-from-issue`** — Label names must match the project's label taxonomy, and request labels must gate unattended queue pickup without blocking direct user requests. -4. **`create-spike`** — Reference to `build-from-issue` as next step must be accurate. -5. **`review-security-issue`** / **`fix-security-issue`** — Cross-references between the two must be accurate. -6. **PR creation and review checks** — The `create-github-pr`, `review-github-pr`, `build-from-issue`, and `principal-engineer-reviewer` references to `sync-agent-infra` must exist and use trigger conditions aligned with this skill. - -## Step 3: Report Drift - -If any inconsistencies are found, report them in a structured format: - -```markdown -## Agent Infrastructure Drift Report - -### Skills Inventory -- ADDED (exists in .agents/skills/ but missing from CONTRIBUTING.md): -- REMOVED (in CONTRIBUTING.md but missing from .agents/skills/): -- OK: skills consistent - -### Architecture Table -- ADDED (exists in crates/ but missing from AGENTS.md): -- REMOVED (in AGENTS.md but missing from crates/): -- OK: components consistent - -### Workflow Chains -- STALE: references non-existent skill -- OK: chains consistent - -### Cross-References -- : references non-existent skill -- : references non-existent label