Skip to content

Commit 884f31e

Browse files
authored
Merge branch 'main' into feat/linear-processor-feedback
2 parents eb64946 + be3207a commit 884f31e

143 files changed

Lines changed: 25105 additions & 2463 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Build context is repo root (see cdk/src/stacks/agent.ts) so the
2+
# Dockerfile can COPY contracts/ alongside agent/. Exclusions below
3+
# keep the context lean — without them the entire monorepo (CDK
4+
# cdk.out/, node_modules/, docs/dist/, etc.) gets uploaded on every
5+
# AgentCore deploy.
6+
7+
# CDK output (recursive include if not excluded)
8+
cdk/cdk.out/
9+
cdk/lib/
10+
cdk/node_modules/
11+
12+
# CLI and docs build artifacts
13+
cli/lib/
14+
cli/node_modules/
15+
docs/dist/
16+
docs/node_modules/
17+
docs/.astro/
18+
19+
# Shared node_modules
20+
node_modules/
21+
22+
# Agent venv and cache (rebuilt inside image via uv)
23+
agent/.venv/
24+
agent/__pycache__/
25+
agent/**/__pycache__/
26+
agent/**/*.pyc
27+
28+
# Git and tooling
29+
.git/
30+
.prek/
31+
.claude/
32+
**/.DS_Store
33+
34+
# Docs and assets not needed in image
35+
*.md
36+
*.png
37+
*.drawio
38+
*.html
39+
*.gif
40+
*.tape
41+
42+
# Worktrees + scratch
43+
abca-worktrees/
44+
.next-session-prompt.md
45+
.e2e-test-plan.md
46+
47+
# Test/coverage output
48+
coverage/
49+
**/coverage/
50+
.pytest_cache/
51+
**/.pytest_cache/
52+
53+
# IDE / OS
54+
.idea/
55+
.vscode/
56+
yarn-error.log
57+
yarn-debug.log
58+
npm-debug.log*

.pre-commit-config.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ repos:
7171
exclude: ^docs/node_modules/
7272
stages: [pre-commit]
7373

74+
- id: types-sync-cdk-cli
75+
name: type sync drift (CDK ↔ CLI)
76+
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && mise run check:types-sync'
77+
language: system
78+
pass_filenames: false
79+
files: ^(cdk/src/handlers/shared/types\.ts$|cli/src/types\.ts$|scripts/check-types-sync\.ts$)
80+
stages: [pre-commit]
81+
82+
- id: constants-sync
83+
name: cross-language constants drift (contracts/constants.json)
84+
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && mise run check:constants-sync'
85+
language: system
86+
pass_filenames: false
87+
files: ^(contracts/constants\.json$|agent/src/policy\.py$|cdk/src/handlers/shared/types\.ts$|cdk/src/constructs/blueprint\.ts$|scripts/check-constants-sync\.ts$)
88+
stages: [pre-commit]
89+
7490
- id: monorepo-security-pre-push
7591
name: security scans (pre-push)
7692
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && mise run hooks:pre-push:security'

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Handler entry tests: `cdk/test/handlers/orchestrate-task.test.ts`, `create-task.
4747
- **`prek install`** fails if Git **`core.hooksPath`** is set — another hook manager owns hooks; see [CONTRIBUTING.md](./CONTRIBUTING.md).
4848
- **Editing on `main` directly** — ALWAYS create a worktree with a feature branch for changes, even trivial ones. Main should stay clean; all work flows through worktree → branch → PR → merge.
4949
- **Git worktrees** — Always **`git fetch origin main`** before creating a new worktree to ensure you branch from the latest remote state. `node_modules/` and `agent/.venv/` are per-tree (not shared). Run **`mise run install`** in each new worktree before building. All CDK path references (`__dirname`-relative) and mise `config_roots` resolve correctly without extra setup.
50+
- **Bumping Cedar engines in isolation**`cedarpy` (Python, `agent/pyproject.toml`) and `@cedar-policy/cedar-wasm` (TypeScript, `cdk/package.json`) are two language bindings over the same Cedar Rust core. They MUST move together; even patch-version drift between bindings can yield divergent `(decision, matching_rule_ids)` on the same `(policy, input)` — invisible to per-side unit tests, caught (only) by `contracts/cedar-parity/` golden fixtures in CI. If you bump one engine you MUST bump the other to a tested-compatible version AND refresh the parity fixtures in the same commit. Both pins are EXACT (no `^`/`~`). See `docs/design/CEDAR_HITL_GATES.md` §15.6 (decision #23) and the parity-contract banner in `mise.toml`. **DO NOT** accept upstream's "Update branch" or auto-merge suggestions on cedarpy without verifying parity with cedar-wasm.
5051

5152
### Tech stack
5253

agent/.dockerignore

Lines changed: 0 additions & 6 deletions
This file was deleted.

agent/Dockerfile

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ COPY --from=gh-builder /out/gh /usr/local/bin/gh
2020
# - build-essential (native compilation for some repos)
2121
# - curl (downloads)
2222
RUN apt-get update && \
23+
# Patch any base-image CVEs that have a fix available in the
24+
# current Debian point release. Without this, transitive system-
25+
# library CVEs (e.g. libnghttp2 CVE-2026-27135) ride the base
26+
# ``python:3.13-slim`` tag until upstream rebuilds, which can be
27+
# weeks. ``--no-install-recommends`` keeps the upgrade narrow and
28+
# reproducible — only already-installed packages get bumped.
29+
apt-get upgrade -y --no-install-recommends && \
2330
apt-get install -y --no-install-recommends \
2431
curl \
2532
git \
2633
build-essential \
2734
ca-certificates \
2835
gnupg && \
29-
# Upgrade base image's CVE-2026-27135 vulnerability
30-
apt-get upgrade -y --no-install-recommends libnghttp2-14 && \
3136
# Cleanup early to keep peak disk usage low during builds.
3237
apt-get clean && \
3338
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
@@ -49,15 +54,28 @@ RUN npm install -g npm@latest && \
4954
# Install uv (fast Python package manager) — pinned for reproducibility
5055
COPY --from=ghcr.io/astral-sh/uv:0.11.14 /uv /usr/local/bin/uv
5156

52-
# Install Python dependencies via uv
53-
COPY pyproject.toml uv.lock /app/
57+
# Install Python dependencies via uv. Build context is repo root (set in
58+
# ``cdk/src/stacks/agent.ts``) so source paths are prefixed with ``agent/``.
59+
COPY agent/pyproject.toml agent/uv.lock /app/
5460
RUN uv sync --frozen --no-dev --directory /app
5561

5662
# Copy agent code (ARG busts cache so file edits are always picked up)
5763
ARG CACHE_BUST=0
58-
COPY src/ /app/src/
59-
COPY prepare-commit-msg.sh /app/
60-
COPY test_sdk_smoke.py test_subprocess_threading.py /app/
64+
COPY agent/src/ /app/src/
65+
# Cedar HITL built-in policy files (hard_deny.cedar + soft_deny.cedar).
66+
# ``agent/src/policy.py::_POLICIES_DIR`` resolves to ``/app/policies``
67+
# at import time; without these files the PolicyEngine init raises
68+
# ``missing built-in hard-deny policies`` and every task fails at 0
69+
# turns before the agent even connects to the CLI. Discovered during
70+
# Chunk 10 E2E T2.2 — the Dockerfile previously only copied ``src/``.
71+
COPY agent/policies/ /app/policies/
72+
# Cross-language constants (S9). ``agent/src/policy.py`` reads
73+
# ``/app/contracts/constants.json`` at import; the same file is consumed
74+
# by ``cdk/src/handlers/shared/types.ts`` at synth time. See
75+
# ``contracts/README.md`` for the contract.
76+
COPY contracts/ /app/contracts/
77+
COPY agent/prepare-commit-msg.sh /app/
78+
COPY agent/test_sdk_smoke.py agent/test_subprocess_threading.py /app/
6179

6280
# Create non-root user (Claude Code CLI refuses bypassPermissions as root)
6381
RUN useradd -m -s /bin/bash agent && \

agent/mise.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ run = "uvx bandit[toml] -c pyproject.toml -r . --severity-level=high"
5252

5353
[tasks."security:image"]
5454
description = "Scan container image with trivy"
55+
# Build context is repo root (..) so Dockerfile can COPY contracts/
56+
# alongside agent/ — matches cdk/src/stacks/agent.ts. Without -f and
57+
# the .. context, the build fails because COPY agent/... can't find
58+
# agent/ inside the agent/ directory.
5559
run = [
56-
"docker image inspect bgagent-local:latest >/dev/null 2>&1 || (ARCH=\"$(uname -m)\"; PLATFORM=\"linux/arm64\"; if [ \"$ARCH\" = \"x86_64\" ]; then PLATFORM=\"linux/amd64\"; fi; docker build --build-arg TARGETPLATFORM=\"$PLATFORM\" --build-arg CACHE_BUST=\"$(date +%s)\" -t bgagent-local:latest .)",
60+
"docker image inspect bgagent-local:latest >/dev/null 2>&1 || (ARCH=\"$(uname -m)\"; PLATFORM=\"linux/arm64\"; if [ \"$ARCH\" = \"x86_64\" ]; then PLATFORM=\"linux/amd64\"; fi; docker build --build-arg TARGETPLATFORM=\"$PLATFORM\" --build-arg CACHE_BUST=\"$(date +%s)\" -f Dockerfile -t bgagent-local:latest ..)",
5761
"trivy image --scanners vuln --ignore-unfixed --ignorefile .trivyignore --severity HIGH,CRITICAL --exit-code 1 bgagent-local:latest",
5862
]
5963

agent/policies/hard_deny.cedar

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Built-in hard-deny policy set for Cedar HITL engine.
2+
//
3+
// Hard-deny is ABSOLUTE: no --pre-approve scope and no blueprint `disable:`
4+
// directive can bypass these rules. See docs/design/CEDAR_HITL_GATES.md
5+
// §12.5 and decision #8.
6+
//
7+
// Every rule in this file MUST carry @tier("hard") + @rule_id annotations.
8+
// Adding a rule here expands the set of categorically-forbidden agent
9+
// actions; removing a rule requires a security review.
10+
11+
// Base catch-all permit. Specific forbid rules below override.
12+
@rule_id("base_permit")
13+
permit (principal, action, resource);
14+
15+
// pr_review tasks may never invoke Write. Absolute; cannot be overridden
16+
// by per-blueprint customization or --pre-approve.
17+
@tier("hard")
18+
@rule_id("pr_review_forbid_write")
19+
forbid (
20+
principal == Agent::TaskAgent::"pr_review",
21+
action == Agent::Action::"invoke_tool",
22+
resource == Agent::Tool::"Write"
23+
);
24+
25+
// pr_review tasks may never invoke Edit.
26+
@tier("hard")
27+
@rule_id("pr_review_forbid_edit")
28+
forbid (
29+
principal == Agent::TaskAgent::"pr_review",
30+
action == Agent::Action::"invoke_tool",
31+
resource == Agent::Tool::"Edit"
32+
);
33+
34+
// Reject `rm -rf /` and similar absolute-root destructive commands.
35+
@tier("hard")
36+
@rule_id("rm_slash")
37+
forbid (principal, action == Agent::Action::"execute_bash", resource)
38+
when { context.command like "*rm -rf /*" };
39+
40+
// Reject writes into `.git/` at the repo root (breaks local git state).
41+
@tier("hard")
42+
@rule_id("write_git_internals")
43+
forbid (principal, action == Agent::Action::"write_file", resource)
44+
when { context.file_path like ".git/*" };
45+
46+
// Reject writes into nested `.git/` directories (submodules, worktrees).
47+
@tier("hard")
48+
@rule_id("write_git_internals_nested")
49+
forbid (principal, action == Agent::Action::"write_file", resource)
50+
when { context.file_path like "*/.git/*" };
51+
52+
// Reject any SQL DROP TABLE through Bash — agents should not be running
53+
// destructive DDL against production or dev databases without a human
54+
// in the loop. Hard-deny because even "just testing locally" is a common
55+
// vector for data loss (wrong DB connected via saved credentials).
56+
@tier("hard")
57+
@rule_id("drop_table")
58+
forbid (principal, action == Agent::Action::"execute_bash", resource)
59+
when { context.command like "*DROP TABLE*" };

agent/policies/soft_deny.cedar

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Base catch-all permit. Without it, cedarpy's default-deny would turn
2+
// every non-matching Cedar evaluation on this tier into a DENY decision,
3+
// making the soft tier indistinguishable from hard-deny. With it, Cedar
4+
// returns ALLOW (no matching forbid) and our engine's STEP 3 sees only
5+
// the genuine forbid hits as REQUIRE_APPROVAL.
6+
@rule_id("base_permit")
7+
permit (principal, action, resource);
8+
9+
// Built-in soft-deny policy set for Cedar HITL engine.
10+
//
11+
// Soft-deny is the HUMAN-IN-THE-LOOP surface: matching rules pause the
12+
// tool call, write an approval request to DynamoDB, and await a human
13+
// response via `bgagent approve` / `bgagent deny`. See
14+
// docs/design/CEDAR_HITL_GATES.md §§2, 6, 15.4.
15+
//
16+
// Every rule in this file MUST carry:
17+
// @tier("soft")
18+
// @rule_id("...") — stable ID for --pre-approve rule:X
19+
// @approval_timeout_s — integer seconds >= 30 (<120 emits WARN per IMPL-25)
20+
// @severity — "low" | "medium" | "high"
21+
// @category — optional free-form UX grouping
22+
//
23+
// Blueprints may OPT OUT of specific rules here via
24+
// `security.cedarPolicies.disable: [rule_id]`. They may NOT disable any
25+
// rule in hard_deny.cedar (blueprint loader rejects those at task start).
26+
27+
// Gate any git --force / -f push. 300s default approval window, medium severity.
28+
// Covers both long-form (--force) and short-form (-f) variants, including
29+
// the bare `git push -f` invocation with no branch argument.
30+
@tier("soft")
31+
@rule_id("force_push_any")
32+
@approval_timeout_s("300")
33+
@severity("medium")
34+
@category("destructive")
35+
forbid (principal, action == Agent::Action::"execute_bash", resource)
36+
when { context.command like "*git push --force*"
37+
|| context.command like "*git push -f *"
38+
|| context.command like "*git push -f" };
39+
40+
// Force-push to main/prod specifically — longer window, higher severity.
41+
// Multi-match with force_push_any is expected: the engine's annotation
42+
// merging picks min(300, 600)=300s and max(medium, high)=high.
43+
@tier("soft")
44+
@rule_id("force_push_main")
45+
@approval_timeout_s("600")
46+
@severity("high")
47+
@category("destructive")
48+
forbid (principal, action == Agent::Action::"execute_bash", resource)
49+
when { context.command like "*git push --force origin main*"
50+
|| context.command like "*git push --force origin prod*"
51+
|| context.command like "*git push -f origin main*"
52+
|| context.command like "*git push -f origin prod*" };
53+
54+
// Non-force pushes to protected branches — catches the case where an
55+
// agent bypasses PR workflow by pushing directly.
56+
@tier("soft")
57+
@rule_id("push_to_protected_branch")
58+
@approval_timeout_s("300")
59+
@severity("medium")
60+
@category("destructive")
61+
forbid (principal, action == Agent::Action::"execute_bash", resource)
62+
when { context.command like "*git push origin main*"
63+
|| context.command like "*git push origin master*"
64+
|| context.command like "*git push origin prod*"
65+
|| context.command like "*git push origin release/*" };
66+
67+
// Writes to `.env` files typically contain secrets. 600s window, high severity.
68+
@tier("soft")
69+
@rule_id("write_env_files")
70+
@approval_timeout_s("600")
71+
@severity("high")
72+
@category("filesystem")
73+
forbid (principal, action == Agent::Action::"write_file", resource)
74+
when { context.file_path like "*.env" };
75+
76+
// Writes to any path containing "credentials" — SSH keys, AWS creds,
77+
// service-account JSON, etc. 300s window, high severity.
78+
@tier("soft")
79+
@rule_id("write_credentials")
80+
@approval_timeout_s("300")
81+
@severity("high")
82+
@category("auth")
83+
forbid (principal, action == Agent::Action::"write_file", resource)
84+
when { context.file_path like "*credentials*" };

agent/pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ dependencies = [
1111
"uvicorn==0.47.0", #https://pypi.org/project/uvicorn/
1212
"aws-opentelemetry-distro==0.17.0", #https://pypi.org/project/aws-opentelemetry-distro/
1313
"mcp==1.27.1", #https://pypi.org/project/mcp/
14-
"cedarpy==4.8.3", #https://github.com/k9securityio/cedar-py
14+
# CEDAR ENGINE PARITY — DO NOT BUMP IN ISOLATION.
15+
# cedarpy (Python, agent runtime) and @cedar-policy/cedar-wasm (TypeScript,
16+
# CDK Lambdas) are two language bindings over the same Cedar Rust core.
17+
# Even patch-version drift between the bindings can produce divergent
18+
# (decision, matching_rule_ids) on the same (policy, input) — a class
19+
# of bug invisible to per-side unit tests. The contracts/cedar-parity/
20+
# golden fixtures are how CI catches divergence; if you bump cedarpy
21+
# you MUST bump @cedar-policy/cedar-wasm to a tested-compatible version
22+
# in cdk/package.json AND refresh the parity fixtures, in the same
23+
# commit. See docs/design/CEDAR_HITL_GATES.md §15.6 (decision #23) and
24+
# the parity-contract banner in mise.toml.
25+
"cedarpy==4.8.0", #https://github.com/k9securityio/cedar-py — EXACT pin (no ^/~), parity with @cedar-policy/cedar-wasm@4.10.0
1526
]
1627

1728
[tool.bandit]

agent/src/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ def build_config(
110110
channel_metadata: dict[str, str] | None = None,
111111
trace: bool = False,
112112
user_id: str = "",
113+
approval_timeout_s: int | None = None,
114+
initial_approvals: list[str] | None = None,
115+
initial_approval_gate_count: int = 0,
116+
approval_gate_cap: int | None = None,
113117
) -> TaskConfig:
114118
"""Build and validate configuration from explicit parameters.
115119
@@ -164,6 +168,10 @@ def build_config(
164168
channel_metadata=channel_metadata or {},
165169
trace=trace,
166170
user_id=user_id,
171+
approval_timeout_s=approval_timeout_s,
172+
initial_approvals=initial_approvals or [],
173+
initial_approval_gate_count=initial_approval_gate_count,
174+
approval_gate_cap=approval_gate_cap,
167175
)
168176

169177

0 commit comments

Comments
 (0)