Setting the file. One moment.
Test Unit Grouping · Agent Advisor · aws/agent-toolkit-for-aws · Skills Docs
ContentsBack to the top of the page Add Capabilities
81
Creating Amazon Aurora Db Cluster With Instances
104
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Creating API Gateway Stage
— line 114
This file
Number 23.77
Position 77 of 81
Type Python
Size 72 KB
Lines 1,167 scripts/ test_unit_grouping.py
Python · 1,167 lines · 72 KB
)
12 ASSEMBLE_MD = (pathlib.Path( __file__ ).parent.parent
13 / "references" / "phases" / "discover" / "discover-assemble.md" )
14
15
16 def _norm (p):
17 return re.sub( r " \s + " , " " , p.read_text())
18
19
20 def test_in_process_agents_merge ():
21 text = _norm( DISCOVER_MD )
22 assert "in-process" in text.lower()
23 assert re.search( r "in-process . {0,200} ONE unit" , text, re. IGNORECASE ), \
24 "in-process coupled agents must be stated to merge into one unit"
25
26
27 def test_coupling_mode_vocabulary ():
28 text = _norm( DISCOVER_MD )
29 for mode in [ "queue" , "api" , "a2a" , "none" ]:
30 assert f "` { mode } `" in text
31 assert re.search( r "in_process . {0,160} never" , text), \
32 "in_process must be stated to never survive grouping"
33
34
35 def test_workload_class_vocabulary ():
36 text = _norm( DISCOVER_MD )
37 for cls in [ "agent_session" , "batch" , "light_io" , "service" ]:
38 assert f "` { cls } `" in text
39
40
41 def test_confirmation_only_when_multiple ():
42 # Collapse invariant: a single-unit draft is recorded silently.
43 text = _norm( ASSEMBLE_MD )
44 assert re.search( r "ONLY when . {0,80} more than one unit | > \s * 1 unit" , text), \
45 "grouping confirmation must be gated on >1 unit"
46
47
48 CLARIFY_MD = (pathlib.Path( __file__ ).parent.parent
49 / "references" / "phases" / "clarify" / "clarify.md" )
50
51
52 def test_clarify_two_levels ():
53 text = _norm( CLARIFY_MD )
54 for dim in [ "ops_preference" , "existing_cluster" , "multi_cloud" , "platform_fit" ]:
55 assert f "` { dim } `" in text, f "system-level dim { dim } must be named"
56 assert re.search( r "system-level . {0,200} asked once" , text, re. IGNORECASE )
57 assert re.search( r "primary unit . {0,200} full" , text, re. IGNORECASE )
58 assert re.search( r "ONE batched delta question" , text), \
59 "non-primary units get exactly one batched delta question"
60 assert re.search( r "inherit . {0,120} primary" , text, re. IGNORECASE )
61
62
63 def test_scoring_loop_per_agent_unit ():
64 text = _norm( CLARIFY_MD )
65 assert re.search( r "once per `agent_session` unit" , text), \
66 "scoring must be stated to run once per agent-class unit"
67 assert "scoring.py is NOT modified" in text or "scoring.py unchanged" in text
68
69
70 def test_cross_phase_field_seams ():
71 """Whole-branch review found field-name forks at phase seams (M1-M4).
72 Pin the reader side to the producer's vocabulary."""
73 report = _norm(pathlib.Path( __file__ ).parent.parent
74 / "references" / "phases" / "generate" / "generate-report.md" )
75 plan = _norm(pathlib.Path( __file__ ).parent.parent
76 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
77 clarify = _norm( CLARIFY_MD )
78 # M2/M3: readers use the producer's names
79 assert "PLATFORM.decision" not in report and "PLATFORM.rationale" not in report
80 assert "platform.architecture" not in plan
81 assert "consolidated" in report and "consolidate |" not in report
82 design = _norm(pathlib.Path( __file__ ).parent.parent
83 / "references" / "phases" / "design" / "design.md" )
84 assert "consolidate |" not in design, "stale consolidate token in design.md"
85 # M1: clarify writes the legacy mirror
86 assert re.search( r "entry_point . {0,300} answers . {0,200} primary unit" , clarify, re. IGNORECASE ) or \
87 re.search( r "legacy . {0,120} mirror" , clarify, re. IGNORECASE )
88 # M4: plan injection uses real unit field names
89 assert "model_recommendation" in plan and "agentcore_services" in plan
90
91
92 DISCOVER = DISCOVER_MD # alias for readability
93
94
95 def test_temporal_detection_creates_units ():
96 text = _norm( DISCOVER_MD )
97 assert "`temporal_worker_poll`" in text, "temporal poll tier must be a unit class"
98 assert re.search( r "one \s + `temporal_worker_poll` \s + unit per ( worker fleet | task-queue group ) " ,
99 text, re. IGNORECASE )
100 assert re.search( r "Activity execution class . {0,200} ( agent_session | `agent_session` ) " ,
101 text, re. DOTALL ), "activity classes must map onto unit classes"
102 assert "decision-refs/temporal.md" in text, "classification vocabulary must defer to temporal.md"
103
104
105 def test_intake_routes_temporal_into_main_flow ():
106 intake = _norm(pathlib.Path( __file__ ).parent.parent
107 / "references" / "phases" / "intake" / "intake.md" )
108 assert "entry_point = temporal_worker" not in intake, \
109 "the temporal branch entry point is retired — temporal routes into the main flow"
110 assert re.search( r " [ Tt ] emporal signal" , intake), \
111 "intake still recognizes the temporal signal (as detection context, not a branch)"
112
113
114 def test_migration_plan_unit_correlation_overlay ():
115 plan = _norm(pathlib.Path( __file__ ).parent.parent
116 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
117 assert '"evidence"' in plan, "injection units must carry the evidence correlation key"
118 assert "advisor_unit" in plan and "advisor_target_runtime" in plan
119 assert re.search( r "additive . {0,200} never ( modify | remove ) " , plan, re. IGNORECASE | re. DOTALL ), \
120 "annotation must be declared additive-only"
121 assert re.search( r " [ Ss ] ingle-unit . {0,120} ( skip | SKIP ) " , plan), \
122 "collapse invariant: single-unit runs skip the overlay"
123
124
125 def test_migration_plan_validates_advisor_model_contract_without_reselection ():
126 plan = _norm(pathlib.Path( __file__ ).parent.parent
127 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
128
129 assert re.search( r "Validate the advisor model/path contract" , plan), \
130 "migration-plan must validate the advisor's per-workload model/path contract"
131 assert "advisor wins" in plan.lower(), \
132 "the advisor must remain the model-selection authority"
133 assert "plan_model_mismatch" in plan and "_halt_and_inform" in plan, \
134 "a plan mismatch must be visible and blocking"
135 assert re.search( r "do not rewrite `design \. json`" , plan, re. IGNORECASE ), \
136 "migration-plan must not silently replace the confirmed advisor decision"
137 assert "model-recommendation-input.json" in plan and \
138 "model_recommendation.py" in plan, \
139 "mismatch resolution must return to the deterministic Model Recommend phase"
140 frontmatter = plan.split( "---" , 2 )[ 1 ] if plan.count( "---" ) >= 2 else ""
141 assert re.search( r "model contract validation" , frontmatter, re. IGNORECASE ), \
142 "migration-plan postconditions must enforce contract validation"
143
144
145 def test_unit_trigger_vocabulary ():
146 text = _norm( DISCOVER_MD )
147 assert '"trigger"' in text or "`trigger`" in text
148 for v in [ "request" , "event" , "schedule" , "temporal" ]:
149 assert f "` { v } `" in text, f "trigger vocab missing { v } "
150
151
152 def test_activity_units_carry_task_queue_join_key ():
153 # Codex P1 #3: the diagram connected each fleet to EVERY activity (cartesian product)
154 # because activity units had no queue membership. Discover must give each Activity-class
155 # unit a task_queue field so a fleet connects only to the Activities on its queues[].
156 text = _norm( DISCOVER_MD )
157 assert re.search( r '"task_queue"' , text) and re.search( r "Activity-class unit" , text), \
158 "discover must give each Activity-class unit a task_queue field"
159 assert re.search( r "join key | never a cartesian product | never a cartesian" , text, re. IGNORECASE ), \
160 "discover must frame task_queue as the fleet<->activity join key (no cartesian product)"
161
162
163 def test_design_writes_effective_runtime_and_coupling_per_unit ():
164 # Codex round-2 #2/#5: design.json must carry per-unit effective_runtime (single source
165 # for every downstream deploy/cost/render target) and per-unit coupling (so the diagram
166 # wires only queue-coupled units). Consolidation lives in platform, effective_runtime on units.
167 design = _norm(pathlib.Path( __file__ ).parent.parent
168 / "references" / "phases" / "design" / "design.md" )
169 assert "effective_runtime" in design, "design.md must produce a per-unit effective_runtime"
170 assert re.search( r "consolidated . {0,80} platform \. runtime" , design, re. IGNORECASE ) and \
171 re.search( r "split . {0,160} ( verdict | resolved runtime ) " , design, re. IGNORECASE ), \
172 "effective_runtime must equal platform.runtime when consolidated, else the resolved verdict"
173 assert re.search( r '"coupling"' , design) or re.search( r "coupling . {0,40} context-signals" , design), \
174 "design.md must carry each unit's coupling over from context-signals"
175
176
177 def test_platform_runtime_enum_allows_lambda_consolidation ():
178 # Codex round-6 #2 (regression from round-5): the consolidation superset is "the runtime
179 # satisfying every unit's hard constraints with the highest summed score" — any qualifying
180 # runtime is legal, incl. Lambda / Lambda MicroVMs for an all-agent system. The
181 # platform.runtime enum must NOT be over-narrowed to only ecs/eks/agentcore, and
182 # migration-plan explicitly handles a lambda consolidation superset.
183 design = _norm(pathlib.Path( __file__ ).parent.parent
184 / "references" / "phases" / "design" / "design.md" )
185 m = re.search( r '"runtime": \s * " ([ ^" ] * consolidation superset [ ^" ] * ) "' , design)
186 assert m, "design.md must document the platform.runtime superset enum"
187 enum = m.group( 1 )
188 assert "lambda" in enum, \
189 "platform.runtime enum must allow Lambda/Lambda MicroVMs consolidation, not only ecs/eks/agentcore"
190
191
192 def test_downstream_phases_read_effective_runtime ():
193 # Codex round-2 #2: estimate (cost), generate report (render target), and the diagram
194 # must all key on effective_runtime, not the raw verdict — else a consolidated run costs
195 # and displays the split verdicts while the POC deploys the superset.
196 base = pathlib.Path( __file__ ).parent.parent / "references" / "phases"
197 est = _norm(base / "estimate" / "estimate.md" )
198 rep = _norm(base / "generate" / "generate-report.md" )
199 assert "effective_runtime" in est, "estimate must cost by effective_runtime"
200 assert "effective_runtime" in rep, "report must render the effective_runtime as the target"
201
202
203 def test_estimate_eks_not_hardcoded_fargate ():
204 # Codex round-6 #3: EKS cost must follow the cluster's actual node capacity type, not assume
205 # EKS-on-Fargate. EC2/Spot/Karpenter/GPU and existing-cluster reuse price differently.
206 est = _norm(pathlib.Path( __file__ ).parent.parent
207 / "references" / "phases" / "estimate" / "estimate.md" )
208 assert re.search( r "do NOT assume Fargate | not assume Fargate" , est, re. IGNORECASE ), \
209 "estimate must not hardcode EKS as Fargate"
210 assert re.search( r "node capacity type" , est, re. IGNORECASE ) and \
211 re.search( r "EC2 | Karpenter | Spot" , est), \
212 "estimate must price EKS by actual node capacity type (EC2/Spot/Karpenter/GPU)"
213 # Codex round-7 #2: reusing an existing cluster is near-zero ONLY with spare capacity;
214 # otherwise Karpenter/ASG adds nodes and the full incremental cost applies.
215 assert re.search( r "near-zero . {0,60} ( only | ONLY ). {0,40} spare capacity" , est, re. IGNORECASE | re. DOTALL ) or \
216 re.search( r "spare capacity . {0,80} near-zero" , est, re. IGNORECASE | re. DOTALL ), \
217 "existing-cluster near-zero must be gated on stated spare capacity"
218 assert re.search( r "Karpenter/ASG adds nodes | full incremental" , est, re. IGNORECASE ), \
219 "estimate must charge full incremental node cost when no spare capacity"
220 # Codex round-9 #2: the node-aware EKS rule must apply to ANY unit whose effective_runtime is
221 # eks — not only agent_session. A W1 existing-cluster-reuse or a consolidation can land a
222 # service/batch unit on EKS; the service branch must not flat-price it as Fargate.
223 assert re.search( r "ANY unit whose . ? effective_runtime == . ? eks" , est, re. IGNORECASE ), \
224 "EKS pricing rule must apply to any unit on eks regardless of class"
225 assert re.search( r "regardless of \s + class" , est, re. IGNORECASE ), \
226 "EKS pricing rule must be class-agnostic (service/batch/light_io/agent_session)"
227 # Codex round-10: the temporal_worker_poll polling tier must NOT be hardcoded "tens of $/mo"
228 # for EKS — a GPU/EC2/Karpenter worker fleet is priced by node capacity like any other unit.
229 m = re.search( r "temporal_worker_poll units . {0,2600} " , est, re. DOTALL )
230 assert m, "estimate must have a temporal_worker_poll cost bullet"
231 tw = m.group( 0 )
232 assert re.search( r "effective_runtime == . ? eks . {0,200} ( EKS pricing rule | node capacity ) " ,
233 tw, re. IGNORECASE | re. DOTALL ), \
234 "temporal polling tier must apply the node-aware EKS rule, not a flat 'tens of $/mo'"
235 # Codex round-11 #1: serverless_workers is a legal temporal verdict but had no polling-tier
236 # cost path — must define one (pre-release → MCP rate or qualitative fallback, not fabricated).
237 assert re.search( r "serverless_workers . {0,300} ( qualitative | Public Preview | unverified ) " ,
238 tw, re. IGNORECASE | re. DOTALL ), \
239 "temporal polling tier must define a serverless_workers path (Public Preview/qualitative fallback)"
240 # Codex round-11 #2: must NOT assert execution-tier dominance unconditionally — compare the
241 # two computed bands (a GPU/high-node polling fleet or low Activity volume can flip it).
242 assert not re.search( r "execution tier \s + dominates (?! [ ^. ] * compar ) " , tw, re. IGNORECASE ), \
243 "must not state 'execution tier dominates' unconditionally"
244 assert re.search( r "compare the . {0,20} bands | can equal or exceed | material or even dominant" ,
245 tw, re. IGNORECASE ), \
246 "temporal takeaway must compare the two bands, not assume a fixed order"
247 # Self-audit (pre-round-12): Temporal Cloud orchestration is a system-level cost — the prior
248 # text both said "not folded into any unit's other breakdown" AND "a separate line in the
249 # per-unit cost breakdown", which is contradictory and unexecutable (breakdown is fixed at
250 # {compute, model_tokens, other}). It must be recorded at the SYSTEM level (total + top-level
251 # assumptions), never inside a unit breakdown.
252 # (scope to the whole estimate.md — the temporal table is wide, so a fixed window is fragile)
253 assert re.search( r "do NOT fold them into any unit" , est, re. IGNORECASE ), \
254 "Temporal Cloud actions must not be folded into a unit breakdown"
255 assert not re.search( r "separate line in the per-unit . {0,20} breakdown" , est, re. IGNORECASE ), \
256 "must not instruct putting orchestration into a per-unit breakdown (schema has no slot)"
257 assert re.search( r "total_monthly_magnitude_usd . {0,120} ( system-level | orchestration | PLUS ) " ,
258 est, re. IGNORECASE | re. DOTALL ), \
259 "total must account for the system-level orchestration line, not just the unit sum"
260
261
262 def test_agentcore_endpoint_note_is_per_unit_not_primary ():
263 # Codex round-9 #1: the AgentCore /invocations+/ping endpoint note must attach to EVERY unit
264 # whose effective_runtime is agentcore, not just the primary/winning unit. A split system with
265 # primary=Lambda + secondary AgentCore must still emit the note for the AgentCore unit.
266 plan = _norm(pathlib.Path( __file__ ).parent.parent
267 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
268 assert re.search( r "PER UNIT" , plan) and \
269 re.search( r "EVERY unit whose . ? effective_runtime == . ? agentcore" , plan, re. IGNORECASE ), \
270 "AgentCore endpoint note must be per-unit, keyed on each unit's effective_runtime"
271 assert re.search( r "NOT just the primary" , plan, re. IGNORECASE ), \
272 "AgentCore endpoint note must not be keyed on the primary/winning unit only"
273 # Audit finding #1: the prose is correct but the injection JSON schema must ALSO carry a
274 # per-unit endpoint field, or an assembler following the schema drops the note for a
275 # secondary AgentCore unit — re-introducing the primary-keyed twin.
276 assert re.search( r '"endpoint_contract"' , plan), \
277 "migration-plan injection schema must have a per-unit endpoint_contract field"
278
279
280 def test_poc_goal_line_enumerates_lambda_microvms ():
281 # Audit finding #3: the poc.md Goal-line runtime enumeration must include lambda_microvms —
282 # every other effective_runtime has a goal phrase; omitting it is the 'list missing
283 # lambda_microvms' twin.
284 poc = _norm(pathlib.Path( __file__ ).parent.parent
285 / "references" / "phases" / "poc" / "poc.md" )
286 # The Goal line lists AgentCore/ECS/EKS/Fargate/Lambda/Batch/Temporal — Lambda MicroVMs too.
287 m = re.search( r " \*\* Goal \*\* . {0,600} " , poc, re. DOTALL )
288 assert m and re.search( r "Lambda MicroVMs" , m.group( 0 )), \
289 "poc Goal-line enumeration must include a Lambda MicroVMs phrase"
290
291
292 def test_migration_plan_consolidated_overrides_top_level_approach ():
293 # Codex round-2 #2: the top-level injection reads the primary unit's legacy mirror; under
294 # consolidation it must instead follow platform.runtime (retarget), not inject harness/AgentCore.
295 plan = _norm(pathlib.Path( __file__ ).parent.parent
296 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
297 assert re.search( r " [ Cc ] onsolidated platform overrides" , plan) or \
298 re.search( r "platform \. mode \s * == \s * \" ? consolidated \" ? . {0,200} platform \. runtime" , plan, re. DOTALL ), \
299 "migration-plan must override the top-level approach from platform.runtime when consolidated"
300 # Codex round-7 #1: the non-agentcore superset list must include lambda_microvms (restored as
301 # a legal superset), or an AgentCore primary leaks harness into a MicroVMs consolidation.
302 assert re.search( r "lambda_microvms" , plan) and \
303 re.search( r "harness/strands ONLY when | only inject . ? . ? harness" , plan, re. IGNORECASE ), \
304 "migration-plan retarget override must cover lambda_microvms; harness only when superset is agentcore"
305
306
307 def test_handoff_required_scans_all_units_effective_runtime ():
308 # Codex round-7 #3 + round-8 #2: handoff_required must be true when ANY unit's effective_runtime
309 # needs a compute handoff — one of ecs, eks, fargate, or batch (not just the primary/winning
310 # runtime; fargate=ECS and batch both hand the compute layer to migration skill per their
311 # service cards). AgentCore/Lambda/Lambda MicroVMs are self-contained.
312 design = _norm(pathlib.Path( __file__ ).parent.parent
313 / "references" / "phases" / "design" / "design.md" )
314 assert re.search( r "ANY unit' ? s ? . ? effective_runtime . ? needs a" , design, re. IGNORECASE ), \
315 "handoff_required must be keyed on any unit's effective_runtime, not just the primary"
316 assert re.search( r "not just the primary" , design, re. IGNORECASE ), \
317 "design must state handoff_required is not just the primary/winning runtime"
318 # round-8 #2: the set must include fargate and batch, not just ecs/eks.
319 assert re.search( r " \{ ecs, eks, fargate, batch \} " , design), \
320 "handoff_required set must include fargate and batch (both hand off compute), not just ecs/eks"
321 # build_diagram's HANDOFF_RUNTIMES must stay in sync with the design definition.
322 import build_diagram
323 assert build_diagram. HANDOFF_RUNTIMES == { "ecs" , "eks" , "fargate" , "batch" }, \
324 "build_diagram.HANDOFF_RUNTIMES must match design.md's handoff set (ecs/eks/fargate/batch)"
325
326
327 def test_agentcore_consolidation_keeps_endpoint_note ():
328 # Codex round-8 #1: the AgentCore endpoint/services note must follow platform.runtime, not be
329 # unconditionally suppressed under consolidation — a consolidation ONTO AgentCore still needs
330 # the POST /invocations + GET /ping note, while a non-AgentCore superset must NOT get it.
331 plan = _norm(pathlib.Path( __file__ ).parent.parent
332 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
333 assert re.search( r "AgentCore . * note follows . * platform \. runtime" , plan, re. IGNORECASE ), \
334 "AgentCore endpoint note must follow platform.runtime, not be unconditionally suppressed"
335 assert re.search( r "ONLY when . ? platform \. runtime == . ? agentcore" , plan), \
336 "AgentCore note injected only when the superset is itself AgentCore"
337
338
339 def test_contributing_does_not_mislabel_llm_to_bedrock_as_dsl ():
340 # Codex round-3 #5: llm-to-bedrock is a single prose SKILL.md (no phase frontmatter); the
341 # frontmatter validator only checks heroku-to-aws + agent-advisor. CONTRIBUTING must not
342 # claim llm-to-bedrock is a phase-DSL skill.
343 contributing = (pathlib.Path( __file__ ).parent.parent.parent.parent / "CONTRIBUTING.md" )
344 if not contributing.exists():
345 return # CONTRIBUTING lives at the plugin root; skip if layout differs
346 text = re.sub( r " \s + " , " " , contributing.read_text())
347 # In the architecture table, the llm-to-bedrock row must NOT say "phase DSL".
348 m = re.search( r " \*\* llm-to-bedrock \*\* \s * \| ([ ^| ] * ) \| " , text)
349 assert m, "CONTRIBUTING must have an llm-to-bedrock architecture row"
350 assert "phase DSL" not in m.group( 1 ), \
351 "llm-to-bedrock is prose, not phase DSL — CONTRIBUTING must not label it DSL"
352 assert re.search( r " [ Tt ] wo are built on the . {0,20} phase DSL" , text) or \
353 re.search( r "prose SKILL" , text), \
354 "CONTRIBUTING must state two DSL skills (heroku + agent-advisor), the rest prose"
355
356
357 def test_co_recommend_resolved_to_runtime_before_gate_and_effective_runtime ():
358 # Codex round-3 #1: co_recommend is NOT a runtime. The platform gate must compare the
359 # RESOLVED runtime (chosen_runtime for a co_recommend unit), and design's effective_runtime
360 # must never be the literal "co_recommend". Two tied units sent to different runtimes must
361 # trigger the divergence gate, not be seen as "both co_recommend" and skip it.
362 confirm = _norm(pathlib.Path( __file__ ).parent.parent
363 / "references" / "phases" / "confirm" / "confirm.md" )
364 design = _norm(pathlib.Path( __file__ ).parent.parent
365 / "references" / "phases" / "design" / "design.md" )
366 assert re.search( r "resolved runtime" , confirm, re. IGNORECASE ) and \
367 re.search( r " [ Nn ] ever compare the literal | not a runtime" , confirm), \
368 "confirm platform gate must compare resolved runtime, not the literal co_recommend"
369 assert re.search( r "chosen_runtime" , confirm), \
370 "confirm gate must use chosen_runtime for a co_recommend unit"
371 assert re.search( r " [ Nn ] ever the literal . ? co_recommend | ALWAYS a concrete runtime" , design), \
372 "design effective_runtime must never be the literal co_recommend"
373
374
375 def test_migration_plan_deployment_model_consistent_with_target_runtime ():
376 # Codex round-3 #2: a consolidated (or co_recommend→non-agentcore) row must not carry
377 # deployment_model: harness with target_runtime: ecs. deployment_model is harness/etc ONLY
378 # when target_runtime is agentcore; otherwise framework_on_runtime.
379 plan = _norm(pathlib.Path( __file__ ).parent.parent
380 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
381 assert re.search( r "deployment_model . {0,80} consistent with . {0,20} target_runtime" , plan, re. IGNORECASE ), \
382 "migration-plan must require deployment_model consistent with target_runtime"
383 assert re.search( r "only when . {0,40} target_runtime \s * == \s * \" ? agentcore" , plan, re. IGNORECASE ) and \
384 re.search( r "framework_on_runtime" , plan), \
385 "harness only when target_runtime==agentcore, else framework_on_runtime"
386
387
388 def test_confirm_step3_covers_all_non_agentcore_runtimes ():
389 # Codex round-4 #3: Step 3 (add-on services for non-AgentCore runtimes) must be the
390 # fallthrough for EVERY non-agentcore pick — including lambda_microvms from a co_recommend
391 # tie — or that unit never confirms agentcore_services/tool_choices.
392 confirm = _norm(pathlib.Path( __file__ ).parent.parent
393 / "references" / "phases" / "confirm" / "confirm.md" )
394 assert re.search( r "Step 3 . {0,120} lambda_microvms" , confirm) or \
395 re.search( r "non-AgentCore runtime . {0,120} lambda_microvms" , confirm, re. IGNORECASE ), \
396 "confirm Step 3 must include lambda_microvms among the non-agentcore runtimes"
397 assert re.search( r " [ Ee ] very non-agentcore runtime | no runtime pick skips" , confirm), \
398 "confirm Step 3 must be the fallthrough for every non-agentcore runtime"
399
400
401 def test_confirm_resolves_each_agent_unit_independently ():
402 # Codex round-2 #1: confirm must run per agent_session unit (each unit's own verdict,
403 # deployment_model, services, co_recommend tie), writing confirm.json.units[<id>] — not one
404 # global deployment_model/services applied to every unit.
405 confirm = _norm(pathlib.Path( __file__ ).parent.parent
406 / "references" / "phases" / "confirm" / "confirm.md" )
407 assert re.search( r "once per . ? agent_session . ? unit | per-unit confirm" , confirm, re. IGNORECASE ), \
408 "confirm must iterate per agent_session unit"
409 assert "units[<unit_id>]" in confirm or re.search( r "confirm \. json \. units \[ " , confirm), \
410 "confirm.json must carry per-unit entries"
411 assert re.search( r " [ Nn ] ever ( apply | copy ). {0,90} ( another | primary ) " , confirm), \
412 "confirm must forbid applying one unit's runtime/services to another"
413
414
415 def test_migration_plan_injects_effective_runtime ():
416 # Codex P1 #1: consolidated must drive the plan. The injection's target_runtime is the
417 # EFFECTIVE runtime (platform.runtime under consolidated), not the raw split verdict.
418 plan = _norm(pathlib.Path( __file__ ).parent.parent
419 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
420 assert "effective runtime" in plan.lower(), \
421 "migration-plan must inject the effective runtime"
422 assert re.search( r 'platform \. mode \s * == \s * " ? consolidated" ? ' , plan) and \
423 "platform.runtime" in plan, \
424 "migration-plan effective runtime must resolve platform.runtime under consolidated"
425 assert "raw_verdict" in plan, \
426 "migration-plan must still carry the raw verdict for report trade-off display"
427
428
429 def test_report_v3_data_seams ():
430 report = _norm(pathlib.Path( __file__ ).parent.parent
431 / "references" / "phases" / "generate" / "generate-report.md" )
432 for token in [ '"trigger"' , "provenance" , "platform_decision.offer" ,
433 '"breakdown"' , "drivers" , "key_change" , "volatile_facts" ]:
434 assert token.strip( '"' ) in report, f "v3 report must consume { token } "
435 assert "hero-panel" not in report, "v3 dropped the hero panel"
436
437
438 def test_migrate_runs_estimate ():
439 skill = _norm(pathlib.Path( __file__ ).parent.parent / "SKILL.md" )
440 assert "skip Estimate" not in skill, "migrate now runs Estimate (target-state run cost)"
441 est = _norm(pathlib.Path( __file__ ).parent.parent
442 / "references" / "phases" / "estimate" / "estimate.md" )
443 assert re.search( r "target-state run cost" , est, re. IGNORECASE )
444 assert re.search( r "TCO . {0,120} ( Migration Plan | migration plugins | gcp ) " , est, re. IGNORECASE | re. DOTALL )
445
446
447 def test_poll_units_list_queues ():
448 text = _norm( DISCOVER_MD )
449 assert '"queues"' in text or "`queues`" in text
450 assert re.search( r "temporal_worker_poll . {0,200} queues" , text, re. IGNORECASE )
451
452
453 def test_migration_plan_gcp_report_firewall ():
454 """ BUG -1 (live test 0715): the inline gcp engine must render migration-report.html
455 in gcp's OWN format — the advisor v3 shell must not bleed into it."""
456 mp = _norm(pathlib.Path( __file__ ).parent.parent
457 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
458 assert re.search( r " [ Cc ] ontext firewall" , mp), "Phase E must state the gcp context firewall"
459 assert "report-shell.md" in mp and re.search(
460 r " [ Dd ] o NOT apply . {0,120} report-shell" , mp, re. DOTALL ), \
461 "must forbid applying the advisor shell to gcp artifacts"
462 assert re.search( r "ADDITIVE-ONLY | additive-only" , mp), \
463 "the help-banner injection must be declared additive-only"
464
465
466 def test_help_banner_suppressed_pre_launch ():
467 """The support page is not launched — the help banner must be SUPPRESSED at its single
468 source, and every consumer (recommendation report, POC report, migration-report
469 post-process) must gate on that status and omit the banner while suppressed."""
470 root = pathlib.Path( __file__ ).parent.parent
471 banner = _norm(root / "references" / "report-help-banner.md" )
472 assert re.search( r "banner_status: \s * SUPPRESSED" , banner), \
473 "report-help-banner.md must declare banner_status: SUPPRESSED pre-launch"
474 # Each consumer must reference the gate so it doesn't render the banner unconditionally.
475 for rel in [
476 "references/phases/generate/generate-report.md" ,
477 "references/phases/poc/poc-report.md" ,
478 "references/phases/migration-plan/migration-plan.md" ,
479 ]:
480 text = _norm(root / rel)
481 assert re.search( r "SUPPRESSED" , text) and re.search( r "banner_status" , text), \
482 f " { rel } must gate the help banner on banner_status (skip while SUPPRESSED)"
483 # Codex round-12 #3: unconditional "render the CTA" statements must ALSO be gated — the
484 # top-of-file instruction in generate-report.md and the usage note in report-help-banner.md
485 # previously told the interpreter to render the CTA / put the banner at the top of EVERY
486 # report with no gate, which could still leak the staging link.
487 gen = _norm(root / "references" / "phases" / "generate" / "generate-report.md" )
488 assert re.search( r "ONLY when . {0,40} banner_status . {0,20} LIVE | render NO help CTA" , gen, re. IGNORECASE ), \
489 "generate-report.md top instruction must gate the help CTA on banner_status"
490 assert re.search( r "applies ONLY while . ? banner_status . ? is . ? LIVE" , banner, re. IGNORECASE ), \
491 "report-help-banner.md usage note must be gated on banner_status LIVE"
492
493
494 def test_serverless_workers_unverified_breakdown_schema ():
495 # Codex round-12 #1: SW polling may be qualitatively unverifiable, but the phase otherwise
496 # requires every breakdown component + the unit total to be a dollar band. Reconcile: an
497 # unverifiable component uses "unverified", and the unit total reflects it rather than
498 # fabricating a number.
499 est = _norm(pathlib.Path( __file__ ).parent.parent
500 / "references" / "phases" / "estimate" / "estimate.md" )
501 assert re.search( r '"unverified"' , est), \
502 "estimate must define an 'unverified' breakdown value for a non-priceable SW rate"
503 assert re.search( r "EXCEPT when . {0,80} unverifiable | unverified . {0,120} monthly_magnitude_usd" ,
504 est, re. IGNORECASE | re. DOTALL ), \
505 "the 'every component is a band' rule must carve out the unverified SW case for the total"
506
507
508 def test_report_cost_dominance_not_hardcoded_and_temporal_vars_defined ():
509 # Codex round-12 #2: the report must not hardcode "model tokens dominate every line" (Estimate
510 # now compares bands), and TEMPORAL_COST / TEMPORAL_COST_SUMMARY used in the cost table must be
511 # defined in the data-collection table or they render blank.
512 rep = _norm(pathlib.Path( __file__ ).parent.parent
513 / "references" / "phases" / "generate" / "generate-report.md" )
514 assert "model tokens dominate every line" not in rep, \
515 "report must not hardcode unconditional model-token dominance"
516 assert re.search( r "COST_DOMINANT_NOTE" , rep), \
517 "report must derive the dominant-tier note from the actual estimate bands"
518 # Every templated variable used in the cost summary must be defined in the collection table.
519 for var in [ "COST_DOMINANT_NOTE" , "TEMPORAL_COST" , "TEMPORAL_COST_SUMMARY" ]:
520 assert len (re.findall( rf "`? { var } `?" , rep)) >= 2 , \
521 f " { var } must be BOTH defined in the data-collection table AND used in the template"
522
523
524 def test_report_reads_fields_producers_actually_write ():
525 # Contract audit (pre-round-13): the report template must not reference estimate.json /
526 # design.json fields that the producers never emit (they render blank / empty parens).
527 rep = _norm(pathlib.Path( __file__ ).parent.parent
528 / "references" / "phases" / "generate" / "generate-report.md" )
529 est = _norm(pathlib.Path( __file__ ).parent.parent
530 / "references" / "phases" / "estimate" / "estimate.md" )
531 # Fields the report reads that estimate.json must actually produce:
532 assert "ESTIMATE.total or" not in rep and "ESTIMATE.total }} " not in rep, \
533 "report must read total_monthly_magnitude_usd, not the non-existent ESTIMATE.total"
534 for total_field in [ "total_compute" , "total_model" , "total_other" ]:
535 assert total_field in est, \
536 f "estimate.md must emit { total_field } that the cost Total row reads"
537 # driver key is `driver`, not `dimension`; model field is `model`, not `model_display`.
538 assert "driver.dimension" not in rep, "report must read driver.driver (producer's key)"
539 assert "model_display" not in rep, "report must read model_recommendation.model (producer's key)"
540 # per-unit breakdown has no compute_note/model_note subfields (breakdown is fixed at 3 keys).
541 assert "compute_note" not in rep and "model_note" not in rep, \
542 "report must not read breakdown subfields the producer never writes"
543 # per-unit scores live in scoring-result.json, not on design.json units[].
544 assert "unit.scores[" not in rep, \
545 "report must read SCORING_RESULT.units[unit.id].scores, not unit.scores off a design unit"
546 # PLATFORM_DECISION has no `rationale`; render from offer.sacrifices instead.
547 assert "PLATFORM_DECISION.rationale" not in rep, \
548 "report must render the platform decision from offer.sacrifices, not a non-existent rationale"
549 # poc-report Overview help CTA must be gated too (not just the body block).
550 poc = _norm(pathlib.Path( __file__ ).parent.parent
551 / "references" / "phases" / "poc" / "poc-report.md" )
552 assert re.search( r "GATED on . {0,40} banner_status | render NO help strip" , poc, re. IGNORECASE ), \
553 "poc-report Overview must gate the help CTA on banner_status, matching its body block"
554
555
556 def test_report_contract_round13_fixes ():
557 # Codex round-13: four more producer→consumer mismatches in generate-report.md.
558 rep = _norm(pathlib.Path( __file__ ).parent.parent
559 / "references" / "phases" / "generate" / "generate-report.md" )
560 est = _norm(pathlib.Path( __file__ ).parent.parent
561 / "references" / "phases" / "estimate" / "estimate.md" )
562 # #1 TEMPORAL_UNITS_PRESENT must key on units[].workload_class, not design.temporal.units
563 # (the temporal block has no units field).
564 assert re.search( r "TEMPORAL_UNITS_PRESENT . {0,160} workload_class == . ? temporal_worker_poll" ,
565 rep, re. DOTALL ), \
566 "TEMPORAL_UNITS_PRESENT must key on units[].workload_class == temporal_worker_poll"
567 # #2 the unverified-component rule must cover ALL three column totals + the grand total,
568 # not only total_model.
569 assert re.search( r "uniform across all three column totals AND" , est), \
570 "estimate must apply the unverified-exclusion rule to every total, not just total_model"
571 # #3 rule-based (non-agent) units read design's rationale, not fabricated verdict_detail /
572 # rule_cite / rule_text / considered_and_rejected.
573 for ghost in [ "verdict_detail" , "rule_text" , "considered_and_rejected" ]:
574 assert ghost not in rep, \
575 f "rule-based form must not read { ghost } (design produces rationale + key_change only)"
576 # #4 eliminated is a {runtime: reason} map — read as a map, scoped to the current unit,
577 # never as an array with .length / elim.runtime.
578 assert ".eliminated.length" not in rep and "elim.runtime" not in rep, \
579 "eliminated is a map; must not read .length / elim.runtime"
580 assert re.search( r "FOR EACH \( runtime, reason \) IN SCORING_RESULT \. units \[ unit \. id \]\. eliminated" ,
581 rep), \
582 "eliminated must be iterated as a (runtime, reason) map scoped to the current unit"
583
584
585 def test_report_all_template_vars_have_a_producer ():
586 # Mechanical audit (pre-round-14): every JSON-contract {{ VARIABLE }} in generate-report.md
587 # must either be defined in the R0 data-collection table or be a documented prose/derived
588 # value. These specific ones were used-but-undefined (render blank) or ghost fields.
589 rep = _norm(pathlib.Path( __file__ ).parent.parent
590 / "references" / "phases" / "generate" / "generate-report.md" )
591 # §1 summary non-agent Basis is the twin of the §3 rule-based form (round-13 #3): it must read
592 # unit.rationale, NOT unit.rule_cite (design produces rationale, not rule_cite).
593 assert "unit.rule_cite" not in rep, \
594 "§1 summary non-agent Basis must read unit.rationale, not the unproduced unit.rule_cite"
595 # used-but-previously-undefined variables must now be defined in the R0 table.
596 for var in [ "PRIMARY_UNIT" , "unit.runner_up_runtime" , "unit.runner_up_score" , "SYSTEM_NAME" ]:
597 assert var in rep, \
598 f " { var } must be defined in the R0 data-collection table (it is used in the template)"
599 # the runner-up row must be gated so a null runner-up doesn't render empty parens.
600 assert re.search( r "IF unit \. runner_up_runtime" , rep), \
601 "the Runner-up row must be gated on unit.runner_up_runtime so it degrades gracefully"
602
603
604 def test_report_contract_round14_fixes ():
605 # Codex round-14: three more producer→consumer mismatches — two were my own mechanical-audit
606 # misjudgments (assumed pct existed, assumed PROVENANCE path was right).
607 rep = _norm(pathlib.Path( __file__ ).parent.parent
608 / "references" / "phases" / "generate" / "generate-report.md" )
609 # #1 runner-up must be PER UNIT (derived inside the loop from that unit's scores), not a
610 # global scalar shared across all agent units.
611 assert "RUNNER_UP_RUNTIME" not in rep and "RUNNER_UP_SCORE" not in rep, \
612 "runner-up must not be a global scalar; use per-unit unit.runner_up_*"
613 assert re.search( r "unit \. runner_up_runtime . {0,220} per-unit loop | NOT a global scalar" ,
614 rep, re. DOTALL | re. IGNORECASE ), \
615 "runner-up must be documented as per-unit derived, not global"
616 # #2 pct has no producer in scoring.json — the score loop must define the normalization
617 # formula, and must NOT destructure pct from the scores map.
618 assert "(runtime, score, pct) IN" not in rep, \
619 "the scores map is {runtime: score} ; pct must not be destructured from it"
620 assert re.search( r "pct \s * = \s * ROUND \( 100" , rep), \
621 "the score bar must define pct = ROUND(100 * score / MAX(...)) — a real normalization"
622 # #3 provenance is layered (system.provenance + units[id].provenance), not top-level
623 # answers.json.provenance.
624 assert "answers.json.provenance" not in rep, \
625 "PROVENANCE must read the layered system.provenance / units[id].provenance, not top-level"
626 assert re.search( r "system \. provenance" , rep) and re.search( r "units \[ unit_id \]\. provenance" , rep), \
627 "the Assessment-inputs Source must look up the layered provenance by scope"
628
629
630 def _run_score_units (answers):
631 import subprocess # nosec B404 — test-only, fixed args
632 import sys
633 import tempfile
634
635 script = pathlib.Path( __file__ ).parent / "score_units.py"
636 with tempfile.TemporaryDirectory() as tmp:
637 # No context-signals.json in this dir — mirrors a skipped-Discover run.
638 answers_path = pathlib.Path(tmp) / "answers.json"
639 answers_path.write_text(json.dumps(answers), encoding = "utf-8" )
640 proc = subprocess.run( # nosec B603 — test-only, fixed args
641 [sys.executable, str (script), str (answers_path)],
642 capture_output = True , text = True , check = True ,
643 )
644 return json.loads(proc.stdout)
645
646
647 _SCORING_ANSWERS = {
648 "entry_point" : "build_scratch" ,
649 "primary_unit" : "solo" ,
650 "system" : { "ops_preference" : "minimal" , "existing_cluster" : "none" ,
651 "multi_cloud" : "no" , "platform_fit" : "none" },
652 "units" : {
653 "solo" : { "workload_class" : "agent_session" , "session_duration" : "15min_to_8hr" ,
654 "traffic_pattern" : "steady" , "session_state" : "stateful" ,
655 "isolation" : "nice_to_have" , "memory_needs" : "session_only" ,
656 "multi_agent" : "no" , "framework" : "langgraph" , "idle_resume" : "none" ,
657 "compute_tier" : "light" , "launch_concurrency" : "moderate" ,
658 "deployment_preference" : "framework" , "compliance" : [],
659 "model_priority" : "balanced" , "region" : "single" },
660 "cron" : { "workload_class" : "batch" },
661 },
662 }
663
664
665 def test_scoring_command_importable_and_wraps_units ():
666 # Codex round-15 P1, retargeted from the former inline interpreter one-liner to the
667 # committed score_units.py: the Clarify scoring command must be runnable and must WRAP
668 # results under {"units": {...}} (downstream reads .units[id]), not emit a bare
669 # {unit_id: result} map.
670 clarify = _norm(pathlib.Path( __file__ ).parent.parent
671 / "references" / "phases" / "clarify" / "clarify.md" )
672 assert re.search( r 'score_units \. py" \s + " \$ RUN_DIR/answers \. json"' , clarify), \
673 "clarify must invoke the committed score_units.py with answers.json as an argument"
674 assert "python -c" not in clarify, \
675 "clarify must not inline scoring code — committed scripts only (skills.sh RCE flag)"
676 out = _run_score_units( _SCORING_ANSWERS )
677 assert set (out[ "units" ].keys()) == { "solo" }, \
678 "results must be wrapped under a top-level 'units' key, agent_session units only"
679 # single-unit collapse: primary unit's result must also be mirrored to the top level.
680 for key, value in out[ "units" ][ "solo" ].items():
681 assert out.get(key) == value, \
682 f "primary unit's ' { key } ' must be mirrored to the top level for legacy readers"
683
684
685 def test_build_diagram_single_unit_design_renders_from_unit ():
686 # Codex round-15 P2: a single-unit design must render from the design UNIT (effective_runtime,
687 # model), not fall through to the legacy `result` path (which produced None/unknown).
688 import build_diagram
689 design = {
690 "units" : [{
691 "id" : "solo" , "workload_class" : "agent_session" ,
692 "verdict" : "agentcore" , "effective_runtime" : "agentcore" ,
693 "deployment_model" : "harness" ,
694 "model_recommendation" : { "model" : "claude_sonnet_5" },
695 "agentcore_services" : [ "memory" ],
696 }],
697 "platform" : { "mode" : "split" , "runtime" : None , "interconnect" : "in_process" },
698 }
699 # Pass a WRAPPED scoring-result as `result` (no top-level verdict/model) — the bug case.
700 out = build_diagram.build_diagram({ "units" : { "solo" : {}}}, {}, design = design)
701 assert "AgentCore Runtime" in out[ "mermaid" ], \
702 "single-unit design must render the unit's effective_runtime, not None"
703 assert "claude_sonnet_5" in out[ "mermaid" ], \
704 "single-unit design must render the unit's model, not 'unknown'"
705
706
707 def test_report_assessment_inputs_enumerates_layered_answers ():
708 # Codex round-15 P2: the Assessment-inputs table must flatten answers.system + each
709 # answers.units[id] (excluding provenance), not iterate the impossible "ANSWERS + UNITS".
710 rep = _norm(pathlib.Path( __file__ ).parent.parent
711 / "references" / "phases" / "generate" / "generate-report.md" )
712 assert "(dim, value, scope, unit_id) IN ANSWERS + UNITS" not in rep, \
713 "Assessment inputs must not iterate ANSWERS + UNITS (yields no dimensions)"
714 assert re.search( r "FOR EACH \( dim, value \) IN ANSWER_LAYERS \. system EXCEPT" , rep) and \
715 re.search( r "FOR EACH \( dim, value \) IN ANSWER_LAYERS \. units \[ unit_id \] EXCEPT" , rep), \
716 "Assessment inputs must flatten ANSWER_LAYERS.system + .units[id], excluding provenance"
717
718
719 def test_temporal_poc_dispatches_on_effective_runtime ():
720 # Codex round-15 P2: a temporal_worker_poll unit on EKS must get the eks (kubectl) base, not
721 # always the ecs Terraform shape.
722 shapes = _norm(pathlib.Path( __file__ ).parent.parent
723 / "references" / "decision-refs" / "poc-shapes.md" )
724 poc = _norm(pathlib.Path( __file__ ).parent.parent
725 / "references" / "phases" / "poc" / "poc.md" )
726 assert re.search( r " [ Bb ] ase shape follows . {0,40} effective_runtime" , shapes), \
727 "the Temporal POC base shape must follow the unit's effective_runtime"
728 assert re.search( r "effective_runtime == . ? eks . {0,120} ( eks | kubectl | Kubernetes ) " , shapes, re. DOTALL ), \
729 "an EKS temporal worker must use the eks/kubectl base, not ecs Terraform"
730 assert re.search( r "effective . ? runtime . {0,80} eks . {0,60} kubectl | eks . {0,40} NO Terraform" , poc, re. DOTALL | re. IGNORECASE ), \
731 "poc.md temporal dispatch must route EKS workers to the kubectl base"
732
733
734 def test_runner_up_excludes_the_winner ():
735 # Codex round-15 P3: runner-up must EXCLUDE unit.verdict, not just be "2nd sorted" — else a
736 # co_recommend winner picked from a tie can appear as its own runner-up.
737 rep = _norm(pathlib.Path( __file__ ).parent.parent
738 / "references" / "phases" / "generate" / "generate-report.md" )
739 assert re.search( r "runner_up_runtime . {0,200} EXCLUDING . ? unit \. verdict" , rep, re. DOTALL ), \
740 "runner-up must be the top score EXCLUDING unit.verdict, not the 2nd-sorted entry"
741
742
743 def test_scoring_reads_only_answers_json ():
744 # Codex round-17 P1, retargeted to the committed score_units.py: scoring must NOT open
745 # context-signals.json (absent on skipped-Discover runs — build_scratch / no-path).
746 # workload_class is persisted into answers.json.units[id] (Step 4), and entry_point read
747 # from .phase-status.json — answers.json is always present.
748 clarify = _norm(pathlib.Path( __file__ ).parent.parent
749 / "references" / "phases" / "clarify" / "clarify.md" )
750 src = (pathlib.Path( __file__ ).parent / "score_units.py" ).read_text( encoding = "utf-8" )
751 assert "context-signals.json" not in src, \
752 "the executed scoring code must not open context-signals.json (absent on skipped-Discover)"
753 assert "sys.argv[1]" in src, \
754 "scoring must read answers.json from argv (always present), never a hardcoded path"
755 # behavioral: runs to completion in a directory that has ONLY answers.json
756 out = _run_score_units( _SCORING_ANSWERS )
757 assert "cron" not in out[ "units" ], \
758 "the filter must read workload_class from the answers.json unit entry (batch excluded)"
759 # workload_class is persisted into answers.json units + filtered from there
760 assert re.search( r '"workload_class": \s * "<class>"' , clarify), \
761 "answers.json units[] must persist workload_class (Step 4 schema)"
762 assert 'info.get("workload_class") == "agent_session"' in src, \
763 "the filter must read workload_class from the answers.json unit entry"
764 # entry_point sourced from .phase-status.json, not context-signals.json
765 assert re.search( r "entry_point . {0,80} \. phase-status \. json" , clarify, re. DOTALL ), \
766 "entry_point must be read from .phase-status.json"
767
768
769 def test_clarify_scope_gate_requires_agent_unit ():
770 # Scope decision: agent-advisor is scoped to agentic systems. Clarify halts a purely
771 # non-agent system (no agent_session unit) with _halt_and_inform, so every phase after may
772 # assume >=1 agent unit. Round-24 P1: the gate must run BEFORE primary selection (an
773 # all-non-agent system has no valid agent primary to pick/question).
774 clarify_raw = (pathlib.Path( __file__ ).parent.parent
775 / "references" / "phases" / "clarify" / "clarify.md" ).read_text()
776 clarify = re.sub( r " \s + " , " " , clarify_raw)
777 assert re.search( r " [ Ss ] cope gate" , clarify) and re.search( r "BEFORE primary selection" , clarify), \
778 "clarify must have a scope gate that runs before primary selection"
779 assert re.search( r "at least one . ? agent_session . ? unit" , clarify, re. IGNORECASE ), \
780 "the gate must require >=1 agent_session unit"
781 assert re.search( r "_halt_and_inform" , clarify), \
782 "the gate must halt (not silently proceed) for a purely non-agent system"
783 # ORDERING: the scope-gate section must physically precede the "Primary unit" bullet.
784 gate_pos = clarify_raw.find( "Scope gate" )
785 primary_pos = clarify_raw.find( "**Primary unit:**" )
786 assert 0 < gate_pos < primary_pos, \
787 "the scope gate must appear before the Primary unit selection in the file"
788 # the retired zero-agent sentinel/flag must be gone
789 assert "no_agent_units" not in clarify, \
790 "the no_agent_units zero-agent path must be removed (scoped out via the gate)"
791 assert "'non_agent'" not in clarify and '"non_agent"' not in clarify, \
792 "the non_agent sentinel must be gone"
793
794
795 def test_clarify_materializes_single_unit_when_no_inventory ():
796 # A single-workload build_scratch run has no unit in context-signals/context-notes; Clarify
797 # materializes one — default agent_session (so the ordinary case passes the scope gate).
798 clarify = _norm(pathlib.Path( __file__ ).parent.parent
799 / "references" / "phases" / "clarify" / "clarify.md" )
800 assert re.search( r "No inventory at all | MATERIALIZES exactly one . {0,20} unit" , clarify), \
801 "clarify must materialize a single unit when no inventory exists"
802 assert re.search( r "workload_class . {0,40} defaults to . ? agent_session" , clarify, re. DOTALL ), \
803 "the materialized single unit defaults to agent_session"
804 # the materialized record is COMPLETE (coupling/trigger/description/evidence) for downstream.
805 for field in [ "coupling" , "trigger" , "description" , "evidence" ]:
806 assert field in clarify, \
807 f "the materialized unit record must carry { field } for downstream fallback"
808
809
810 def test_scoring_result_schema_scored_only ():
811 # After scope-out, scoring-result.json is a single scored shape (no zero-agent variant): the
812 # flat primary mirror + a non-empty units{} map whose values are scored per-unit results.
813 import json
814 schema = json.loads((pathlib.Path( __file__ ).parent.parent
815 / "scripts" / "schemas" / "scoring-result.json" ).read_text())
816 assert "oneOf" not in schema, "schema must be a single scored shape (no zero-agent union)"
817 assert "units" in schema.get( "required" , []), "scored shape must require units"
818 try :
819 import jsonschema
820 except ImportError :
821 return
822 jsonschema.Draft7Validator.check_schema(schema)
823 V = jsonschema.Draft7Validator(schema)
824 _unit = { "verdict" : "ecs" , "scores" : { "ecs" : 10 }, "eliminated" : {},
825 "deployment_model" : None , "agentcore_services" : [],
826 "model_recommendation" : { "model" : "m" , "reasoning" : "r" },
827 "deferred_verification_requirements" : [],
828 "recommendation_status" : "final" }
829 scored = { ** _unit, "assumptions_used" : [], "warnings" : [], "units" : { "solo" : _unit}}
830 assert V.is_valid(scored), "scored shape (wrapped, non-empty units) must validate"
831 assert not V.is_valid({ ** scored, "units" : {}}), "empty units must be rejected"
832 assert not V.is_valid({k: v for k, v in scored.items() if k != "units" }), \
833 "missing units must be rejected"
834 assert not V.is_valid({ ** scored, "units" : { "broken" : {}}}), \
835 "a units value missing scored fields must be rejected"
836 assert not V.is_valid({ ** scored, "verdict" : "non_agent" }), \
837 "the retired non_agent sentinel must be rejected by the enum"
838 # the zero-agent shape is no longer valid.
839 assert not V.is_valid({ "units" : {}, "no_agent_units" : True }), \
840 "the retired zero-agent shape must no longer validate"
841
842
843 def test_confirm_persists_resolved_runtimes_including_temporal ():
844 # round-19 redesign: Confirm resolves every unit's runtime ONCE (incl temporal Tier-1 user
845 # choices) into resolved_runtimes; Design consumes it verbatim and never re-asks.
846 confirm = _norm(pathlib.Path( __file__ ).parent.parent
847 / "references" / "phases" / "confirm" / "confirm.md" )
848 design = _norm(pathlib.Path( __file__ ).parent.parent
849 / "references" / "phases" / "design" / "design.md" )
850 assert "resolved_runtimes" in confirm, \
851 "confirm must persist resolved_runtimes covering every unit"
852 assert re.search( r "Tier-1 rule . {0,80} ( user choice | AskUserQuestion ). {0,120} resolved_runtimes" ,
853 confirm, re. DOTALL | re. IGNORECASE ), \
854 "confirm must persist the temporal Tier-1 user choice into resolved_runtimes"
855 assert re.search( r "resolved_runtimes . {0,120} verbatim | Consume Confirm . {0,80} do NOT re-evaluate" ,
856 design, re. DOTALL | re. IGNORECASE ), \
857 "Design must consume resolved_runtimes verbatim, not re-evaluate temporal Tier 1"
858
859
860 def test_poc_gate_keys_on_effective_runtime_not_agent_pool ():
861 # round-19 redesign: Gate 2 / poc entry must offer any supported effective_runtime
862 # (incl batch/fargate/serverless_workers), not just the agent-only scoring pool.
863 gen = _norm(pathlib.Path( __file__ ).parent.parent
864 / "references" / "phases" / "generate" / "generate.md" )
865 poc = _norm(pathlib.Path( __file__ ).parent.parent
866 / "references" / "phases" / "poc" / "poc.md" )
867 plan = _norm(pathlib.Path( __file__ ).parent.parent
868 / "references" / "phases" / "migration-plan" / "migration-plan.md" )
869 for doc, name in [(gen, "generate" ), (poc, "poc" ), (plan, "migration-plan" )]:
870 assert re.search( r "effective_runtime" , doc), \
871 f " { name } POC gate must key on effective_runtime"
872 assert re.search( r "batch . {0,20} fargate . {0,20} serverless_workers | serverless_workers" , doc), \
873 f " { name } POC gate must include batch/fargate/serverless_workers"
874
875
876 def test_confirm_resolves_temporal_via_temporal_md ():
877 # Codex round-18 P2: workload-classes.md has no temporal_worker_poll rule; a zero-agent
878 # Temporal system must resolve its polling runtime via temporal.md Tier 1 for the platform gate.
879 confirm = _norm(pathlib.Path( __file__ ).parent.parent
880 / "references" / "phases" / "confirm" / "confirm.md" )
881 assert re.search( r "temporal_worker_poll . {0,120} temporal \. md" , confirm, re. DOTALL ), \
882 "confirm must resolve temporal_worker_poll units via temporal.md, not workload-classes.md"
883
884
885 def test_eks_temporal_cert_paths_mounted_as_volume ():
886 # Codex round-18 P2: TEMPORAL_TLS_*_PATH are file paths; secretKeyRef injects env values, so
887 # the cert Secret keys must be mounted as a volume with the *_PATH vars set to mounted paths.
888 shapes = _norm(pathlib.Path( __file__ ).parent.parent
889 / "references" / "decision-refs" / "poc-shapes.md" )
890 assert re.search( r "TEMPORAL_TLS_CA_PATH . {0,200} ( volume | volumeMount | mounted ) " , shapes, re. DOTALL ), \
891 "cert *_PATH vars must be backed by a mounted volume, not secretKeyRef env values"
892
893
894 def test_assessment_inputs_excludes_workload_class_metadata ():
895 # Codex round-18 P3: after persisting workload_class into answer units, the Assessment-inputs
896 # loop must exclude it (metadata, not a scored dimension), not render it with source=detected.
897 rep = _norm(pathlib.Path( __file__ ).parent.parent
898 / "references" / "phases" / "generate" / "generate-report.md" )
899 assert re.search( r 'EXCEPT "provenance", "workload_class"' , rep), \
900 "the unit Assessment-inputs loop must exclude both provenance and workload_class"
901
902
903 def test_confirm_rescore_reuses_wrapper_command ():
904 # Codex round-16 P2: no-viable-runtime rescoring must reuse clarify's wrapper-producing
905 # command, NOT invoke scoring.py directly (which writes a flat result and clobbers units{}).
906 confirm = _norm(pathlib.Path( __file__ ).parent.parent
907 / "references" / "phases" / "confirm" / "confirm.md" )
908 assert re.search( r "Do NOT invoke . ? scoring \. py . ? directly" , confirm, re. IGNORECASE ), \
909 "confirm rescoring must forbid direct scoring.py (it clobbers the units {} wrapper)"
910 assert re.search( r "clarify \. md Step 5" , confirm), \
911 "confirm rescoring must reuse clarify.md Step 5's wrapper-producing command"
912 assert "scripts/scoring.py $RUN_DIR/answers.json" not in confirm, \
913 "the direct flat scoring.py rescore command must be gone"
914
915
916 def test_report_answer_layers_variable_defined ():
917 # Codex round-16 P2: the Assessment-inputs flattening reads ANSWER_LAYERS (the layered
918 # answers.json), which must be a defined R0 variable — ANSWERS is only the primary merge.
919 rep = _norm(pathlib.Path( __file__ ).parent.parent
920 / "references" / "phases" / "generate" / "generate-report.md" )
921 assert len (re.findall( r "` ? ANSWER_LAYERS` ? " , rep)) >= 2 , \
922 "ANSWER_LAYERS must be defined in R0 AND used by the Assessment-inputs flattening"
923 assert re.search( r "ANSWER_LAYERS . {0,120} \. system . {0,40} \. units" , rep, re. DOTALL ), \
924 "ANSWER_LAYERS must be sourced from the layered answers.json (.system + .units)"
925
926
927 def test_eks_temporal_poc_no_http_smoke_no_terraform_teardown ():
928 # Codex round-16 P2: the EKS Temporal shape must NOT inherit the generic EKS HTTP smoke
929 # (a worker has no HTTP endpoint) and must NOT mandate terraform destroy (EKS uses kubectl).
930 shapes = _norm(pathlib.Path( __file__ ).parent.parent
931 / "references" / "decision-refs" / "poc-shapes.md" )
932 # The EKS temporal variant must state there is NO service.yaml / no HTTP curl smoke.
933 assert re.search( r "NO ` ? service \. yaml` ?| exposes NO HTTP" , shapes, re. IGNORECASE ), \
934 "EKS temporal POC must not inherit the HTTP service/port-forward smoke"
935 assert re.search( r "kubectl logs" , shapes), \
936 "EKS temporal smoke proof must be via kubectl logs, not an HTTP curl"
937 # Teardown must be base-conditional (kubectl delete on EKS, not terraform destroy).
938 assert re.search( r "on \*\* EKS \*\* , ? \s * ` ? kubectl delete" , shapes, re. IGNORECASE ) or \
939 re.search( r "never ` ? terraform \s + destroy` ? on the EKS base" , shapes, re. IGNORECASE ), \
940 "EKS temporal teardown must be kubectl delete, never terraform destroy"
941
942
943 def test_build_diagram_preserves_authoritative_empty_services ():
944 # Codex round-16 P2: an explicit empty agentcore_services list means the user declined all
945 # add-ons; the single-unit design path must NOT fall back to scoring defaults for [].
946 import build_diagram
947 design = {
948 "units" : [{
949 "id" : "solo" , "workload_class" : "agent_session" ,
950 "verdict" : "ecs" , "effective_runtime" : "ecs" ,
951 "model_recommendation" : { "model" : "claude_sonnet_5" },
952 "agentcore_services" : [], # authoritative: user declined all
953 }],
954 "platform" : { "mode" : "split" , "runtime" : None , "interconnect" : "in_process" },
955 }
956 # result carries scoring defaults that must NOT leak in when the unit says [].
957 result = { "verdict" : "ecs" , "agentcore_services" : [ "identity" , "observability" ],
958 "model_recommendation" : { "model" : "claude_sonnet_5" }}
959 out = build_diagram.build_diagram(result, {}, design = design)
960 assert "Identity" not in out[ "mermaid" ] and "Observability" not in out[ "mermaid" ], \
961 "an authoritative empty agentcore_services must not fall back to scoring defaults"
962
963
964 def test_design_effective_runtime_enum_includes_serverless_workers ():
965 # Codex round-16 P2: serverless_workers is a legal temporal Tier 1 outcome that Estimate/POC
966 # dispatch on — it must be in Design's effective_runtime enum.
967 design = _norm(pathlib.Path( __file__ ).parent.parent
968 / "references" / "phases" / "design" / "design.md" )
969 assert re.search( r "effective_runtime . {0,200} serverless_workers" , design, re. DOTALL ), \
970 "the effective_runtime enum must include serverless_workers"
971
972
973 def test_eks_temporal_artifacts_complete_and_not_ecs_runtask ():
974 # Codex round-17 P2: the EKS Temporal artifact list must include smoke_worker.py and a
975 # concrete starter Job manifest, and the shared delta must not call the starter an
976 # ECS-only run-task.
977 shapes = _norm(pathlib.Path( __file__ ).parent.parent
978 / "references" / "decision-refs" / "poc-shapes.md" )
979 assert re.search( r "smoke_worker \. py" , shapes) and re.search( r "smoke-job \. yaml" , shapes), \
980 "EKS temporal artifacts must include smoke_worker.py and k8s/smoke-job.yaml"
981 # the shared delta must distinguish ECS run-task from EKS Job, not say run-task unconditionally
982 assert re.search( r "ECS . {0,30} run-task . {0,60} EKS . {0,30} Job | EKS base a one-shot . ? Job" ,
983 shapes, re. DOTALL ), \
984 "the starter must be specified per base (ECS run-task vs EKS Job)"
985 # secret created from env/files, not committed in a generated secret.yaml
986 assert re.search( r "do NOT commit connection material into \s * a generated . ? secret \. yaml" ,
987 shapes, re. IGNORECASE ), \
988 "EKS temporal Secret must be created from env/files, not a committed secret.yaml"
989
990
991 def test_serverless_workers_has_no_card_derivation ():
992 # Codex round-17 P2: serverless_workers has no runtime card; Design must not try to load
993 # serverless_workers.md and must derive key_change from temporal.md + poc-shapes.md.
994 design = _norm(pathlib.Path( __file__ ).parent.parent
995 / "references" / "phases" / "design" / "design.md" )
996 assert re.search( r "serverless_workers . {0,60} NO runtime card | NO runtime card . {0,60} serverless_workers" ,
997 design, re. DOTALL | re. IGNORECASE ), \
998 "design must state serverless_workers has no runtime card"
999 assert re.search( r " [ Dd ] o NOT try to load . ? serverless_workers \. md | skip this load for it" ,
1000 design), \
1001 "design must not attempt to load a nonexistent serverless_workers.md"
1002 assert re.search( r "serverless_workers . {0,200} temporal \. md" , design, re. DOTALL ), \
1003 "serverless_workers key_change must derive from temporal.md + poc-shapes.md"
1004
1005
1006 def test_design_consumes_resolved_runtimes_for_all_units ():
1007 # Codex round-20 P1: Design must use confirm.resolved_runtimes verbatim for EVERY unit, not
1008 # re-evaluate temporal/workload rules (which can disagree with platform_decision).
1009 design = _norm(pathlib.Path( __file__ ).parent.parent
1010 / "references" / "phases" / "design" / "design.md" )
1011 assert re.search( r "non-agent unit' ? s ? runtime comes from . ? confirm \. json \. resolved_runtimes . {0,40} VERBATIM" ,
1012 design, re. DOTALL | re. IGNORECASE ), \
1013 "Design must consume resolved_runtimes verbatim for every non-agent unit"
1014 assert re.search( r " [ Ff ] allback ( only | when ). {0,80} resolved_runtimes is absent | [ Ff ] allback only:" , design), \
1015 "rule re-evaluation must be a fallback, not the primary path"
1016
1017
1018 def test_design_top_level_chosen_runtime_defined ():
1019 # Codex round-20 P2: the legacy mirror must define top-level chosen_runtime (postcondition
1020 # requires it) — the primary unit's resolved runtime.
1021 design = _norm(pathlib.Path( __file__ ).parent.parent
1022 / "references" / "phases" / "design" / "design.md" )
1023 assert re.search( r "top-level ` ? chosen_runtime` ? . {0,120} resolved_runtimes \[ primary_unit \] " ,
1024 design, re. DOTALL ), \
1025 "top-level chosen_runtime must be defined as the primary unit's resolved runtime"
1026
1027
1028 def test_mixed_system_model_less_and_temporal_downstream ():
1029 # Model-less non-agent units still occur in MIXED systems (an agent + a batch/service unit),
1030 # so the consumer-phase handling for model-less units + the temporal fixes survive the
1031 # scope-out; only the pure zero-agent branches were removed.
1032 gen = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "generate" / "generate.md" )
1033 poc = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "poc" / "poc.md" )
1034 pocrep = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "poc" / "poc-report.md" )
1035 est = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "estimate" / "estimate.md" )
1036 bd = (pathlib.Path( __file__ ).parent / "build_diagram.py" ).read_text()
1037 # poc resolves model only when non-null; model-less units omit Bedrock wiring
1038 assert re.search( r "whose ` ? model_recommendation` ? is non-null" , poc), \
1039 "poc Step 2 must resolve model only for non-null model_recommendation"
1040 assert re.search( r " [ Mm ] odel-less units . {0,200} omits all Bedrock wiring" , poc, re. DOTALL ), \
1041 "poc must define the model-less unit path (no Bedrock wiring)"
1042 # generate temporal gate on units[].workload_class, not temporal.units
1043 assert re.search( r "workload_class == . ? temporal_worker_poll . {0,80} temporal . {0,20} block has no . ? units" , gen, re. DOTALL ), \
1044 "generate temporal §3c must gate on units[].workload_class, not temporal.units"
1045 # generate serverless_workers no-card exception
1046 assert re.search( r "serverless_workers . {0,60} NO . {0,20} card" , gen, re. DOTALL ), \
1047 "generate must keep the serverless_workers no-card exception"
1048 # build_diagram omits the model node when model is unknown/None
1049 assert re.search( r 'if model and model != "unknown":' , bd), \
1050 "build_diagram must render the model node only when a real model exists"
1051 # poc-report diagram built per-unit from effective_runtime, not hardcoded AgentCore
1052 assert re.search( r "each on ITS OWN ` ? effective_runtime` ? , NOT hardcoded to AgentCore" , pocrep, re. DOTALL ), \
1053 "poc-report diagram must be built per-unit from effective_runtime"
1054 # estimate: worker unit charges only polling; execution tier not re-added (double-count fix)
1055 assert re.search( r "DOUBLE-COUNT | do NOT also add execution cost into the worker unit" , est, re. IGNORECASE ), \
1056 "estimate must not double-count the execution tier into the worker unit"
1057 # the pure zero-agent branch language must be gone from generate's postcondition
1058 assert "ZERO-AGENT path" not in gen and "no_agent_units" not in gen, \
1059 "generate must not reference the removed zero-agent path"
1060
1061
1062 def test_schema_validates_each_units_value ():
1063 # Codex round-22 #6: the scored wrapper must validate each units[] value as a scored result,
1064 # not accept units:{"broken":{}}.
1065 import json
1066 schema = json.loads((pathlib.Path( __file__ ).parent.parent
1067 / "scripts" / "schemas" / "scoring-result.json" ).read_text())
1068 assert "$defs" in schema and "scoredUnit" in schema[ "$defs" ], \
1069 "schema must define a reusable scoredUnit and apply it to units values"
1070 try :
1071 import jsonschema
1072 except ImportError :
1073 return
1074 V = jsonschema.Draft7Validator(schema)
1075 bare = { "verdict" : "ecs" , "scores" : { "ecs" : 10 }, "eliminated" : {},
1076 "deployment_model" : None , "agentcore_services" : [],
1077 "model_recommendation" : { "model" : "m" , "reasoning" : "r" },
1078 "deferred_verification_requirements" : [],
1079 "recommendation_status" : "final" ,
1080 "assumptions_used" : [], "warnings" : []}
1081 assert V.is_valid({ ** bare, "units" : { "solo" : bare}}), "valid per-unit value must validate"
1082 assert not V.is_valid({ ** bare, "units" : { "broken" : {}}}), \
1083 "a units value missing scored fields must be rejected"
1084
1085
1086 def test_scope_gate_after_temporal_activity_classification ():
1087 # Codex round-25 P1: the scope gate must not halt a no-code Temporal workload before its
1088 # Activities are classified (a temporal_worker_poll-only seed would falsely halt).
1089 clarify = _norm(pathlib.Path( __file__ ).parent.parent
1090 / "references" / "phases" / "clarify" / "clarify.md" )
1091 assert re.search( r " [ Dd ] o NOT halt a Temporal system on the strength of a . ? temporal_worker_poll" ,
1092 clarify), \
1093 "the gate must not halt a Temporal system before its Activities are classified"
1094 assert re.search( r "Activity classification is part of building \s * the inventory . {0,80} BEFORE the scope gate" ,
1095 clarify, re. DOTALL ), \
1096 "no-code Activity classification must run before the scope gate concludes"
1097
1098
1099 def test_poc_shapes_model_less_variants_per_shape ():
1100 # Codex round-25 P1: each runtime shape (ecs/eks/lambda/batch) must have an explicit
1101 # model-less variant, not just the common-contract conditional.
1102 shapes = _norm(pathlib.Path( __file__ ).parent.parent
1103 / "references" / "decision-refs" / "poc-shapes.md" )
1104 # at least the common conditional + per-shape model-less notes
1105 assert shapes.count( "Model-less variant" ) >= 3 , \
1106 "ecs/eks/lambda/batch shapes must each carry an explicit model-less variant note"
1107 assert re.search( r "Bedrock wiring is CONDITIONAL on the unit' ? s ` ? model_recommendation != null" ,
1108 shapes), \
1109 "the common contract must gate Bedrock wiring on model_recommendation != null"
1110 # poc.md prereq: Bedrock model access only for model-bearing units
1111 poc = _norm(pathlib.Path( __file__ ).parent.parent
1112 / "references" / "phases" / "poc" / "poc.md" )
1113 assert re.search( r "Bedrock model access for the \s * resolved model id ONLY when the unit is model-bearing" ,
1114 poc, re. DOTALL ), \
1115 "poc prerequisites must make Bedrock model access conditional on model-bearing units"
1116
1117
1118 def test_round26_temporal_single_workload_and_multiunit_poc_report ():
1119 # Codex round-26: (1) a single-workload Temporal signal still records a draft incl.
1120 # temporal_worker_poll, and the one-unit collapse does not skip Activity classification/gate;
1121 # (2) the POC report renders per-unit runtime/model, not a single global one.
1122 intake = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "intake" / "intake.md" )
1123 clarify = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "clarify" / "clarify.md" )
1124 pocrep = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "poc" / "poc-report.md" )
1125 assert re.search( r "Temporal signal ALWAYS records a draft, even for a single workload" , intake), \
1126 "intake must record a Temporal draft even for a single workload"
1127 assert re.search( r "Temporal Activity interview/classification below and the scope gate are NOT skippable" ,
1128 clarify), \
1129 "the one-unit collapse must not skip the Temporal Activity classification or the scope gate"
1130 assert re.search( r "one node per \s * unit in . ? UNITS . ? , each on ITS OWN . ? effective_runtime" , pocrep, re. DOTALL ), \
1131 "the POC report diagram must render per-unit runtimes, not a single global one"
1132 assert re.search( r "resolved Bedrock model PER UNIT" , pocrep), \
1133 "the POC report must resolve model per unit, not a single global MODEL_DISPLAY"
1134
1135
1136 def test_round26_model_less_prose_conditional ():
1137 # Codex round-26 #3: W5 README, poc goal, and batch key_change must not claim Bedrock for a
1138 # model-less unit.
1139 shapes = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "decision-refs" / "poc-shapes.md" )
1140 poc = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "phases" / "poc" / "poc.md" )
1141 batch = _norm(pathlib.Path( __file__ ).parent.parent / "references" / "decision-refs" / "batch.md" )
1142 assert re.search( r "do not claim Bedrock calls for a \s * model-less unit" , shapes, re. DOTALL ), \
1143 "W5 README claim must be conditional on model-bearing"
1144 assert re.search( r "Never claim . ? Bedrock connectivity . ? for a unit whose . ? model_recommendation . ? is null" , poc), \
1145 "poc goal must not claim Bedrock connectivity for a model-less unit"
1146 assert re.search( r "` ? bedrock:InvokeModel` ? ONLY when the \s * job actually calls a model" , batch, re. DOTALL ), \
1147 "batch key_change source must make InvokeModel conditional on a model-calling job"
1148
1149
1150 def test_round27_service_cards_model_less_conditional ():
1151 # Codex round-27 P2: service-card model prose (batch Six Dimensions, ecs/eks/lambda IAM) must
1152 # not mandate bedrock:InvokeModel unconditionally — Design/Generate consume these for non-agent
1153 # units, so a model-less secondary unit would get false Bedrock IAM. Cards + a Design
1154 # consumption rule now condition Bedrock on model-bearing.
1155 dr = pathlib.Path( __file__ ).parent.parent / "references" / "decision-refs"
1156 for card in [ "ecs.md" , "eks.md" , "lambda.md" , "lambda-microvms.md" ]:
1157 t = _norm(dr / card)
1158 assert re.search( r "bedrock:InvokeModel` ? \( model-bearing units only" , t), \
1159 f " { card } IAM must condition InvokeModel on model-bearing units"
1160 batch = _norm(dr / "batch.md" )
1161 assert re.search( r "model-bearing jobs only" , batch) and \
1162 re.search( r "model-less . {0,80} omits ` ? bedrock:InvokeModel" , batch, re. DOTALL ), \
1163 "batch card must condition its Bedrock items on a model-bearing job"
1164 design = _norm(pathlib.Path( __file__ ).parent.parent
1165 / "references" / "phases" / "design" / "design.md" )
1166 assert re.search( r " [ Mm ] odel-less consumption rule . {0,200} model_recommendation . ? is null" , design, re. DOTALL ), \
1167 "Design must state the model-less card-consumption rule (strip Bedrock items)"