Skip to content

Commit 30b0057

Browse files
authored
Merge branch 'main' into feat/117-dlq-cloudwatch-alarms
2 parents 1d63050 + 9bdbf14 commit 30b0057

11 files changed

Lines changed: 92 additions & 29 deletions

File tree

.github/workflows/build.yml

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/src/hooks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import task_state
3131
from nudge_reader import _xml_escape
3232
from output_scanner import scan_tool_output
33-
from policy import APPROVAL_RATE_LIMIT, Outcome
33+
from policy import APPROVAL_RATE_LIMIT, FLOOR_TIMEOUT_S, Outcome
3434
from progress_writer import _generate_ulid
3535
from shell import log, log_error_cw
3636

@@ -42,7 +42,7 @@
4242
# Chunk 3 constants (§6.5 pseudocode)
4343
# ---------------------------------------------------------------------------
4444

45-
FLOOR_30S: int = 30 # §6 decision #6: approval floor on effective timeout
45+
FLOOR_30S: int = FLOOR_TIMEOUT_S # §6 decision #6: sourced from contracts/constants.json
4646
CLEANUP_MARGIN_120S: int = 120 # §6.5 lifetime-margin reserve for cleanup
4747
# Poll cadence per §3 decision #3 and IMPL-12: 2s for the first 30s, 5s
4848
# thereafter. Exact counts vary with ``timeout_s``; these pin the

agent/src/policy.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,7 @@
7575
# Constants (§3, §5.2, §12.9)
7676
# ---------------------------------------------------------------------------
7777

78-
FLOOR_TIMEOUT_S: int = 30 # §6 decision #6: rejected below this at load
7978
WARN_TIMEOUT_S: int = 120 # IMPL-25: sub-120s emits WARN on blueprint load
80-
DEFAULT_TASK_TIMEOUT_S: int = 300 # §6 decision #6 default
8179

8280

8381
def _load_shared_constants() -> dict:
@@ -109,6 +107,9 @@ def _load_shared_constants() -> dict:
109107
DEFAULT_APPROVAL_GATE_CAP: int = int(_AGC["default"]) # decision #13 default
110108
APPROVAL_GATE_CAP_MIN: int = int(_AGC["min"])
111109
APPROVAL_GATE_CAP_MAX: int = int(_AGC["max"])
110+
_ATS = _SHARED_CONSTANTS["approval_timeout_s"]
111+
FLOOR_TIMEOUT_S: int = int(_ATS["min"]) # §6 decision #6: rejected below this at load
112+
DEFAULT_TASK_TIMEOUT_S: int = int(_ATS["default"]) # §6 decision #6 default
112113
CACHE_MAX_ENTRIES: int = 50 # §12.9: decoupled from approvalGateCap
113114
CACHE_TTL_S: float = 60.0 # §12.8 sliding-window TTL on DENIED/TIMED_OUT
114115
POLICIES_MAX_BYTES: int = 64 * 1024 # finding #12: reject blueprints > 64 KB

cdk/mise.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ run = "yarn eslint"
1717

1818
[tasks.test]
1919
description = "Jest tests"
20+
depends = [":compile"]
2021
run = "yarn test"
2122

2223
[tasks.synth]
@@ -25,6 +26,7 @@ run = "yarn synth"
2526

2627
[tasks."synth:quiet"]
2728
description = "cdk synth (quiet)"
29+
depends = [":compile"]
2830
run = "yarn synth:quiet"
2931

3032
[tasks.deploy]
@@ -54,5 +56,5 @@ description = "No-op bundle step for cdk/cdk.json (NodejsFunction bundles at syn
5456
run = "node -e \"process.exit(0)\""
5557

5658
[tasks.build]
57-
description = "compile, test, eslint, synth (quiet)"
58-
depends = [":compile", ":test", ":eslint", ":synth:quiet"]
59+
description = "compile, test, eslint, synth (quiet) — DAG: compile first, then test+synth+eslint in parallel"
60+
depends = [":test", ":eslint", ":synth:quiet"]

cdk/src/constructs/user-concurrency-table.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export interface UserConcurrencyTableProps {
3636
* @default RemovalPolicy.DESTROY
3737
*/
3838
readonly removalPolicy?: RemovalPolicy;
39+
40+
/**
41+
* Whether to enable point-in-time recovery.
42+
* @default true
43+
*/
44+
readonly pointInTimeRecovery?: boolean;
3945
}
4046

4147
/**
@@ -44,9 +50,6 @@ export interface UserConcurrencyTableProps {
4450
* Schema: user_id (PK). Each item holds an atomic counter (active_count)
4551
* representing the number of currently running tasks for the user.
4652
* The application layer uses conditional updates for increment/decrement.
47-
*
48-
* No point-in-time recovery: this is transient counter data that can be
49-
* reconstructed from the Tasks table by a reconciliation process.
5053
*/
5154
export class UserConcurrencyTable extends Construct {
5255
/**
@@ -64,6 +67,9 @@ export class UserConcurrencyTable extends Construct {
6467
type: dynamodb.AttributeType.STRING,
6568
},
6669
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
70+
pointInTimeRecoverySpecification: {
71+
pointInTimeRecoveryEnabled: props.pointInTimeRecovery ?? true,
72+
},
6773
removalPolicy: props.removalPolicy ?? RemovalPolicy.DESTROY,
6874
});
6975
}

cdk/src/handlers/shared/types.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,15 +1022,18 @@ export const INITIAL_APPROVALS_MAX_ENTRIES = 20;
10221022
/** Maximum per-entry length for an `initial_approvals` scope string. */
10231023
export const INITIAL_APPROVALS_MAX_ENTRY_LENGTH = 128;
10241024

1025-
/** Floor for `approval_timeout_s` (§6 decision #6). */
1026-
export const APPROVAL_TIMEOUT_S_MIN = 30;
1025+
/** Floor for `approval_timeout_s` (§6 decision #6).
1026+
* Sourced from ``contracts/constants.json`` (S9). */
1027+
export const APPROVAL_TIMEOUT_S_MIN = sharedConstants.approval_timeout_s.min;
10271028

10281029
/** Absolute ceiling for `approval_timeout_s` before the
1029-
* `maxLifetime - 300` clip is applied (§7.3). */
1030-
export const APPROVAL_TIMEOUT_S_MAX = 3600;
1030+
* `maxLifetime - 300` clip is applied (§7.3).
1031+
* Sourced from ``contracts/constants.json`` (S9). */
1032+
export const APPROVAL_TIMEOUT_S_MAX = sharedConstants.approval_timeout_s.max;
10311033

1032-
/** Default `approval_timeout_s` when the submit payload omits it. */
1033-
export const APPROVAL_TIMEOUT_S_DEFAULT = 300;
1034+
/** Default `approval_timeout_s` when the submit payload omits it.
1035+
* Sourced from ``contracts/constants.json`` (S9). */
1036+
export const APPROVAL_TIMEOUT_S_DEFAULT = sharedConstants.approval_timeout_s.default;
10341037

10351038
/**
10361039
* Cedar HITL: bounds + platform default for the per-task approval-gate cap

cdk/test/constructs/user-concurrency-table.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ describe('UserConcurrencyTable', () => {
4545
});
4646
});
4747

48-
test('does not enable point-in-time recovery', () => {
48+
test('enables point-in-time recovery by default', () => {
4949
template.hasResourceProperties('AWS::DynamoDB::Table', {
50-
PointInTimeRecoverySpecification: Match.absent(),
50+
PointInTimeRecoverySpecification: {
51+
PointInTimeRecoveryEnabled: true,
52+
},
5153
});
5254
});
5355

@@ -96,4 +98,17 @@ describe('UserConcurrencyTable with custom props', () => {
9698
UpdateReplacePolicy: 'Retain',
9799
});
98100
});
101+
102+
test('allows disabling point-in-time recovery', () => {
103+
const app = new App();
104+
const stack = new Stack(app, 'TestStack');
105+
new UserConcurrencyTable(stack, 'UserConcurrencyTable', { pointInTimeRecovery: false });
106+
const template = Template.fromStack(stack);
107+
108+
template.hasResourceProperties('AWS::DynamoDB::Table', {
109+
PointInTimeRecoverySpecification: {
110+
PointInTimeRecoveryEnabled: false,
111+
},
112+
});
113+
});
99114
});

contracts/constants.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,10 @@
33
"min": 1,
44
"max": 500,
55
"default": 50
6+
},
7+
"approval_timeout_s": {
8+
"min": 30,
9+
"max": 3600,
10+
"default": 300
611
}
712
}

contracts/constants.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ JSON at TypeScript compile time via `resolveJsonModule`.
3636
"min": 1,
3737
"max": 500,
3838
"default": 50
39+
},
40+
"approval_timeout_s": {
41+
"min": 30,
42+
"max": 3600,
43+
"default": 300
3944
}
4045
}
4146
```
@@ -49,6 +54,14 @@ JSON at TypeScript compile time via `resolveJsonModule`.
4954
- **`approval_gate_cap.default`** — value applied when a blueprint omits
5055
the field. 50 is the design-decision default (see
5156
`docs/design/CEDAR_HITL_GATES.md` decision #13).
57+
- **`approval_timeout_s.min`** — floor for `approval_timeout_s` (§6
58+
decision #6). 30 seconds — below this, humans cannot realistically
59+
respond to an approval prompt.
60+
- **`approval_timeout_s.max`** — absolute ceiling for `approval_timeout_s`
61+
before the `maxLifetime - 300` clip is applied (§7.3). 3600 seconds
62+
(1 hour).
63+
- **`approval_timeout_s.default`** — value applied when the submit payload
64+
omits `approval_timeout_s`. 300 seconds (5 minutes) per §6 decision #6.
5265

5366
## Adding new constants
5467

mise.toml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,11 @@ run = [
151151
##### BUILD #####
152152
##################
153153

154-
# Agent code is packaged by CDK (Docker asset); validate Python before heavy JS/CDK work.
155-
# Multiple `depends` run in parallel, so agent is a sole prerequisite, then packages run in order.
154+
# All packages run in parallel via DAG. Each resolves its own internal dependencies.
155+
# Agent quality runs alongside CDK/CLI/docs — no sequential bottleneck.
156156
[tasks.build]
157-
description = "Monorepo build (agent quality, then cdk, cli, docs)"
158-
depends = ["//agent:quality"]
159-
run = [
160-
"MISE_EXPERIMENTAL=1 mise //cdk:build",
161-
"MISE_EXPERIMENTAL=1 mise //cli:build",
162-
"MISE_EXPERIMENTAL=1 mise //docs:build",
163-
]
157+
description = "Monorepo build (agent, cdk, cli, docs in parallel)"
158+
depends = ["//agent:quality", "//cdk:build", "//cli:build", "//docs:build"]
164159

165160
[tasks.default]
166161
description = "Install + build"

0 commit comments

Comments
 (0)