Feat: AIAC Phase 1 runnable UC-1 onboarding demo walkthrough - #725
Feat: AIAC Phase 1 runnable UC-1 onboarding demo walkthrough#725oblinder wants to merge 14 commits into
Conversation
Split demo/ into demo/assets/ (reusable github_agent + github_tool
workloads) and demo/use-cases/ (populated by later handoffs), per
handoff 01. Consolidates the previously split/conflicting deployment
docs into a single demo/assets/INSTALL.md and adds an idempotent
demo/assets/install.sh.
- git mv demo/{agents,tools} -> demo/assets/{agents,tools}
- Fix github_tool/test/conftest.py parents[4] -> parents[5] for the
new nesting depth (scenario import from test/integration)
- Recreate github_agent's uv venv and verify both workloads' own test
suites pass from their new locations
- Update path references in docs/specs/demo/*.md (docs/issues and
docs/gh-issues are gitignored; updated on disk but not tracked here)
- Point github_agent/README.md's "Deploying to Kagenti" section at
the new consolidated INSTALL.md instead of duplicating steps
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
The AuthBridge sidecar reuses a workload's declared PORT as its own reverse-proxy listener and shifts the app's real listen port to PORT+1. AuthBridge also has a fixed health-check listener hardcoded to 9091. github-tool declared PORT=9090, shifting the app to 9091 and colliding with that fixed listener — the container crash-looped fighting the sidecar for the port. Found by actually running install.sh against the live cluster (not caught by static review): the tool rolled out but crash-looped at 1/2 ready. - Change github-tool's PORT/containerPort/probes/targetPort from 9090 to 9095 (shifted: 9096), clear of every AuthBridge-fixed port (8080, 8081, 9091, 9093, 9094). The Service's external port stays 9090. - Document the port-shift invariant in docs/specs/demo/github-tool.md §7 and demo/assets/INSTALL.md. Verified live: rollout succeeded, pod 2/2 Running with 0 restarts, and a port-forwarded MCP POST to /mcp reaches AuthBridge's JWT-validation layer as expected. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Adds demo/use-cases/uc1-onboarding/: a strictly-live, external-facing demo proving AIAC discovers an agent and a tool, reads a two-line plain-English policy, and generates enforceable least-privilege authorization via RFC 8693 token exchange. Makefile + numbered Python scripts organized into init/ (prereqs, clear, setup), onboard/ (agent, tool), run/ (per-user drivers), and lib/ (shared scenario/helpers). Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Anatoly Koyfman <anatoly@il.ibm.com>
…config - CLAUDE.md: issue-tracking section describes the GitHub issues/AIAC Project layout (no migration history); adds an '## Agent skills' block wiring the Matt Pocock engineering skills. - .gitignore: drop obsolete docs/issues/ and docs/gh-issues/ entries. - docs/agents/: issue-tracker, triage-labels, and domain config the skills read from. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Relocates demo/use-cases/uc1-onboarding/init/setup_keycloak.py to lib/, alongside the other shared demo helpers, and updates the lib/scenario.py comment that referenced its old path. Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
|
Warning Review limit reached
Next review available in: 45 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe change adds GitHub agent and MCP tool demo assets, a complete UC-1 authorization workflow with Keycloak integration, Kubernetes deployment infrastructure, installation helpers, OPA Kind scripts, and updated documentation. ChangesDocumentation and repository guidance
GitHub agent configuration and runtime
GitHub agent deployment and testing
GitHub tool service
Demo installation infrastructure
UC-1 authorization workflow
Kind OPA integration
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant GithubAgent as GitHub Agent
participant GithubAgents as CrewAI Crews
participant MCPTools as MCP Tools
User->>GithubAgent: submit GitHub query
GithubAgent->>GithubAgents: extract query information
GithubAgents->>MCPTools: invoke selected tools
MCPTools-->>GithubAgents: return results
GithubAgents-->>GithubAgent: return report
GithubAgent-->>User: return response
sequenceDiagram
participant Runner as UC-1 Runner
participant Keycloak
participant Controller
participant OPA
Runner->>Keycloak: authenticate user
Runner->>Controller: onboard agent or tool
Controller-->>Runner: return generated Rego
Runner->>OPA: evaluate inbound policy
OPA-->>Runner: return decision
alt Inbound Allowed
Runner->>Keycloak: exchange token
Keycloak-->>Runner: return token
Runner->>OPA: evaluate outbound intent
OPA-->>Runner: return decision
else Inbound Denied
Runner->>Runner: stop
end
Possibly related issues
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Correctness / agent behavior:
- prompts.py: stop mapping the read-only "sub-issues under #N" example to
sub_issue_write; use issue_read only.
- prompts.py: instruct null (JSON) rather than the string "None" for absent
extraction fields, so json.loads on the output succeeds.
- main.py: guard the crew task output before .raw so a task that produced no
output returns a message instead of raising AttributeError.
Robustness (demo scripts/lib):
- setup_keycloak.py: send the full client representation on the token-exchange
update_client PUT ({**client, ...}); a bare {"attributes": ...} clobbers the
client's other fields.
- _lib.py: add a 30s timeout to the opa eval subprocess so a stalled opa aborts
with a clear message instead of hanging the demo.
- show-state.py: abort when the prior diff snapshot is missing instead of
silently diffing against empty sets.
- 00-prereqs.py: fix the step denominator ("of 8" -> "of 4").
- scenario.py: type Intent.function_name as str | None (the devops-user
inbound-denial intent carries None).
Deployment / install:
- install.sh, INSTALL.md: build/load the demo images under the localhost/
prefix to match the Deployment manifests' image refs (localhost/github-*:latest,
IfNotPresent) and avoid ImagePullBackOff on the docker path.
- demo.md: drop the invalid 'make show --diff' form (make treats --diff as an
option); keep only 'make diff PRIOR=...'.
opa-kind-enable.sh:
- derive the OPA bundle_url from $RELEASE_NAMESPACE instead of hardcoding
rossoctl-system, matching where step 1 deploys bundle-service.
- add --wait --timeout 5m to helm upgrade so the release settles before the
agent pods are restarted.
- track temp files and clean them on EXIT (podman tar no longer leaks on a
failed kind load); use portable trailing-X mktemp templates.
a2a_agent.py: run MCPServerAdapter __enter__/__exit__ on a single dedicated
worker thread so the adapter's session lifecycle stays on one thread, rather
than asyncio.to_thread's shared pool which could split them across workers.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (5)
scripts/opa-kind-enable.sh (2)
54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueValidate the resolved container runtime.
If the user sets
CONTAINER_RUNTIMEto a runtime that is not installed, the script fails later at thebuildstep with a shell "command not found" error. A single check after detection gives a clear message.♻️ Proposed check
else CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" fi + +if ! command -v "$CONTAINER_RUNTIME" &> /dev/null; then + echo "ERROR: container runtime '${CONTAINER_RUNTIME}' not found in PATH" >&2 + exit 1 +fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/opa-kind-enable.sh` around lines 54 - 60, After the container-runtime detection conditional, validate the resolved CONTAINER_RUNTIME with command -v before any build or runtime commands execute. If it is unavailable, emit a clear error identifying the missing runtime and exit nonzero; preserve the existing provider and default-selection behavior.
142-143: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBoth scripts delete agent pods without waiting for the replacements. Each script runs
kubectl delete pods -l rossoctl.io/type=agentand then prints its completion message. The replacement pods are still starting, so the printed verification command can read stale state, and a caller that chains the next demo step can hit AuthBridge sidecars that do not yet serve the intended pipeline. The shared root cause is one missing readiness wait after the label-based delete.
scripts/opa-kind-enable.sh#L142-L143: addkubectl wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5mafter the delete.scripts/opa-kind-restore.sh#L50-L51: add the samekubectl waitcall after the delete.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/opa-kind-enable.sh` around lines 142 - 143, The agent pod restart flows do not wait for replacement pods to become Ready. In scripts/opa-kind-enable.sh lines 142-143, add kubectl wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5m immediately after the pod deletion; apply the same change in scripts/opa-kind-restore.sh lines 50-51 after its delete command.aiac/demo/use-cases/uc1-onboarding/Makefile (1)
48-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
make diffwithoutPRIORpasses a bare--diffflag.If a user runs
make diffwith noPRIOR, the recipe expands to--diffwith no value. Add a default so the target is self-describing.♻️ Proposed default
+PRIOR ?= 01-after-agent + diff: ## Show the latest snapshot, diffed against generated/<PRIOR> (e.g. make diff PRIOR=01-after-agent) $(PYTHON) show-state.py --diff $(PRIOR)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/use-cases/uc1-onboarding/Makefile` around lines 48 - 49, Update the diff target’s show-state.py invocation to supply a meaningful default value when PRIOR is unset, so running make diff without arguments remains self-describing while preserving the explicit PRIOR behavior.aiac/demo/use-cases/uc1-onboarding/lib/_lib.py (1)
73-80: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
table()raises IndexError whenrowsis empty andheadersis None.Line 75 indexes
all_rows[0]. Every current caller passesheaders, so this is latent only. Add an early return to keep the helper safe.♻️ Proposed guard
def table(rows: list[tuple[str, ...]], headers: tuple[str, ...] | None = None) -> None: all_rows = ([headers] if headers else []) + rows + if not all_rows: + return widths = [max(len(str(r[i])) for r in all_rows) for i in range(len(all_rows[0]))]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py` around lines 73 - 80, Update table() to return immediately when both rows and headers are absent, before accessing all_rows[0]. Preserve the existing formatting behavior when headers or data rows are available.aiac/demo/use-cases/uc1-onboarding/demo.md (1)
12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a language to the two fenced code blocks.
markdownlint reports MD040 for the policy block at line 12 and the architecture diagram at line 106. Use
textfor both.Also applies to: 106-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/use-cases/uc1-onboarding/demo.md` at line 12, Update the two fenced code blocks in the demo documentation, including the policy block and architecture diagram, to specify the text language after each opening fence. Use text for both blocks to satisfy markdownlint MD040.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@aiac/CLAUDE.md`:
- Around line 25-27: Update the issue-label description in CLAUDE.md to use the
canonical aiac-status:<value> prefix instead of status:<value>, matching the
conventions documented in issue-tracker.md and triage-labels.md while preserving
the existing status-field description.
In `@aiac/demo/assets/agents/github_agent/.dockerignore`:
- Around line 3-4: Update the Docker ignore rules near the existing .env entry
to exclude all dotenv files using the .env* pattern, and re-include only a
non-secret template file if the build requires it.
In `@aiac/demo/assets/agents/github_agent/a2a_agent.py`:
- Around line 206-207: Update the MCP setup lifecycle around adapter.__enter__
and its ThreadPoolExecutor so coroutine cancellation cannot leave setup I/O
running without cleanup. Add cooperative cancellation for the blocked worker, or
move both __enter__ and __exit__ into an explicitly managed process/thread
execution context that guarantees cleanup before cancellation completes.
In `@aiac/demo/assets/agents/github_agent/Dockerfile`:
- Around line 12-13: Update the Dockerfile runtime setup to create a non-root
user with UID 10001, change the ownership target from 1001 to 10001, and run the
image as USER 10001. Keep AuthBridge-specific Kubernetes UID overrides separate
from this image default.
In `@aiac/demo/assets/agents/github_agent/README.md`:
- Around line 23-24: Use a repository-root path for the UC-1 stub in both
aiac/demo/assets/agents/github_agent/README.md lines 23-24 and
aiac/docs/specs/demo/github-agent.md line 221, changing the reference to
aiac/demo/assets/tools/github_tool/; keep both documents consistent.
In `@aiac/demo/assets/INSTALL.md`:
- Around line 48-55: Update the deployment guidance in
aiac/demo/assets/agents/github_agent/README.md:73-74 to clearly label the linked
INSTALL.md as discovery-only and not sufficient for agent execution, while
documenting the required github-tool-mcp prerequisite for execution. Leave
aiac/demo/assets/INSTALL.md:48-55 unchanged, since it correctly remains a
discovery-only install path.
In `@aiac/demo/assets/install.sh`:
- Around line 42-51: Update build_and_load() to use kind load image-archive when
detect_runtime() selects podman, saving the built image with $RUNTIME save and
passing that archive to kind; retain kind load docker-image for docker. Ensure
the Podman path follows the supported image-archive import flow.
In `@aiac/demo/assets/tools/github_tool/requirements.txt`:
- Around line 1-5: Update the dependency entries in requirements.txt to use
exact audited versions rather than lower and upper bounds, including pinning mcp
to the audited 1.9.4 release and explicitly pinning its vulnerable transitive
python-multipart dependency to a safe audited version. Pin uvicorn, httpx,
pytest, and pytest-asyncio as well, using the project’s approved versions.
In `@aiac/demo/assets/tools/github_tool/test/test_server.py`:
- Around line 26-28: Update test_returns_exactly_four_tool_names to assert that
the tools response length equals the expected number of TOOL_SCOPES entries
before comparing the name set, ensuring duplicate or additional entries cannot
pass the test.
In `@aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py`:
- Around line 120-122: Update the installation-complete check in 00-prereqs.py
at lines 120-122 to include aiac-policy-store and wait for both Deployments and
the StatefulSet to become ready before returning. Also update the install.sh
skip path at lines 151-153 to verify rollout readiness for both demo Deployments
before skipping installation.
In `@aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py`:
- Around line 54-75: Invoke ensure_default_audience_scope after the tool
onboarding flow creates the tool audience scope, using the agent client UUID and
tool audience scope name. Update the documented setup/onboard sequence or add
the call to the tool onboarding entry point, such as 04-onboard-tool.py, so
running make demo assigns the scope as a default on the agent client.
In `@aiac/demo/use-cases/uc1-onboarding/show-state.py`:
- Around line 69-79: Update the snapshot handling in show-state’s file
enumeration and grant_sets flow so incomplete snapshots do not reach grant_sets,
which expects both Rego files. Require both cfg.inbound_rego and
cfg.outbound_rego before evaluating grants; otherwise retain the available-file
output and skip grant evaluation without failing.
- Around line 87-103: Update show_diff to first render unified, line-oriented
diffs for the inbound and outbound Rego files between prior_dir and current_dir,
handling a missing current snapshot file consistently with the existing flow.
Keep the existing grant_sets comparison and added/removed summary after the
textual diffs so both policy changes and order-independent grant differences are
shown.
In `@aiac/docs/agents/domain.md`:
- Around line 21-31: Update the fenced tree block in domain.md to specify the
text language identifier, changing the opening fence to use text while
preserving the existing non-executable directory tree content.
In `@scripts/opa-kind-restore.sh`:
- Around line 46-48: Update the helm upgrade invocation in the restore flow of
opa-kind-restore.sh to include --wait with the existing 5-minute timeout,
ensuring the reverted pipeline configuration is reconciled before subsequent pod
deletion. Leave the existing values file and documented omission of --set flags
unchanged.
---
Nitpick comments:
In `@aiac/demo/use-cases/uc1-onboarding/demo.md`:
- Line 12: Update the two fenced code blocks in the demo documentation,
including the policy block and architecture diagram, to specify the text
language after each opening fence. Use text for both blocks to satisfy
markdownlint MD040.
In `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py`:
- Around line 73-80: Update table() to return immediately when both rows and
headers are absent, before accessing all_rows[0]. Preserve the existing
formatting behavior when headers or data rows are available.
In `@aiac/demo/use-cases/uc1-onboarding/Makefile`:
- Around line 48-49: Update the diff target’s show-state.py invocation to supply
a meaningful default value when PRIOR is unset, so running make diff without
arguments remains self-describing while preserving the explicit PRIOR behavior.
In `@scripts/opa-kind-enable.sh`:
- Around line 54-60: After the container-runtime detection conditional, validate
the resolved CONTAINER_RUNTIME with command -v before any build or runtime
commands execute. If it is unavailable, emit a clear error identifying the
missing runtime and exit nonzero; preserve the existing provider and
default-selection behavior.
- Around line 142-143: The agent pod restart flows do not wait for replacement
pods to become Ready. In scripts/opa-kind-enable.sh lines 142-143, add kubectl
wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent
--timeout=5m immediately after the pod deletion; apply the same change in
scripts/opa-kind-restore.sh lines 50-51 after its delete command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7d01bc82-495a-41f2-a4bc-99ee2d9845db
⛔ Files ignored due to path filters (1)
aiac/demo/assets/agents/github_agent/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (55)
aiac/.gitignoreaiac/CLAUDE.mdaiac/demo/assets/INSTALL.mdaiac/demo/assets/agents/github_agent/.dockerignoreaiac/demo/assets/agents/github_agent/.env.templateaiac/demo/assets/agents/github_agent/Dockerfileaiac/demo/assets/agents/github_agent/README.mdaiac/demo/assets/agents/github_agent/a2a_agent.pyaiac/demo/assets/agents/github_agent/github_agent/__init__.pyaiac/demo/assets/agents/github_agent/github_agent/agents.pyaiac/demo/assets/agents/github_agent/github_agent/config.pyaiac/demo/assets/agents/github_agent/github_agent/data_types.pyaiac/demo/assets/agents/github_agent/github_agent/event.pyaiac/demo/assets/agents/github_agent/github_agent/llm.pyaiac/demo/assets/agents/github_agent/github_agent/main.pyaiac/demo/assets/agents/github_agent/github_agent/prompts.pyaiac/demo/assets/agents/github_agent/github_agent/tools.pyaiac/demo/assets/agents/github_agent/k8s/configmaps.yamlaiac/demo/assets/agents/github_agent/k8s/github-agent-deployment.yamlaiac/demo/assets/agents/github_agent/pyproject.tomlaiac/demo/assets/agents/github_agent/test/test_agent_card.pyaiac/demo/assets/agents/github_agent/test/test_prereq.pyaiac/demo/assets/agents/github_agent/test/test_tools.pyaiac/demo/assets/agents/github_agent/test_startup.expaiac/demo/assets/install.shaiac/demo/assets/tools/github_tool/Dockerfileaiac/demo/assets/tools/github_tool/k8s/github-tool-deployment.yamlaiac/demo/assets/tools/github_tool/pytest.iniaiac/demo/assets/tools/github_tool/requirements.txtaiac/demo/assets/tools/github_tool/server.pyaiac/demo/assets/tools/github_tool/test/__init__.pyaiac/demo/assets/tools/github_tool/test/conftest.pyaiac/demo/assets/tools/github_tool/test/test_server.pyaiac/demo/use-cases/.gitkeepaiac/demo/use-cases/uc1-onboarding/Makefileaiac/demo/use-cases/uc1-onboarding/demo.mdaiac/demo/use-cases/uc1-onboarding/init/00-prereqs.pyaiac/demo/use-cases/uc1-onboarding/init/01-clear.pyaiac/demo/use-cases/uc1-onboarding/init/02-setup.pyaiac/demo/use-cases/uc1-onboarding/lib/_lib.pyaiac/demo/use-cases/uc1-onboarding/lib/scenario.pyaiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.pyaiac/demo/use-cases/uc1-onboarding/onboard/03-onboard-agent.pyaiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.pyaiac/demo/use-cases/uc1-onboarding/run/run-developer.pyaiac/demo/use-cases/uc1-onboarding/run/run-devops.pyaiac/demo/use-cases/uc1-onboarding/run/run-tester.pyaiac/demo/use-cases/uc1-onboarding/show-state.pyaiac/docs/agents/domain.mdaiac/docs/agents/issue-tracker.mdaiac/docs/agents/triage-labels.mdaiac/docs/specs/demo/github-agent.mdaiac/docs/specs/demo/github-tool.mdscripts/opa-kind-enable.shscripts/opa-kind-restore.sh
| **Task**-typed leaf issues. Every issue carries the `aiac` label plus cumulative | ||
| `area:<path>` labels; open issues also carry a `status:<value>` label, and the | ||
| Project's **AIAC Status** field records the issue's status. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use the canonical aiac-status:<value> label.
This paragraph says open issues carry status:<value>, but aiac/docs/agents/issue-tracker.md and aiac/docs/agents/triage-labels.md use aiac-status:<value>. Keep one prefix. Otherwise issue filters and triage commands can select different issues.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@aiac/CLAUDE.md` around lines 25 - 27, Update the issue-label description in
CLAUDE.md to use the canonical aiac-status:<value> prefix instead of
status:<value>, matching the conventions documented in issue-tracker.md and
triage-labels.md while preserving the existing status-field description.
| with concurrent.futures.ThreadPoolExecutor(max_workers=1) as mcp_exec: | ||
| mcp_tools = await loop.run_in_executor(mcp_exec, adapter.__enter__) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
cd aiac
.venv/bin/python - <<'PY'
import asyncio
import time
from concurrent.futures import ThreadPoolExecutor
async def blocked_scope():
loop = asyncio.get_running_loop()
with ThreadPoolExecutor(max_workers=1) as executor:
await loop.run_in_executor(executor, time.sleep, 2)
async def main():
task = asyncio.create_task(blocked_scope())
await asyncio.sleep(0.1)
started = time.monotonic()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
elapsed = time.monotonic() - started
print(f"Cancellation completion time: {elapsed:.2f}s")
assert elapsed >= 1.5, "Executor shutdown did not wait for the running worker"
asyncio.run(main())
PYRepository: rossoctl/cortex
Length of output: 215
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "Repository files around target:"
git ls-files | rg '(^|/)a2a_agent\.py$|(^|/)README\.md$|(^|/)pyproject\.toml$|(^|/)requirements|(^|/)poetry.lock$' || true
if [ -f aiac/demo/assets/agents/github_agent/a2a_agent.py ]; then
echo
echo "Target outline:"
ast-grep outline aiac/demo/assets/agents/github_agent/a2a_agent.py --view condensed || true
echo
echo "Target lines 180-230:"
sed -n '180,230p' aiac/demo/assets/agents/github_agent/a2a_agent.py | cat -n
fi
echo
echo "Python availability:"
for p in python python3 .venv/bin/python /usr/bin/python3; do
if command -v "$p" >/dev/null 2>&1; then
"$p" -V
fi
doneRepository: rossoctl/cortex
Length of output: 4777
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import asyncio
import concurrent.futures
import inspect
import sys
print("Python", sys.version)
print("ThreadPoolExecutor shutdown signature:")
print(inspect.signature(concurrent.futures.ThreadPoolExecutor.shutdown))
print("ThreadPoolExecutor exit signature:")
try:
method = concurrent.futures.ThreadPoolExecutor.__exit__
except Exception as e:
print("Error:", type(e).__name__, e)
method = None
if method:
print(inspect.signature(method))
# Programmatic call-site check based on source strings:
# avoid importing/converting potentially repository code.
src = open("aiac/demo/assets/agents/github_agent/a2a_agent.py", encoding="utf-8").read()
checks = {
"uses same executor for __enter__ and __exit__": "mcp_exec" in src[src.index("adapter = MCPServerAdapter"):] and
"adapter.__enter__" in src[src.index("adapter = MCPServerAdapter"):] and
"adapter.__exit__" in src[src.index("adapter = MCPServerAdapter"):] and
"ThreadPoolExecutor(max_workers=1)" in src[src.index("adapter = MCPServerAdapter"):],
"executor is context-managed inside coroutine": "with concurrent.futures.ThreadPoolExecutor(max_workers=1) as mcp_exec:" in src,
}
for name, ok in checks.items():
print(f"{name}: {ok}")
PYRepository: rossoctl/cortex
Length of output: 493
🌐 Web query:
Python asyncio loop.run_in_executor cancellation run_in_executor shutdown ThreadPoolExecutor wait True
💡 Result:
In Python, calling.cancel on the asyncio.Future returned by loop.run_in_executor does not stop or terminate the underlying task executing in the ThreadPoolExecutor [1][2][3]. The cancellation is superficial: it only notifies the asyncio event loop that you are no longer interested in the result [3]. The task in the worker thread will continue to execute to completion [1][2][4]. Python threads cannot be safely interrupted or killed once they have started running [4][3]. To effectively stop a long-running blocking operation in a ThreadPoolExecutor, you must design your code to cooperate with a signaling mechanism [1][4][3]. Common patterns include: 1. Using a threading.Event: Pass a threading.Event object to your blocking function and periodically check it (e.g., if event.is_set: return). When you want to cancel, set the event [1][3]. 2. Cooperative cancellation: For longer tasks, structure them as a series of smaller, interruptible steps that check for a stop signal frequently [4]. Regarding ThreadPoolExecutor shutdown: - When you use a context manager (with ThreadPoolExecutor as executor:), the executor implicitly calls.shutdown(wait=True) upon exiting the block [5][6]. This causes the main thread to wait until all pending and currently running tasks in that executor are completed before continuing [4][5]. - If you cancel an asyncio task that is waiting on a result from an executor, the asyncio part of the task handles the cancellation, but the executor's worker thread is unaware and will keep running [2][4]. - To shut down an executor while tasks are still running, you may need to call executor.shutdown(wait=True) (or wait=False if you do not want to block) manually [5][6]. Note that shutdown with wait=True will block the calling thread until the executor finishes its tasks [5][6]. Shutdown with wait=False will allow the program to continue, but ensure resources are cleaned up later [5]. - Loop.shutdown_default_executor can be used to manage the cleanup of the default executor explicitly [7][8]. In summary, because standard Python threads cannot be forcefully terminated, cancellation must be implemented cooperatively within the function being executed [1][4].
Citations:
- 1: https://stackoverflow.com/questions/26413613/asyncio-is-it-possible-to-cancel-a-future-been-run-by-an-executor
- 2: run_in_executor not stopping thread after task cancellation in asyncio (Python 3.11) python/cpython#107505
- 3: https://www.pythontutorials.net/blog/asyncio-is-it-possible-to-cancel-a-future-been-run-by-an-executor/
- 4: https://stackoverflow.com/questions/69760390/python-asyncio-cancel-run-in-executor-blocks-mainthread
- 5: https://docs.python.org/3.5/library/concurrent.futures.html
- 6: https://docs.python.org/3/library/concurrent.futures.html
- 7: https://docs.python.org/3/library/asyncio-eventloop.html
- 8: https://stackoverflow.com/questions/72542334/cancel-run-in-executor-coroutine-from-the-main-thread-not-working
Do not rely on cancellation to stop MCP setup work.
loop.run_in_executor(..., adapter.__enter__) cannot be stopped by task.cancel() when adapter.__enter__ is already scheduled on the thread pool. The worker may still perform MCP connection I/O after the coroutine is cancelled, blocking the same-thread __exit__ cleanup or leaving the connection in a pending state. Add cooperative cancellation for blocked work, or move the whole lifecycle to an explicit process/thread pool where cleanup is guaranteed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@aiac/demo/assets/agents/github_agent/a2a_agent.py` around lines 206 - 207,
Update the MCP setup lifecycle around adapter.__enter__ and its
ThreadPoolExecutor so coroutine cancellation cannot leave setup I/O running
without cleanup. Add cooperative cancellation for the blocked worker, or move
both __enter__ and __exit__ into an explicitly managed process/thread execution
context that guarantees cleanup before cancellation completes.
Source: Coding guidelines
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 15
🧹 Nitpick comments (5)
scripts/opa-kind-enable.sh (2)
54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueValidate the resolved container runtime.
If the user sets
CONTAINER_RUNTIMEto a runtime that is not installed, the script fails later at thebuildstep with a shell "command not found" error. A single check after detection gives a clear message.♻️ Proposed check
else CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" fi + +if ! command -v "$CONTAINER_RUNTIME" &> /dev/null; then + echo "ERROR: container runtime '${CONTAINER_RUNTIME}' not found in PATH" >&2 + exit 1 +fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/opa-kind-enable.sh` around lines 54 - 60, After the container-runtime detection conditional, validate the resolved CONTAINER_RUNTIME with command -v before any build or runtime commands execute. If it is unavailable, emit a clear error identifying the missing runtime and exit nonzero; preserve the existing provider and default-selection behavior.
142-143: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBoth scripts delete agent pods without waiting for the replacements. Each script runs
kubectl delete pods -l rossoctl.io/type=agentand then prints its completion message. The replacement pods are still starting, so the printed verification command can read stale state, and a caller that chains the next demo step can hit AuthBridge sidecars that do not yet serve the intended pipeline. The shared root cause is one missing readiness wait after the label-based delete.
scripts/opa-kind-enable.sh#L142-L143: addkubectl wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5mafter the delete.scripts/opa-kind-restore.sh#L50-L51: add the samekubectl waitcall after the delete.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/opa-kind-enable.sh` around lines 142 - 143, The agent pod restart flows do not wait for replacement pods to become Ready. In scripts/opa-kind-enable.sh lines 142-143, add kubectl wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5m immediately after the pod deletion; apply the same change in scripts/opa-kind-restore.sh lines 50-51 after its delete command.aiac/demo/use-cases/uc1-onboarding/Makefile (1)
48-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
make diffwithoutPRIORpasses a bare--diffflag.If a user runs
make diffwith noPRIOR, the recipe expands to--diffwith no value. Add a default so the target is self-describing.♻️ Proposed default
+PRIOR ?= 01-after-agent + diff: ## Show the latest snapshot, diffed against generated/<PRIOR> (e.g. make diff PRIOR=01-after-agent) $(PYTHON) show-state.py --diff $(PRIOR)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/use-cases/uc1-onboarding/Makefile` around lines 48 - 49, Update the diff target’s show-state.py invocation to supply a meaningful default value when PRIOR is unset, so running make diff without arguments remains self-describing while preserving the explicit PRIOR behavior.aiac/demo/use-cases/uc1-onboarding/lib/_lib.py (1)
73-80: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
table()raises IndexError whenrowsis empty andheadersis None.Line 75 indexes
all_rows[0]. Every current caller passesheaders, so this is latent only. Add an early return to keep the helper safe.♻️ Proposed guard
def table(rows: list[tuple[str, ...]], headers: tuple[str, ...] | None = None) -> None: all_rows = ([headers] if headers else []) + rows + if not all_rows: + return widths = [max(len(str(r[i])) for r in all_rows) for i in range(len(all_rows[0]))]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py` around lines 73 - 80, Update table() to return immediately when both rows and headers are absent, before accessing all_rows[0]. Preserve the existing formatting behavior when headers or data rows are available.aiac/demo/use-cases/uc1-onboarding/demo.md (1)
12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a language to the two fenced code blocks.
markdownlint reports MD040 for the policy block at line 12 and the architecture diagram at line 106. Use
textfor both.Also applies to: 106-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/use-cases/uc1-onboarding/demo.md` at line 12, Update the two fenced code blocks in the demo documentation, including the policy block and architecture diagram, to specify the text language after each opening fence. Use text for both blocks to satisfy markdownlint MD040.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@aiac/CLAUDE.md`:
- Around line 25-27: Update the issue-label description in CLAUDE.md to use the
canonical aiac-status:<value> prefix instead of status:<value>, matching the
conventions documented in issue-tracker.md and triage-labels.md while preserving
the existing status-field description.
In `@aiac/demo/assets/agents/github_agent/.dockerignore`:
- Around line 3-4: Update the Docker ignore rules near the existing .env entry
to exclude all dotenv files using the .env* pattern, and re-include only a
non-secret template file if the build requires it.
In `@aiac/demo/assets/agents/github_agent/a2a_agent.py`:
- Around line 206-207: Update the MCP setup lifecycle around adapter.__enter__
and its ThreadPoolExecutor so coroutine cancellation cannot leave setup I/O
running without cleanup. Add cooperative cancellation for the blocked worker, or
move both __enter__ and __exit__ into an explicitly managed process/thread
execution context that guarantees cleanup before cancellation completes.
In `@aiac/demo/assets/agents/github_agent/Dockerfile`:
- Around line 12-13: Update the Dockerfile runtime setup to create a non-root
user with UID 10001, change the ownership target from 1001 to 10001, and run the
image as USER 10001. Keep AuthBridge-specific Kubernetes UID overrides separate
from this image default.
In `@aiac/demo/assets/agents/github_agent/README.md`:
- Around line 23-24: Use a repository-root path for the UC-1 stub in both
aiac/demo/assets/agents/github_agent/README.md lines 23-24 and
aiac/docs/specs/demo/github-agent.md line 221, changing the reference to
aiac/demo/assets/tools/github_tool/; keep both documents consistent.
In `@aiac/demo/assets/INSTALL.md`:
- Around line 48-55: Update the deployment guidance in
aiac/demo/assets/agents/github_agent/README.md:73-74 to clearly label the linked
INSTALL.md as discovery-only and not sufficient for agent execution, while
documenting the required github-tool-mcp prerequisite for execution. Leave
aiac/demo/assets/INSTALL.md:48-55 unchanged, since it correctly remains a
discovery-only install path.
In `@aiac/demo/assets/install.sh`:
- Around line 42-51: Update build_and_load() to use kind load image-archive when
detect_runtime() selects podman, saving the built image with $RUNTIME save and
passing that archive to kind; retain kind load docker-image for docker. Ensure
the Podman path follows the supported image-archive import flow.
In `@aiac/demo/assets/tools/github_tool/requirements.txt`:
- Around line 1-5: Update the dependency entries in requirements.txt to use
exact audited versions rather than lower and upper bounds, including pinning mcp
to the audited 1.9.4 release and explicitly pinning its vulnerable transitive
python-multipart dependency to a safe audited version. Pin uvicorn, httpx,
pytest, and pytest-asyncio as well, using the project’s approved versions.
In `@aiac/demo/assets/tools/github_tool/test/test_server.py`:
- Around line 26-28: Update test_returns_exactly_four_tool_names to assert that
the tools response length equals the expected number of TOOL_SCOPES entries
before comparing the name set, ensuring duplicate or additional entries cannot
pass the test.
In `@aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py`:
- Around line 120-122: Update the installation-complete check in 00-prereqs.py
at lines 120-122 to include aiac-policy-store and wait for both Deployments and
the StatefulSet to become ready before returning. Also update the install.sh
skip path at lines 151-153 to verify rollout readiness for both demo Deployments
before skipping installation.
In `@aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py`:
- Around line 54-75: Invoke ensure_default_audience_scope after the tool
onboarding flow creates the tool audience scope, using the agent client UUID and
tool audience scope name. Update the documented setup/onboard sequence or add
the call to the tool onboarding entry point, such as 04-onboard-tool.py, so
running make demo assigns the scope as a default on the agent client.
In `@aiac/demo/use-cases/uc1-onboarding/show-state.py`:
- Around line 69-79: Update the snapshot handling in show-state’s file
enumeration and grant_sets flow so incomplete snapshots do not reach grant_sets,
which expects both Rego files. Require both cfg.inbound_rego and
cfg.outbound_rego before evaluating grants; otherwise retain the available-file
output and skip grant evaluation without failing.
- Around line 87-103: Update show_diff to first render unified, line-oriented
diffs for the inbound and outbound Rego files between prior_dir and current_dir,
handling a missing current snapshot file consistently with the existing flow.
Keep the existing grant_sets comparison and added/removed summary after the
textual diffs so both policy changes and order-independent grant differences are
shown.
In `@aiac/docs/agents/domain.md`:
- Around line 21-31: Update the fenced tree block in domain.md to specify the
text language identifier, changing the opening fence to use text while
preserving the existing non-executable directory tree content.
In `@scripts/opa-kind-restore.sh`:
- Around line 46-48: Update the helm upgrade invocation in the restore flow of
opa-kind-restore.sh to include --wait with the existing 5-minute timeout,
ensuring the reverted pipeline configuration is reconciled before subsequent pod
deletion. Leave the existing values file and documented omission of --set flags
unchanged.
---
Nitpick comments:
In `@aiac/demo/use-cases/uc1-onboarding/demo.md`:
- Line 12: Update the two fenced code blocks in the demo documentation,
including the policy block and architecture diagram, to specify the text
language after each opening fence. Use text for both blocks to satisfy
markdownlint MD040.
In `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py`:
- Around line 73-80: Update table() to return immediately when both rows and
headers are absent, before accessing all_rows[0]. Preserve the existing
formatting behavior when headers or data rows are available.
In `@aiac/demo/use-cases/uc1-onboarding/Makefile`:
- Around line 48-49: Update the diff target’s show-state.py invocation to supply
a meaningful default value when PRIOR is unset, so running make diff without
arguments remains self-describing while preserving the explicit PRIOR behavior.
In `@scripts/opa-kind-enable.sh`:
- Around line 54-60: After the container-runtime detection conditional, validate
the resolved CONTAINER_RUNTIME with command -v before any build or runtime
commands execute. If it is unavailable, emit a clear error identifying the
missing runtime and exit nonzero; preserve the existing provider and
default-selection behavior.
- Around line 142-143: The agent pod restart flows do not wait for replacement
pods to become Ready. In scripts/opa-kind-enable.sh lines 142-143, add kubectl
wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent
--timeout=5m immediately after the pod deletion; apply the same change in
scripts/opa-kind-restore.sh lines 50-51 after its delete command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7d01bc82-495a-41f2-a4bc-99ee2d9845db
⛔ Files ignored due to path filters (1)
aiac/demo/assets/agents/github_agent/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (55)
aiac/.gitignoreaiac/CLAUDE.mdaiac/demo/assets/INSTALL.mdaiac/demo/assets/agents/github_agent/.dockerignoreaiac/demo/assets/agents/github_agent/.env.templateaiac/demo/assets/agents/github_agent/Dockerfileaiac/demo/assets/agents/github_agent/README.mdaiac/demo/assets/agents/github_agent/a2a_agent.pyaiac/demo/assets/agents/github_agent/github_agent/__init__.pyaiac/demo/assets/agents/github_agent/github_agent/agents.pyaiac/demo/assets/agents/github_agent/github_agent/config.pyaiac/demo/assets/agents/github_agent/github_agent/data_types.pyaiac/demo/assets/agents/github_agent/github_agent/event.pyaiac/demo/assets/agents/github_agent/github_agent/llm.pyaiac/demo/assets/agents/github_agent/github_agent/main.pyaiac/demo/assets/agents/github_agent/github_agent/prompts.pyaiac/demo/assets/agents/github_agent/github_agent/tools.pyaiac/demo/assets/agents/github_agent/k8s/configmaps.yamlaiac/demo/assets/agents/github_agent/k8s/github-agent-deployment.yamlaiac/demo/assets/agents/github_agent/pyproject.tomlaiac/demo/assets/agents/github_agent/test/test_agent_card.pyaiac/demo/assets/agents/github_agent/test/test_prereq.pyaiac/demo/assets/agents/github_agent/test/test_tools.pyaiac/demo/assets/agents/github_agent/test_startup.expaiac/demo/assets/install.shaiac/demo/assets/tools/github_tool/Dockerfileaiac/demo/assets/tools/github_tool/k8s/github-tool-deployment.yamlaiac/demo/assets/tools/github_tool/pytest.iniaiac/demo/assets/tools/github_tool/requirements.txtaiac/demo/assets/tools/github_tool/server.pyaiac/demo/assets/tools/github_tool/test/__init__.pyaiac/demo/assets/tools/github_tool/test/conftest.pyaiac/demo/assets/tools/github_tool/test/test_server.pyaiac/demo/use-cases/.gitkeepaiac/demo/use-cases/uc1-onboarding/Makefileaiac/demo/use-cases/uc1-onboarding/demo.mdaiac/demo/use-cases/uc1-onboarding/init/00-prereqs.pyaiac/demo/use-cases/uc1-onboarding/init/01-clear.pyaiac/demo/use-cases/uc1-onboarding/init/02-setup.pyaiac/demo/use-cases/uc1-onboarding/lib/_lib.pyaiac/demo/use-cases/uc1-onboarding/lib/scenario.pyaiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.pyaiac/demo/use-cases/uc1-onboarding/onboard/03-onboard-agent.pyaiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.pyaiac/demo/use-cases/uc1-onboarding/run/run-developer.pyaiac/demo/use-cases/uc1-onboarding/run/run-devops.pyaiac/demo/use-cases/uc1-onboarding/run/run-tester.pyaiac/demo/use-cases/uc1-onboarding/show-state.pyaiac/docs/agents/domain.mdaiac/docs/agents/issue-tracker.mdaiac/docs/agents/triage-labels.mdaiac/docs/specs/demo/github-agent.mdaiac/docs/specs/demo/github-tool.mdscripts/opa-kind-enable.shscripts/opa-kind-restore.sh
🛑 Comments failed to post (4)
aiac/demo/assets/agents/github_agent/.dockerignore (1)
3-4: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Ignore all dotenv files from the build context.
The Dockerfile uses
COPY . ., but this file excludes only.env. Credentials in.env.ollama,.env.openai, or.env.claudecan therefore be copied into the image. Use.env*and re-include only a non-secret template if required.Proposed ignore rule
-.env +.env*📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.# Secrets — never bake into the image .env*🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/assets/agents/github_agent/.dockerignore` around lines 3 - 4, Update the Docker ignore rules near the existing .env entry to exclude all dotenv files using the .env* pattern, and re-include only a non-secret template file if the build requires it.aiac/demo/assets/agents/github_agent/Dockerfile (1)
12-13: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Run the image as UID 10001.
The Dockerfile defaults to UID 1001 and does not create the runtime user. The repository contract requires AIAC service images to create and run as UID 10001. Keep any AuthBridge-specific Kubernetes override separate from the image default.
Proposed Dockerfile change
-RUN chown -R 1001:1001 /app -USER 1001 +RUN useradd --no-create-home --uid 10001 aiac \ + && chown -R 10001:10001 /app +USER 10001As per coding guidelines,
aiac/**/Dockerfileimages must create and run as non-root UID 10001.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.RUN useradd --no-create-home --uid 10001 aiac \ && chown -R 10001:10001 /app USER 10001🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/assets/agents/github_agent/Dockerfile` around lines 12 - 13, Update the Dockerfile runtime setup to create a non-root user with UID 10001, change the ownership target from 1001 to 10001, and run the image as USER 10001. Keep AuthBridge-specific Kubernetes UID overrides separate from this image default.Source: Coding guidelines
aiac/demo/assets/tools/github_tool/requirements.txt (1)
1-5: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash set -euo pipefail echo "== requirements file ==" sed -n '1,40p' aiac/demo/assets/tools/github_tool/requirements.txt echo echo "== package versions from OSV ==" python3 - <<'PY' import json, urllib.request for pkg, ver in [("mcp", "1.9.4"), ("python-multipart", "0.0.9")]: data = json.dumps({ "version": ver, "package": {"name": pkg, "ecosystem": "PyPI"} }).encode() req = urllib.request.Request( "https://api.osv.dev/v1/query", data=data, headers={"Content-Type": "application/json"} ) with urllib.request.urlopen(req, timeout=20) as resp: body = json.load(resp) print(f"-- {pkg}=={ver}") print(json.dumps(body, indent=2, sort_keys=True)) PY echo echo "== optional resolver metadata with pip (if available) ==" python3 - <<'PY' import json, urllib.request def fetch_json(url): req = urllib.request.Request(url, headers={"User-Agent":"read-only-verification"}) with urllib.request.urlopen(req, timeout=20) as resp: return json.load(resp) for pkg in ("mcp", "python-multipart"): print(f"-- {pkg}") try: info = fetch_json(f"https://pypi.org/pypi/{pkg}/json") print("latest:", info["info"].get("version")) print("requires_dist:") for r in info["info"].get("requires_dist") or []: print(r) # If a resolver is available in the sandbox without installing repo code, show one resolution of the exact ranges. except Exception as e: print("pypi fetch failed:", e) PYRepository: rossoctl/cortex
Length of output: 2593
🏁 Script executed:
#!/usr/bin/env bash set -euo pipefail echo "== dependency resolver metadata with pip (if available) ==" python3 - <<'PY' import json, urllib.request, sys, subprocess, tempfile, os def fetch_json(url): req = urllib.request.Request(url, headers={"User-Agent":"read-only-verification"}) with urllib.request.urlopen(req, timeout=20) as resp: return json.load(resp) requirements_path = "aiac/demo/assets/tools/github_tool/requirements.txt" for pkg in ("mcp", "uvicorn", "httpx", "pytest", "pytest-asyncio"): print(f"-- {pkg}") try: info = fetch_json(f"https://pypi.org/pypi/{pkg}/json") print("version:", info["info"].get("version")) print("requires_dist:") for r in info["info"].get("requires_dist") or []: print(r) except Exception as e: print("pypi fetch failed:", e) print("\n-- requirements to resolve:") with open(requirements_path, encoding="utf-8") as f: print(f.read()) requirements_content = open(requirements_path, encoding="utf-8").read() with tempfile.TemporaryDirectory(prefix="cr-resolve-") as td: req_path = os.path.join(td, "requirements.txt") with open(req_path, "w", encoding="utf-8") as f: f.write(requirements_content) try: result = subprocess.run( [sys.executable, "-m", "pip", "install", "--dry-run", "--ignore-installed", "--no-deps", "-r", req_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=180, ) print("dry-run status:", result.returncode) for line in result.stdout.splitlines(): if any(s in line for s in ("Would install", "Collecting")): print(line) print("\nstderr sample:") for line in (result.stderr or "").splitlines()[-20:]: print(line) except Exception as e: print("pip dry-run unavailable or failed:", e) PY echo echo "== pip freeze for installed resolver if available ==" python3 - <<'PY' import pkg_resources, sys print("pkg_resources version:", pkg_resources.get_distribution("setuptools").version if "pkg_resources" in sys.modules else "n/a") print("pip version:", pkg_resources.get_distribution("pip").version) PY echo echo "== exact mcp 1.9.4 dependencies if metadata is available ==" python3 - <<'PY' import urllib.request, json url = "https://pypi.org/pypi/mcp/1.9.4/json" req = urllib.request.Request(url, headers={"User-Agent":"read-only-verification"}) try: with urllib.request.urlopen(req, timeout=20) as resp: info = json.load(resp) print("mcp==1.9.4 required by this:", any(r.startswith("mcp (<2,") for r in info["info"].get("requires_dist") or [])) for i, r in enumerate(info["info"].get("requires_dist") or []): if "python-multipart" in r: print("mcp==1.9.4 dependency mentions python-multipart:", r) break else: print("mcp==1.9.4 dependency mentions python-multipart: None") except Exception as e: print("fetch failed:", e) PYRepository: rossoctl/cortex
Length of output: 1415
🌐 Web query:
OSV vulnerability mcp python 1.9.4💡 Result:
The Python package
mcp(the Model Context Protocol SDK) has been associated with specific security vulnerabilities that were addressed in version 1.9.4. A primary vulnerability, tracked as CVE-2025-53366 (also referenced as GHSA-3qhf-m339-9g5v and PYSEC-2026-1616), involved a validation error in the FastMCP Server [1][2][3]. This flaw could allow an unhandled exception when processing malformed requests, leading to a denial of service (DoS) where the server would return 500 errors and require a manual restart [1][4][5]. Version 1.9.4 includes the fix for this issue [1][3][5]. Additionally, another vulnerability related to an unhandled exception in the streamable HTTP transport (GHSA-j975-95f5-7wqh / PYSEC-2026-1618) was identified, which also leads to a denial of service [6][7]. This issue affects versions prior to 1.10.0 [6][7]. It is recommended to upgrade to at least version 1.9.4 (and ideally to the latest version) to mitigate these risks [5]. You can upgrade using: pip install --upgrade "mcp>=1.9.4" [5] Please note that search results also returned various independent projects named "OSV-MCP" or similar (e.g., StacklokLabs/osv-mcp, EdenYavin/OSV-MCP), which are distinct, third-party MCP servers designed to query the OSV (Open Source Vulnerabilities) database and are not themcpSDK itself [8][9][10][11].Citations:
- 1: https://osv.dev/vulnerability/CVE-2025-53366
- 2: https://osv.dev/vulnerability/GHSA-3qhf-m339-9g5v
- 3: GHSA-3qhf-m339-9g5v
- 4: https://nvd.nist.gov/vuln/detail/CVE-2025-53366
- 5: https://www.sentinelone.com/vulnerability-database/cve-2025-53366/
- 6: https://osv.dev/vulnerability/GHSA-j975-95f5-7wqh
- 7: https://osv.dev/vulnerability/PYSEC-2026-1618
- 8: https://github.com/StacklokLabs/osv-mcp
- 9: https://github.com/edenyavin/osv-mcp
- 10: https://github.com/kodustech/mcp-osv
- 11: https://github.com/ptelang/osv-mcp
🌐 Web query:
OSV vulnerability python-multipart 0.0.9💡 Result:
Version 0.0.9 of python-multipart is affected by multiple high-severity and critical vulnerabilities, as it precedes several security patches released to address parsing, denial-of-service, and path traversal issues. Users are strongly advised to upgrade to the latest version of the library. The following vulnerabilities affect version 0.0.9: 1. Parameter Smuggling (CVE-2026-53538 / GHSA-6jv3-5f52-599m): The library incorrectly treated the semicolon (;) as a field separator in application/x-www-form-urlencoded bodies, inconsistent with the WHATWG URL standard. This allows attackers to smuggle form fields past upstream validators. Fixed in 0.0.30 [1][2][3]. 2. Memory Exhaustion via Negative Content-Length (CVE-2026-53540 / GHSA-v9pg-7xvm-68hf): The parse_form function failed to validate the Content-Length header, where a negative value caused the library to read the entire request body into memory. Fixed in 0.0.31 [4][5]. 3. Denial of Service (DoS) via Unbounded Multipart Headers (GHSA-pp6c-gr5w-3c5g): The parser lacked limits on the number or size of multipart part headers, allowing CPU exhaustion through crafted requests. Fixed in 0.0.27 [6]. 4. DoS via Large Multipart Preamble/Epilogue (CVE-2026-40347 / GHSA-mj87-hwqh-73pj): The library was vulnerable to DoS when processing crafted requests with excessively large preamble or epilogue sections. Fixed in 0.0.26 [7]. 5. DoS via Boundary Deformation (CVE-2024-53981 / GHSA-59g5-xgcq-4qw3): Excessive logging and CPU load could be triggered by malformed multipart boundaries. Fixed in 0.0.18 [8][9][10]. 6. Path Traversal (CVE-2026-24486): A vulnerability existed when using non-default configurations (specifically UPLOAD_DIR and UPLOAD_KEEP_FILENAME=True), allowing arbitrary file writes. Fixed in 0.0.22 [11]. Given these cumulative security risks, users should update to the most recent stable release of python-multipart to ensure all these issues are mitigated. [1][6][11][4][8][7]
Citations:
- 1: https://osv.dev/vulnerability/GHSA-6jv3-5f52-599m
- 2: https://osv.dev/vulnerability/PYSEC-2026-3037
- 3: GHSA-6jv3-5f52-599m
- 4: https://osv.dev/vulnerability/GHSA-v9pg-7xvm-68hf
- 5: GHSA-v9pg-7xvm-68hf
- 6: https://osv.dev/vulnerability/GHSA-pp6c-gr5w-3c5g
- 7: https://osv.dev/vulnerability/CVE-2026-40347
- 8: https://osv.dev/vulnerability/CVE-2024-53981
- 9: GHSA-59g5-xgcq-4qw3
- 10: https://nvd.nist.gov/vuln/detail/CVE-2024-53981
- 11: https://osv.dev/vulnerability/CVE-2026-24486
🌐 Web query:
Pypi mcp 1.9.4 dependencies python-multipart💡 Result:
In the mcp package version 1.9.4 on PyPI, the dependency requirement for python-multipart is defined as >=0.0.9 [1][2]. While this requirement is officially specified in the project's metadata [1], it has been noted by community members that this version floor allows the installation of older versions of python-multipart that may be affected by known security vulnerabilities [3]. Consequently, some downstream users choose to enforce a higher minimum version (e.g., >=0.0.20 or newer) in their own project configurations to mitigate these risks [3].
Citations:
- 1: https://pypi.org/project/mcp/1.9.4/
- 2: https://pypi.org/project/mcp/1.9.1/
- 3: modelcontextprotocol/python-sdk#2482
Pin audited dependency versions.
Use exact pins in
aiac/demo/assets/tools/github_tool/requirements.txtso builds do not select vulnerable releases.mcp<2can select 1.9.4, andmcp==1.9.4allowspython-multipart>=0.0.9, which includes known high-severity multipart parser vulnerabilities.🧰 Tools
🪛 OSV Scanner (2.4.0)
[HIGH] 1-1: mcp 1.9.4: Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection by default
(PYSEC-2026-1617)
[HIGH] 1-1: mcp 1.9.4: MCP Python SDK has Unhandled Exception in Streamable HTTP Transport, Leading to Denial of Service
(PYSEC-2026-1618)
[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: HTTP transports serve session requests without verifying the authenticated principal
(PYSEC-2026-3482)
[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: WebSocket server transport does not support Host/Origin validation
(PYSEC-2026-3483)
[HIGH] 1-1: mcp 1.9.4: Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection by default
(GHSA-9h52-p55h-vw2f)
[HIGH] 1-1: mcp 1.9.4: MCP Python SDK has Unhandled Exception in Streamable HTTP Transport, Leading to Denial of Service
(GHSA-j975-95f5-7wqh)
[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: HTTP transports serve session requests without verifying the authenticated principal
(GHSA-jpw9-pfvf-9f58)
[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: WebSocket server transport does not support Host/Origin validation
(GHSA-vj7q-gjh5-988w)
[HIGH] 1-1: python-multipart 0.0.9: Denial of service (DoS) via deformation
multipart/form-databoundary(PYSEC-2026-1851)
[HIGH] 1-1: python-multipart 0.0.9: Python-Multipart has Arbitrary File Write via Non-Default Configuration
(PYSEC-2026-1852)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Quadratic-time querystring parsing with semicolon separators causes CPU denial of service
(PYSEC-2026-3036)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Semicolon treated as querystring field separator enables parameter smuggling
(PYSEC-2026-3037)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart affected by Denial of Service via large multipart preamble or epilogue data
(PYSEC-2026-3038)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart has Denial of Service via unbounded multipart part headers
(PYSEC-2026-3039)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Negative Content-Length in parse_form buffers the entire body in memory
(PYSEC-2026-3040)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Content-Disposition parameter smuggling via RFC 2231/5987 extended parameters
(PYSEC-2026-3041)
[HIGH] 1-1: python-multipart 0.0.9: Denial of service (DoS) via deformation
multipart/form-databoundary(GHSA-59g5-xgcq-4qw3)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Quadratic-time querystring parsing with semicolon separators causes CPU denial of service
(GHSA-5rvq-cxj2-64vf)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Semicolon treated as querystring field separator enables parameter smuggling
(GHSA-6jv3-5f52-599m)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart affected by Denial of Service via large multipart preamble or epilogue data
(GHSA-mj87-hwqh-73pj)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart has Denial of Service via unbounded multipart part headers
(GHSA-pp6c-gr5w-3c5g)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Negative Content-Length in parse_form buffers the entire body in memory
(GHSA-v9pg-7xvm-68hf)
[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Content-Disposition parameter smuggling via RFC 2231/5987 extended parameters
(GHSA-vffw-93wf-4j4q)
[HIGH] 1-1: python-multipart 0.0.9: Python-Multipart has Arbitrary File Write via Non-Default Configuration
(GHSA-wp53-j4wj-2cfg)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/assets/tools/github_tool/requirements.txt` around lines 1 - 5, Update the dependency entries in requirements.txt to use exact audited versions rather than lower and upper bounds, including pinning mcp to the audited 1.9.4 release and explicitly pinning its vulnerable transitive python-multipart dependency to a safe audited version. Pin uvicorn, httpx, pytest, and pytest-asyncio as well, using the project’s approved versions.Sources: Coding guidelines, Linters/SAST tools
aiac/demo/assets/tools/github_tool/test/test_server.py (1)
26-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the response count.
Set comparison accepts duplicate tool names. A response with an additional duplicate tool passes this test. Assert the list length before comparing the names.
Proposed fix
def test_returns_exactly_four_tool_names(self, client): tools = _tools_list(client) + assert len(tools) == len(TOOL_SCOPES) assert {t["name"] for t in tools} == set(TOOL_SCOPES.keys())📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.def test_returns_exactly_four_tool_names(self, client): tools = _tools_list(client) assert len(tools) == len(TOOL_SCOPES) assert {t["name"] for t in tools} == set(TOOL_SCOPES.keys())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@aiac/demo/assets/tools/github_tool/test/test_server.py` around lines 26 - 28, Update test_returns_exactly_four_tool_names to assert that the tools response length equals the expected number of TOOL_SCOPES entries before comparing the name set, ensuring duplicate or additional entries cannot pass the test.
Issues found
|
- install.sh: load images into kind via image-archive under podman (kind load docker-image only works with docker). - 00-prereqs.py: wait for both demo deployments to roll out on the already-deployed path, so "already deployed" also means "actually up". - 04-onboard-tool.py: assign the tool's *-aud default scope to the agent client now that the tool (and its scope) exist — 02-setup runs too early for this and hits the skip branch. - show-state.py: guard grant tallies behind a complete-snapshot check (grant_sets loads both Rego files) and render the line-oriented Rego text diff the module docstring promises before the semantic set diff. - opa-kind-restore.sh: --wait --timeout 5m so the helm upgrade blocks on readiness before the sidecar restart. - Docs: aiac/-prefix two github_tool path references for consistency with surrounding runnable-command paths; add text language to a fenced block. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <oblinder@gmail.com>
- #1: Note that tracking issues on the s-and-p-team/cortex fork (rather than upstream rossoctl/cortex) is deliberate, in issue-tracker.md and aiac/CLAUDE.md, so the -R scoping on gh examples isn't mistaken for a wrong-slug copy-paste. - #2: Comment USER_PASSWORD in scenario.py as a demo-only credential for ephemeral local/CI clusters, never a production/secret value. - #3: Add --ignore-not-found to the agent-pod restart in scripts/opa-kind-enable.sh so it no-ops cleanly on zero matches. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <oblinder@gmail.com>
|
Thanks @abigailgold — addressed all three in 88603a1.
Assisted-By: Claude (Anthropic AI) noreply@anthropic.com |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py`:
- Line 46: Update the onboarding flow around ensure_default_audience_scope() to
detect when the audience scope assignment fails, using a returned success flag
or propagated exception. Stop onboarding before its success reporting path when
assignment is unsuccessful, while preserving normal continuation when the scope
is assigned.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0c4e755b-1608-4530-9a30-5ffda23020b2
📒 Files selected for processing (12)
aiac/CLAUDE.mdaiac/demo/assets/agents/github_agent/README.mdaiac/demo/assets/install.shaiac/demo/use-cases/uc1-onboarding/init/00-prereqs.pyaiac/demo/use-cases/uc1-onboarding/lib/scenario.pyaiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.pyaiac/demo/use-cases/uc1-onboarding/show-state.pyaiac/docs/agents/domain.mdaiac/docs/agents/issue-tracker.mdaiac/docs/specs/demo/github-agent.mdscripts/opa-kind-enable.shscripts/opa-kind-restore.sh
🚧 Files skipped from review as they are similar to previous changes (8)
- aiac/docs/agents/domain.md
- aiac/demo/assets/install.sh
- aiac/demo/use-cases/uc1-onboarding/lib/scenario.py
- aiac/docs/agents/issue-tracker.md
- aiac/demo/use-cases/uc1-onboarding/show-state.py
- scripts/opa-kind-restore.sh
- aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py
- aiac/CLAUDE.md
CodeRabbit flagged that 04-onboard-tool.py reported success even when ensure_default_audience_scope() silently skipped assignment. After tool onboarding the tool's *-aud client scope must exist; a missing scope means the agent's exchanged tokens would lack the tool audience and downstream calls would fail — yet onboarding still printed success. - ensure_default_audience_scope() now returns bool: False when it skips because the scope is absent, True when assigned/already present. - 04-onboard-tool.py aborts (non-zero) before the success message when the scope is missing post-onboarding. - The 02-setup.py path (run() before the tool exists) still ignores the result, where a missing scope is expected and non-fatal. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <oblinder@gmail.com>
What & why
Adds the runnable UC-1 onboarding demo / walkthrough that PR #710 explicitly deferred to a
follow-up PR. This completes the demo portion of #645 (Phase 1: functional AIAC agent + onboarding
demo).
PR #710 shipped the implementation, the integration-test ladder, and the demo workloads; this PR
adds the live, Makefile-driven walkthrough that ties them together.
The demo flow
demo/use-cases/uc1-onboarding/is a live-only demo (real cluster, real Keycloak, real LLM call,real RFC 8693 token exchange). It reproduces the same pipeline exercised by the rung-1/2/3
integration tests (
test/integration/test_uc1_onboard_*.py, specdocs/specs/integration-test/uc1-onboarding-pipeline.md):Nobody hand-writes the access rules: a two-line plain-English policy plus discovered capabilities
produce the inbound (who may call the agent) and outbound (what it may do downstream) Rego gates.
Contents
demo/use-cases/uc1-onboarding/—Makefile,init/,onboard/,run/,lib/,show-state.py,demo.mddemo/assets/—github-agent+github-toolworkloads, k8s manifests, andINSTALL.mddocs/specs/demo/{github-agent,github-tool}.mdscripts/opa-kind-{enable,restore}.sh— toggle the OPA filesystem-stub writer on a Kind clusteraiac/docs/agents/+aiac/CLAUDE.mddemo.mdTesting
The demo reuses the same scenario oracle and harness as the green
-m integrationrung-1/2/3ladder. A full end-to-end run requires a live Kagenti cluster + Keycloak + an LLM endpoint
(
make prereqs/make demo).Addresses #645. Follows #710.
Assisted-By: Claude (Anthropic AI) noreply@anthropic.com
Summary by CodeRabbit
New Features
Bug Fixes
Documentation