Summary
Follow-up from PR #204 code review comment. The shared constants loaded from contracts/constants.json in agent/src/policy.py are parsed as integers but never validated for semantic correctness (invariants like min > 0, default >= min, max >= default). If constants.json is corrupted (e.g., bad merge sets "min": 0), the floor clamp becomes ineffective — a security-relevant silent misbehavior since approval_timeout_s.min governs how long humans have to review approval prompts.
Problem
policy.py currently does:
_ATS = _SHARED_CONSTANTS["approval_timeout_s"]
FLOOR_TIMEOUT_S: int = int(_ATS["min"])
DEFAULT_TASK_TIMEOUT_S: int = int(_ATS["default"])
No assertions enforce that these values are logically consistent. The same weakness exists for approval_gate_cap.
Proposed change
Add post-load assertions in agent/src/policy.py immediately after parsing each constant group from contracts/constants.json:
For approval_timeout_s:
if FLOOR_TIMEOUT_S <= 0:
raise ValueError(f"contracts/constants.json: approval_timeout_s.min must be > 0, got {FLOOR_TIMEOUT_S}")
if DEFAULT_TASK_TIMEOUT_S < FLOOR_TIMEOUT_S:
raise ValueError(f"contracts/constants.json: approval_timeout_s.default ({DEFAULT_TASK_TIMEOUT_S}) must be >= min ({FLOOR_TIMEOUT_S})")
if APPROVAL_TIMEOUT_S_MAX < DEFAULT_TASK_TIMEOUT_S:
raise ValueError(f"contracts/constants.json: approval_timeout_s.max ({APPROVAL_TIMEOUT_S_MAX}) must be >= default ({DEFAULT_TASK_TIMEOUT_S})")
For approval_gate_cap:
if APPROVAL_GATE_CAP_MIN <= 0:
raise ValueError(f"contracts/constants.json: approval_gate_cap.min must be > 0, got {APPROVAL_GATE_CAP_MIN}")
if DEFAULT_APPROVAL_GATE_CAP < APPROVAL_GATE_CAP_MIN:
raise ValueError(f"contracts/constants.json: approval_gate_cap.default ({DEFAULT_APPROVAL_GATE_CAP}) must be >= min ({APPROVAL_GATE_CAP_MIN})")
if APPROVAL_GATE_CAP_MAX < DEFAULT_APPROVAL_GATE_CAP:
raise ValueError(f"contracts/constants.json: approval_gate_cap.max ({APPROVAL_GATE_CAP_MAX}) must be >= default ({DEFAULT_APPROVAL_GATE_CAP})")
Consider also mirroring equivalent validation in the TypeScript side (cdk/src/handlers/shared/types.ts) where sharedConstants is consumed, to fail at compile/synth time rather than runtime.
Acceptance criteria
Context
References
Summary
Follow-up from PR #204 code review comment. The shared constants loaded from
contracts/constants.jsoninagent/src/policy.pyare parsed as integers but never validated for semantic correctness (invariants likemin > 0,default >= min,max >= default). Ifconstants.jsonis corrupted (e.g., bad merge sets"min": 0), the floor clamp becomes ineffective — a security-relevant silent misbehavior sinceapproval_timeout_s.mingoverns how long humans have to review approval prompts.Problem
policy.pycurrently does:No assertions enforce that these values are logically consistent. The same weakness exists for
approval_gate_cap.Proposed change
Add post-load assertions in
agent/src/policy.pyimmediately after parsing each constant group fromcontracts/constants.json:For
approval_timeout_s:For
approval_gate_cap:Consider also mirroring equivalent validation in the TypeScript side (
cdk/src/handlers/shared/types.ts) wheresharedConstantsis consumed, to fail at compile/synth time rather than runtime.Acceptance criteria
agent/src/policy.pyraisesValueErrorat module load ifapproval_timeout_sinvariants are violated (min > 0,default >= min,max >= default)agent/src/policy.pyraisesValueErrorat module load ifapproval_gate_capinvariants are violated (same pattern)agent/tests/cover each invalid-constant scenario (corrupted JSON triggers the expected error)scripts/check-constants-sync.tsoptionally extended to validate invariants at CI time (belt-and-suspenders)mise //agent:qualitypassesContext
agent/src/policy.py,agent/tests/, optionallyscripts/check-constants-sync.tscontracts/constants.json,contracts/constants.mdapproval_timeout_s.minis the floor ensuring humans have adequate time to review agent approval prompts (§6 decision chore(deps): bump defu from 6.1.4 to 6.1.6 in /docs #6)References
docs/design/CEDAR_HITL_GATES.md§6 decision chore(deps): bump defu from 6.1.4 to 6.1.6 in /docs #6