Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
52b9b4c
feat(carve S2): ECS rightsized planning task def + configurable task …
isadeks Jul 24, 2026
d469bc2
fix(carve S2): de-opinionate the build task — modest default, overrid…
isadeks Jul 27, 2026
75379a9
fix(carve S2): make the sizing knobs actually reachable, and refuse p…
isadeks Jul 28, 2026
57a1ed3
fix(carve S2): give the build task real disk margin — 21 → 50 GiB
isadeks Jul 28, 2026
81155bb
fix(carve S2): make the planning task def reachable in the slice that…
isadeks Jul 28, 2026
f9d16ac
docs(carve S2): describe artifact workflows by their contract, not by…
isadeks Jul 29, 2026
3a38156
feat(carve S3): the agent runtime (Linear no-MCP, attachments, build/…
isadeks Jul 24, 2026
ce47f5d
test: tolerate either frozenset layout when cross-checking the writea…
isadeks Jul 27, 2026
206a3b6
fix(carve S3): keep the Jira app-identity work when applying the agen…
isadeks Jul 27, 2026
566dc3e
fix(carve S3): review findings — close a clone-guard bypass, grant th…
isadeks Jul 27, 2026
88f27ef
fix(carve S3): the clone-guard rewrite broke two things it was meant …
isadeks Jul 28, 2026
7568743
fix(carve S3): report a signal-killed build as infra, not as broken code
isadeks Jul 28, 2026
b34b07f
refactor(carve S3): drop the decomposition workflow from the main-bou…
isadeks Jul 28, 2026
698a6ce
feat(carve S4): Linear issue-context surface — attachments, PDF/image…
isadeks Jul 27, 2026
d4e0cb3
fix(carve S4): review findings — two attachment defects, and stop shi…
isadeks Jul 27, 2026
2f98e44
fix(carve S4): bound the scan input, not just the label — the label b…
isadeks Jul 28, 2026
4e165c5
refactor(carve S4): drop the decomposition config fields from the tab…
isadeks Jul 28, 2026
fbf6516
refactor(carve S4): drop the CLI flags for a feature main will not have
isadeks Jul 29, 2026
7ca5ef0
fix(carve S4): give pdf-parse its own copy so the caller can still up…
isadeks Jul 29, 2026
55ece46
feat(carve S5): orchestration DAG core, row store, and the channel ab…
isadeks Jul 27, 2026
a6913c6
fix(carve S5): keep no-shadow enforced in tests rather than relaxing …
isadeks Jul 27, 2026
8633acf
docs(carve S5): fix comments that describe code this slice does not c…
isadeks Jul 28, 2026
157d3f1
feat(carve S6): auto-decomposition planning and iteration feedback (d…
isadeks Jul 27, 2026
5163733
fix(carve S6): review findings — explain the guardrail exception, fix…
isadeks Jul 27, 2026
ac523da
fix(carve S6): the regex fix over-corrected, and the guardrail commen…
isadeks Jul 28, 2026
e48273b
refactor(carve S6): reduce this slice to the iteration work, without …
isadeks Jul 29, 2026
d9047fa
feat(carve S7): the orchestration compute plane — reconciler, release…
isadeks Jul 27, 2026
8c1e579
fix(carve S7): the retry hint must name the project's own trigger label
isadeks Jul 27, 2026
842f89b
fix(carve S7): actually wire the project's trigger label to the retry…
isadeks Jul 28, 2026
78a6053
fix(carve S7): populate the trigger label instead of always rendering…
isadeks Jul 28, 2026
6a43bdd
refactor(carve S7): remove graph CREATION from the reconciler, keep g…
isadeks Jul 29, 2026
3de6a54
fix(carve S7): show the parent panel a preview even when there is no …
isadeks Jul 29, 2026
19acf10
fix(carve S7): pin the coding workflow when releasing an orchestratio…
isadeks Jul 29, 2026
30e7c13
fix(carve S7): give every child the epic's shared contract, and let a…
isadeks Jul 29, 2026
e8aa93c
fix(carve S7): state the shared context descriptively so the guardrai…
isadeks Jul 29, 2026
08702e2
feat(carve S8): wire the orchestration arc into the stack and activat…
isadeks Jul 27, 2026
e53ce5c
fix(carve S8): review findings — restore Jira fan-out, scope the S3 g…
isadeks Jul 27, 2026
5ec39f7
fix(carve S8): delete the log-delivery pin shim instead of gating it
isadeks Jul 27, 2026
e122e5a
Revert "fix(carve S8): delete the log-delivery pin shim instead of ga…
isadeks Jul 27, 2026
74b2390
test(carve S8): scope the fan-out OAuth-grant assertion to its own po…
isadeks Jul 28, 2026
98ea75e
fix(carve S8): the artifact-prefix check needs to reject traversal, a…
isadeks Jul 28, 2026
e0e0c4e
fix(carve S8): the CLI advertised a default model the runtime no long…
isadeks Jul 28, 2026
5c3ffaf
docs(compute): the build-sizing figures now match the shipped default
isadeks Jul 28, 2026
b7583d2
fix(carve S8): stop reporting failures as successes on the paths this…
isadeks Jul 28, 2026
dc224d4
refactor(carve S8): remove the plan-proposal surface from the webhook
isadeks Jul 29, 2026
8353ec6
docs(carve S8): stop documenting a feature main will not have
isadeks Jul 29, 2026
ad8d7b3
fix(carve S8): drop the reconciler's now-unused S3 grants
isadeks Jul 29, 2026
8b6eb1f
refactor(carve S8): remove three exports the strip left with no consumer
isadeks Jul 29, 2026
f6f2204
fix(carve S8): let a parent comment fix the combined result, and stop…
isadeks Jul 29, 2026
4b730ad
Merge the merged carve base into the orchestration-plane slice
isadeks Jul 29, 2026
3ac65d7
fix(carve S7): bound and paginate the iteration cost sum, and derive …
isadeks Jul 29, 2026
d54ce65
Merge the compute-plane slice into the activation slice
isadeks Jul 29, 2026
f63c440
fix(carve S8): mark a running cost total that is known to be incomplete
isadeks Jul 29, 2026
e7ddc01
style(carve S7): order the new test import as the linter does
isadeks Jul 29, 2026
e68b18a
Merge remote-tracking branch 'upstream/carve/s7-orchestration-plane' …
isadeks Jul 29, 2026
25fea2d
Merge the merged carve base into the activation slice
isadeks Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ description = "Test fixture signing secret in Slack verification unit test (not
stopwords = ["test-signing-secret-abc123"]

[[allowlists]]
# Test idempotency-key fixture in orchestration-release.test.ts (not a real
# credential). Suppressed by stopword rather than a .gitleaksignore commit-SHA
# fingerprint: the finding lives in history and SHA fingerprints break whenever
# history is rewritten (rebases / dep-bump merges), which is exactly how #530's
# baseline regressed. A stopword is SHA-independent. See #537 (regression of #530).
# Test idempotency-key fixture in orchestration tests (not a real credential).
# Suppressed by stopword rather than a .gitleaksignore commit-SHA fingerprint:
# the finding lives in history and SHA fingerprints break whenever history is
# rewritten (rebases / dep-bump merges), which is exactly how #530's baseline
# regressed. A stopword is SHA-independent. See #537 (regression of #530).
description = "Test idempotency-key fixture 'orch_abc_SUB-1' (not a real credential)."
stopwords = ["orch_abc_SUB-1"]

Expand Down
2 changes: 1 addition & 1 deletion agent/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ def get_config() -> TaskConfig:
issue_number=os.environ.get("ISSUE_NUMBER", ""),
github_token=os.environ.get("GITHUB_TOKEN", ""),
anthropic_model=os.environ.get("ANTHROPIC_MODEL", ""),
max_turns=int(os.environ.get("MAX_TURNS", "200")),
max_turns=int(os.environ.get("MAX_TURNS", "100")),
max_budget_usd=float(os.environ.get("MAX_BUDGET_USD", "0")) or None,
aws_region=os.environ.get("AWS_REGION", ""),
dry_run=os.environ.get("DRY_RUN", "").lower() in ("1", "true", "yes"),
Expand Down
2 changes: 1 addition & 1 deletion agent/src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ def _extract_invocation_params(inp: dict, request: Request) -> dict:
# #1: per-repo build/lint verification commands. Empty → agent defaults to mise.
build_command = inp.get("build_command", "")
lint_command = inp.get("lint_command", "")
max_turns = int(inp.get("max_turns", 0)) or int(os.environ.get("MAX_TURNS", "200"))
max_turns = int(inp.get("max_turns", 0)) or int(os.environ.get("MAX_TURNS", "100"))
max_budget_usd = float(inp.get("max_budget_usd", 0)) or None
aws_region = inp.get("aws_region") or os.environ.get("AWS_REGION", "")
task_id = inp.get("task_id", "")
Expand Down
40 changes: 40 additions & 0 deletions cdk/mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ description = "Jest tests"
depends = [":compile"]
run = ["mkdir -p $TMPDIR", "yarn test"]

# Focused, low-footprint test run for iterating on one file/pattern:
# mise //cdk:testf -- orchestration-release
# Skips coverage (the heaviest phase) and runs a single worker, so it
# won't spawn the worker fleet that OOMs the Mac on the 1240-test
# stack-synth suite. Use //cdk:test for the full coverage run (CI parity).
[tasks.testf]
description = "Focused jest run (no coverage, single worker)"
run = "yarn jest --coverage=false --runInBand"

[tasks.synth]
description = "cdk synth"
run = ["mkdir -p $TMPDIR", "yarn synth"]
Expand All @@ -36,8 +45,39 @@ description = "cdk synth (quiet)"
depends = [":compile"]
run = ["mkdir -p $TMPDIR", "yarn synth:quiet"]

# Reclaim regenerable build artifacts that otherwise grow unbounded and fill the
# disk (observed in practice: a deploy died with ENOSPC — the uv cache had grown
# to 51G and Docker.raw to 112G). ALWAYS does the cheap, safe, Docker-free cleanups (stale
# cdk.out + $TMPDIR, both fully regenerated by the next build). The expensive
# uv/Docker prunes are GATED on low free disk (< MIN_FREE_GB) — running them on
# every deploy is both wasteful and risky (concurrent/looping `docker prune`
# helped wedge the daemon once). Gated prunes are best-effort (|| true)
# and run sequentially so they never pile up. Standalone: `mise //cdk:clean:disk`
# (set MIN_FREE_GB=999 to force a prune); also runs before `mise //cdk:deploy`.
[tasks."clean:disk"]
description = "Reclaim disk: stale cdk.out/$TMPDIR always; uv+docker prune only when free disk is low"
run = '''
rm -rf cdk.out || true
rm -rf "$TMPDIR"/* 2>/dev/null || true
MIN_FREE_GB="${MIN_FREE_GB:-25}"
FREE_GB=$(df -g / | awk 'NR==2 {print $4}')
echo "clean:disk — ${FREE_GB}G free (threshold ${MIN_FREE_GB}G)"
if [ "${FREE_GB:-999}" -lt "$MIN_FREE_GB" ]; then
echo "clean:disk — low disk, pruning uv + docker caches…"
uv cache prune || true
docker image prune -f || true
docker builder prune -f || true
df -h / | tail -1
else
echo "clean:disk — enough free disk, skipping uv/docker prune"
fi
'''

[tasks.deploy]
description = "cdk deploy (pass args after --)"
# Reclaim disk first — the agent-image Docker build + CDK asset bundling need
# several GB of working space, and uv/Docker caches accumulate across runs.
depends = [":clean:disk"]
run = "npx cdk deploy"

# Bootstraps with ComputeTypes=agentcore (the template default). To ALSO enable the
Expand Down
4 changes: 3 additions & 1 deletion cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"compile": "tsc --build tsconfig.json",
"watch": "tsc --build -w tsconfig.json",
"test": "jest",
"test": "jest --maxWorkers=${JEST_MAX_WORKERS:-25%}",
"eslint": "eslint --fix src test",
"synth": "npx cdk synth",
"synth:quiet": "npx cdk synth -q"
Expand Down Expand Up @@ -77,6 +77,8 @@
"<rootDir>/@(src|test)/**/*(*.)@(spec|test).ts?(x)",
"<rootDir>/@(src|test)/**/__tests__/**/*.ts?(x)"
],
"maxWorkers": "25%",
"workerIdleMemoryLimit": "1536MB",
"clearMocks": true,
"collectCoverage": true,
"coverageReporters": [
Expand Down
2 changes: 1 addition & 1 deletion cdk/src/constructs/bedrock-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Node } from 'constructs';
* runtime may invoke. Both grant sites — the AgentCore runtime in
* `stacks/agent.ts` and the ECS task role in `constructs/ecs-agent-cluster.ts`
* — derive their `grantInvoke` / IAM ARNs from this one list, so the two
* backends can never drift (they were previously two hand-synced arrays; #433).
* backends can never drift (they were previously two hand-synced arrays).
*
* Scoping is intentionally per-model (explicit foundation-model +
* cross-Region inference-profile ARNs), NOT a `Resource: '*'` wildcard — that
Expand Down
33 changes: 33 additions & 0 deletions cdk/src/constructs/blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ export interface BlueprintProps {
* Override the default poll interval (ms) for awaiting agent completion.
*/
readonly pollIntervalMs?: number;

/**
* Command the agent runs to BUILD/verify the repo before opening a PR
* (and as the pre-change baseline). Drives build-regression gating: if
* the repo built green before the agent's change and fails after, the
* task fails. Defaults to ``mise run build`` when unset.
*
* Set this for repos that do NOT use mise (e.g. ``'npm run build'``,
* ``'gradle build'``, ``'make'``). Without a runnable build command,
* build-regression gating is INERT — a change that breaks the build
* still reports success (the agent emits a one-time warning on the PR).
* Runs in the agent's cloud container against the cloned repo; this is a
* compile/test verification, NOT a deployment.
*/
readonly buildCommand?: string;

/**
* Command the agent runs to LINT the repo (advisory gate). Defaults to
* ``mise run lint`` when unset. Same semantics as ``buildCommand``.
*/
readonly lintCommand?: string;
};

/**
Expand Down Expand Up @@ -239,6 +260,12 @@ export class Blueprint extends Construct {
if (props.pipeline?.pollIntervalMs !== undefined) {
item.poll_interval_ms = { N: String(props.pipeline.pollIntervalMs) };
}
if (props.pipeline?.buildCommand) {
item.build_command = { S: props.pipeline.buildCommand };
}
if (props.pipeline?.lintCommand) {
item.lint_command = { S: props.pipeline.lintCommand };
}
if (this.egressAllowlist.length > 0) {
item.egress_allowlist = { L: this.egressAllowlist.map(d => ({ S: d })) };
}
Expand Down Expand Up @@ -317,6 +344,8 @@ export class Blueprint extends Construct {
if (props.agent?.systemPromptOverrides) fields.push(', #system_prompt_overrides = :system_prompt_overrides');
if (props.credentials?.githubTokenSecretArn) fields.push(', #github_token_secret_arn = :github_token_secret_arn');
if (props.pipeline?.pollIntervalMs !== undefined) fields.push(', #poll_interval_ms = :poll_interval_ms');
if (props.pipeline?.buildCommand) fields.push(', #build_command = :build_command');
if (props.pipeline?.lintCommand) fields.push(', #lint_command = :lint_command');
if (this.egressAllowlist.length > 0) fields.push(', #egress_allowlist = :egress_allowlist');
if (this.cedarPolicies.length > 0) fields.push(', #cedar_policies = :cedar_policies');
if (this.approvalGateCap !== undefined) fields.push(', #approval_gate_cap = :approval_gate_cap');
Expand All @@ -332,6 +361,8 @@ export class Blueprint extends Construct {
if (props.agent?.systemPromptOverrides) names['#system_prompt_overrides'] = 'system_prompt_overrides';
if (props.credentials?.githubTokenSecretArn) names['#github_token_secret_arn'] = 'github_token_secret_arn';
if (props.pipeline?.pollIntervalMs !== undefined) names['#poll_interval_ms'] = 'poll_interval_ms';
if (props.pipeline?.buildCommand) names['#build_command'] = 'build_command';
if (props.pipeline?.lintCommand) names['#lint_command'] = 'lint_command';
if (this.egressAllowlist.length > 0) names['#egress_allowlist'] = 'egress_allowlist';
if (this.cedarPolicies.length > 0) names['#cedar_policies'] = 'cedar_policies';
if (this.approvalGateCap !== undefined) names['#approval_gate_cap'] = 'approval_gate_cap';
Expand All @@ -347,6 +378,8 @@ export class Blueprint extends Construct {
if (props.agent?.systemPromptOverrides) values[':system_prompt_overrides'] = { S: props.agent.systemPromptOverrides };
if (props.credentials?.githubTokenSecretArn) values[':github_token_secret_arn'] = { S: props.credentials.githubTokenSecretArn };
if (props.pipeline?.pollIntervalMs !== undefined) values[':poll_interval_ms'] = { N: String(props.pipeline.pollIntervalMs) };
if (props.pipeline?.buildCommand) values[':build_command'] = { S: props.pipeline.buildCommand };
if (props.pipeline?.lintCommand) values[':lint_command'] = { S: props.pipeline.lintCommand };
if (this.egressAllowlist.length > 0) values[':egress_allowlist'] = { L: this.egressAllowlist.map(d => ({ S: d })) };
if (this.cedarPolicies.length > 0) values[':cedar_policies'] = { L: this.cedarPolicies.map(p => ({ S: p })) };
if (this.approvalGateCap !== undefined) values[':approval_gate_cap'] = { N: String(this.approvalGateCap) };
Expand Down
12 changes: 11 additions & 1 deletion cdk/src/constructs/jira-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ export class JiraIntegration extends Construct {
const commonBundling: lambda.BundlingOptions = {
externalModules: ['@aws-sdk/*'],
};
// pdf-parse (v2, pdfjs-based) can't be esbuild-bundled — its pdfjs/native
// (@napi-rs/canvas) deps break at import (`DOMMatrix is not defined`,
// Ship it unbundled via `nodeModules` so it resolves natively at
// runtime. Mirrors TaskApi's attachment-screening bundling. Jira's #619
// attachment path screens PDFs, so its webhook processor needs the carve-out.
const attachmentScreeningBundling: lambda.BundlingOptions = {
...commonBundling,
nodeModules: ['pdf-parse'],
};

// --- Task creation environment (matches LinearIntegration / SlackIntegration pattern) ---
const createTaskEnv: Record<string, string> = {
Expand Down Expand Up @@ -258,7 +267,8 @@ export class JiraIntegration extends Construct {
JIRA_USER_MAPPING_TABLE_NAME: this.userMappingTable.tableName,
JIRA_WORKSPACE_REGISTRY_TABLE_NAME: this.workspaceRegistryTable.tableName,
},
bundling: commonBundling,
// Uses the PDF attachment-screening path (#619) — pdf-parse must stay unbundled.
bundling: attachmentScreeningBundling,
});
this.projectMappingTable.grantReadData(webhookProcessorFn);
this.userMappingTable.grantReadData(webhookProcessorFn);
Expand Down
Loading
Loading