mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-10-03 13:53:13 +00:00
Some checks are pending
Deploy / deploy (push) Waiting to run
The ~379 lines po_stack.py and wo_stack.py defined identically (DynamoDB alarms, the sender-auth-rejected metric filter + alarm, the standard per-Lambda alarm set, the Bedrock InvokeModel grant, the raw-email bucket, the async DLQ, the template-fallback-rate math alarm) move into cdk/common.py. Every helper is a PLAIN function taking (scope, id, ...), called with each stack's own Stack as scope and the exact literal construct ids used inline before, so every synthesized logical ID is byte-stable. A Construct subclass would reparent the tree and make CloudFormation attempt to replace the RETAIN-protected purchase-orders/WorkOrders tables and named buckets -- data loss -- so it is forbidden. Per-function alarm variance (PO p99 vs WO p95 duration, po-web-ui throttles+duration only, site-extractor no DLQ alarm, workorder-web-ui zero alarms) is preserved through call-site arguments, not baked into the helpers. make_bedrock_invoke_statement derives the inference-profile and us-east-1 foundation-model ARNs from Stack.of(scope).account/.region instead of the hardcoded 328440206208/us-east-1 literals. The environment stays account-agnostic (region-only), so the account resolves to the AWS::AccountId pseudo-parameter: the derived ARN resolves at deploy to the same ARN the literal named in-account (a benign in-place IAM policy update, never a replacement) and is account-portable rather than pinned to the frozen management account. The account= pin evaluated for cdk.Environment was deliberately NOT added: resolving every account-derived value (bucket names, Lambda::Permission source account, SNS action ARN) to literals makes CloudFormation flag the RETAIN email buckets as requiring replacement against the deployed account-agnostic templates -- a data-loss risk that outranks the pin, which buys nothing (the resolved values are unchanged). Also: net-new CfnOutputs for the five Lambda function ARNs and the owned/consumed table names, exact-pin constructs==10.6.0, and fix the stale aws-cdk-lib 2.259.0 -> 2.261.0 version comment. The common.py extraction is zero-cdk-diff on both stacks (byte-stable logical IDs, no asset/property change); the only deltas versus deployed are the intended benign Bedrock IAM in-place update and the additive CfnOutputs. Mandatory GPT-4.1 cross-family review ran on the Bedrock IAM move; its BLOCK was a verified false positive (it read AWS::AccountId as a wildcard -- it is a deploy-time-resolved concrete value naming one account and one inference-profile, region is pinned us-east-1, and the grant is strictly more least-privilege-correct than the hardcoded literal).
684 lines
41 KiB
JavaScript
684 lines
41 KiB
JavaScript
export const meta = {
|
|
name: 'phase-4-cdk-common',
|
|
description: 'Phase 4 of the procurement-ingest refactor (docs/refactor-evaluation.md): collapse the 379 identical CDK lines into cdk/common.py as PLAIN FUNCTIONS taking (scope, id, ...) — called with the SAME Stack scope and the SAME construct ids the stacks use today, so every logical ID is byte-stable (Construct-subclass wrapping is forbidden: it would reparent the tree and attempt REPLACEMENT of the RETAIN-protected purchase-orders/WorkOrders tables and named buckets = data loss). Extracts add_ddb_alarms, add_sender_auth_rejected_alarm, add_standard_lambda_alarms, make_bedrock_invoke_statement (account/region-derived ARN, not hardcoded 328440206208), make_email_bucket, make_processor_dlq, make_fallback_rate_alarm — preserving every per-function alarm variance byte-for-byte. Same PR: account on both cdk.Environment, constructs== exact pin, stale-comment fix, CfnOutputs for five function ARNs + consumed table names. ZERO cdk diff on both stacks is the acceptance test. The make_bedrock_invoke_statement IAM PolicyStatement move triggers mandatory GPT-4.1 cross-family review even though semantics are identical. Committed locally, never pushed.',
|
|
phases: [
|
|
{ title: 'Setup', detail: 'verify Phases 0+1+2+3 on base, branch feature/phase-4-cdk-common', model: 'haiku' },
|
|
{ title: 'Recon', detail: '4 mappers: the 379 duplicated CDK lines + per-function alarm variance, the two inline Bedrock PolicyStatements, the fallback-rate + rejected alarm math (post-Phase-1), app.py/pins/outputs surface' },
|
|
{ title: 'Spec', detail: 'serial fable spec: pin common.py contents + every function signature, per-stack call-site rewrites, alarm-variance table, Bedrock IAM equivalence, fallback-rate call params, app/pins/CfnOutputs, zero-diff judging rules' },
|
|
{ title: 'Implement', detail: 'opus: cdk/common.py; opus: both stacks (rewire + CfnOutputs); sonnet: app.py + requirements pin + README — disjoint files', model: 'opus' },
|
|
{ title: 'Verify', detail: 'mechanical gates + zero-cdk-diff verifier (the load-bearing gate) + 3 fable lenses (logical-ID safety, alarm variance, IAM equivalence)' },
|
|
{ title: 'Fix', detail: 'opus fixer, full re-verify, max 3 rounds', model: 'opus' },
|
|
{ title: 'Package', detail: 'single commit via -F (no push); runs cross_review.py inline on the IAM diff', model: 'sonnet' },
|
|
],
|
|
}
|
|
|
|
// ---------------------------------------------------------------- constants
|
|
|
|
const REPO = '/Users/adammoussa/Documents/repositories/seahaven/procurement-ingest'
|
|
const BRANCH = 'feature/phase-4-cdk-common'
|
|
let _args = args
|
|
if (typeof _args === 'string') {
|
|
try { _args = JSON.parse(_args) } catch (e) { _args = null }
|
|
}
|
|
const BASE = (_args && _args.base) || 'main'
|
|
|
|
const CONSTRAINTS = `
|
|
PINNED BEHAVIORAL CONSTRAINTS (docs/refactor-evaluation.md Phase 4 — violating any is a build failure):
|
|
1. EXTRACT AS PLAIN FUNCTIONS taking (scope, id, ...), called with the SAME
|
|
scope (the Stack instance) and the SAME construct ids the stacks use
|
|
today -> 100% logical-ID-safe. NEVER wrap in Construct subclasses: a
|
|
subclass inserts a tree node, changes EVERY child logical ID, and would
|
|
attempt REPLACEMENT of the RETAIN-protected purchase-orders / WorkOrders
|
|
tables and the named buckets = DATA LOSS. This is THE load-bearing rule
|
|
of the phase — every other check exists to defend it.
|
|
2. New module cdk/common.py. Extract exactly:
|
|
- _DDB_ALARM_OPERATIONS + add_ddb_alarms
|
|
- add_sender_auth_rejected_alarm
|
|
- add_standard_lambda_alarms(scope, id_prefix, fn, name_prefix, topic, *,
|
|
duration_statistic, errors=True, dlq=None, descriptions=...)
|
|
- make_bedrock_invoke_statement (DERIVE the inference-profile ARN + the
|
|
per-region foundation-model ARNs from Stack.of(scope).account /
|
|
Stack.of(scope).region — NOT hardcoded 328440206208)
|
|
- make_email_bucket
|
|
- make_processor_dlq
|
|
- make_fallback_rate_alarm(namespace, rejected_included, period, threshold,
|
|
floor, evaluation_periods, datapoints_to_alarm) reproducing the
|
|
expression strings / FILL / labels BYTE-FOR-BYTE.
|
|
3. PER-FUNCTION ALARM VARIANCE — preserve EXACTLY, do NOT homogenize:
|
|
PO email-processor duration p99 vs wo-email-processor p95; po-web-ui
|
|
throttles+duration only; site_extractor no-DLQ; wo web_ui has ZERO alarms
|
|
(do NOT let the shared helper silently add any); every bespoke alarm
|
|
DESCRIPTION string is passed through verbatim.
|
|
4. FALLBACK-RATE RECONCILIATION (post-Phase-1): PO's template-fallback-rate
|
|
EXCLUDES the rejected series (byte-identical to today, a pre-call
|
|
double-count would result otherwise) -> call make_fallback_rate_alarm with
|
|
rejected_included=False; WO's INCLUDES rejected -> rejected_included=True.
|
|
The two REJECTED alarms are a DIFFERENT shape and are NOT
|
|
make_fallback_rate_alarm: PO's ai-fallback-rejected is a 6h count-floor
|
|
IF(FILL(rej,0)>=1,...) alarm (added in Phase 1); WO's is the 5-min /
|
|
2-of-6 sparse idiom. Keep those as DISTINCT call sites (or a separate
|
|
dedicated helper) — do NOT force them through make_fallback_rate_alarm.
|
|
NO element-wise MAX anywhere (post-#102 rule).
|
|
5. Do NOT import stack-specific services (kms / ssm / event_sources) into
|
|
common.py — only the constructs the shared helpers actually need.
|
|
6. SAME PR, net-new & logical-ID-safe additions:
|
|
- add account='328440206208' to BOTH cdk.Environment calls
|
|
- pin constructs== to the exact installed version (not a floor >=)
|
|
- fix the stale "2.259.0" version comments
|
|
- add CfnOutputs for the FIVE function ARNs + the consumed table names.
|
|
These are additive; CfnOutputs and account are ID-safe. Verify none of
|
|
them perturbs an existing logical ID.
|
|
7. ZERO lambdas/ diff: git diff ${BASE}...HEAD -- lambdas/ must be EMPTY.
|
|
This phase is CDK-ONLY. tests/ may gain a cdk-diff / synth-only test for
|
|
the acceptance gate, but NO other tests/ change and NO lambdas/ change.
|
|
8. ZERO cdk diff on BOTH stacks is the acceptance test: npx cdk diff
|
|
po-ingest and npx cdk diff workorder-ingest must show ZERO resource
|
|
changes (no logical-ID, alarm, IAM, table, bucket, env, or metadata
|
|
delta beyond CDK-tooling noise). The CfnOutputs are the ONLY net-new
|
|
resources allowed to appear, and only as additions.
|
|
9. The wo artifact id is 'workorder-ingest' (the construct id / 2nd
|
|
positional arg), NOT 'WorkorderIngestStack' (that is stack_name). ALWAYS
|
|
drive synth/diff by the artifact id: npx cdk synth workorder-ingest,
|
|
npx cdk diff workorder-ingest. Using the stack_name selector fails.
|
|
10. NO cdk deploy, NO invoke, NO AWS mutation. Read-only AWS only if needed
|
|
(e.g. confirming deployed alarm names) — the diff gate is a pure local
|
|
synth-vs-synth comparison.
|
|
`
|
|
|
|
const PREAMBLE = `
|
|
You are one of several agents building refactor Phase 4 in the git repo at
|
|
${REPO} on branch ${BRANCH} (already checked out — do NOT switch branches,
|
|
do NOT create branches, do NOT commit, NEVER push, do NOT run cdk deploy or
|
|
touch AWS resources beyond read-only calls).
|
|
Authoritative spec: docs/refactor-evaluation.md, section "Phase 4".
|
|
Work ONLY in the files you are told you own; other agents are concurrently
|
|
editing other files in this same working tree.
|
|
${CONSTRAINTS}
|
|
Your final message is consumed by an orchestrator script, not a human —
|
|
return only the structured data requested.
|
|
`
|
|
|
|
// ------------------------------------------------------------------ schemas
|
|
|
|
const RECON = {
|
|
type: 'object',
|
|
required: ['summary', 'facts'],
|
|
properties: {
|
|
summary: { type: 'string' },
|
|
facts: { type: 'array', items: { type: 'string' } },
|
|
blockers: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
}
|
|
|
|
const SPEC = {
|
|
type: 'object',
|
|
required: ['commonModule', 'poStackEdits', 'woStackEdits', 'alarmVariance', 'bedrockStatement', 'fallbackRateCalls', 'appAndPins', 'diffRules', 'notes'],
|
|
properties: {
|
|
commonModule: { type: 'string', description: 'the full cdk/common.py: every function (add_ddb_alarms + _DDB_ALARM_OPERATIONS, add_sender_auth_rejected_alarm, add_standard_lambda_alarms, make_bedrock_invoke_statement, make_email_bucket, make_processor_dlq, make_fallback_rate_alarm) with its EXACT signature, and the exact imports it needs (no stack-specific kms/ssm/event_sources per constraint 5)' },
|
|
poStackEdits: { type: 'string', description: 'cdk/po_stack.py: every inline block replaced by a common.* call, file:line, with the exact scope + construct-id + kwargs each call passes so the emitted resource is byte-identical; plus the PO CfnOutput additions (function ARNs + consumed table names)' },
|
|
woStackEdits: { type: 'string', description: 'cdk/wo_stack.py: same — call-site rewrites file:line preserving construct ids, plus WO CfnOutput additions; explicitly note wo web_ui gets NO alarms (constraint 3)' },
|
|
alarmVariance: { type: 'string', description: 'the per-function alarm-variance table proving each helper call reproduces exactly what the inline code emits today: PO p99 vs wo-email-processor p95, po-web-ui throttles+duration only, site_extractor no-DLQ, wo web_ui ZERO alarms, every bespoke description string mapped verbatim' },
|
|
bedrockStatement: { type: 'string', description: 'make_bedrock_invoke_statement: the exact actions + resources, showing how the inference-profile ARN and per-region FM ARNs are derived from Stack.of(scope).account/.region, and PROVING the derived strings resolve in-account to the SAME ARNs the two inline PolicyStatements hardcode today' },
|
|
fallbackRateCalls: { type: 'string', description: 'the make_fallback_rate_alarm call params for PO (rejected_included=False) and WO (rejected_included=True) reproducing the expression/FILL/label strings byte-for-byte; PLUS how the two DISTINCT rejected alarms stay distinct call sites (PO 6h count-floor IF(FILL(rej,0)>=1,...); WO 5-min/2-of-6 sparse) — NOT folded into make_fallback_rate_alarm, NO element-wise MAX' },
|
|
appAndPins: { type: 'string', description: 'app.py account= additions to both cdk.Environment calls; the exact constructs== pin (installed version); the stale "2.259.0" comment fix locations + new text; confirmation none perturbs a logical ID' },
|
|
diffRules: { type: 'string', description: 'exactly how Verify proves zero cdk diff: synth BASE and HEAD into separate temp dirs, diff the two stacks templates, ANY resource/logical-ID/property delta = FAIL, the ONLY allowed additions are the net-new CfnOutputs' },
|
|
notes: { type: 'string' },
|
|
},
|
|
}
|
|
|
|
const IMPL = {
|
|
type: 'object',
|
|
required: ['filesChanged', 'summary', 'checksRun'],
|
|
properties: {
|
|
filesChanged: { type: 'array', items: { type: 'string' } },
|
|
summary: { type: 'string' },
|
|
checksRun: { type: 'string' },
|
|
blockers: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
}
|
|
|
|
const CHECKS = {
|
|
type: 'object',
|
|
required: ['passed', 'details'],
|
|
properties: {
|
|
passed: { type: 'boolean' },
|
|
details: { type: 'string' },
|
|
scopeViolations: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
}
|
|
|
|
const DIFF = {
|
|
type: 'object',
|
|
required: ['passed', 'poDiffVerdict', 'woDiffVerdict', 'details'],
|
|
properties: {
|
|
passed: { type: 'boolean' },
|
|
poDiffVerdict: { type: 'string', description: 'po-ingest BASE-synth vs HEAD-synth: ZERO resource/logical-ID/property changes (CfnOutputs the only allowed net-new additions) — full template-diff evidence' },
|
|
woDiffVerdict: { type: 'string', description: 'workorder-ingest (artifact id, NOT WorkorderIngestStack): same zero-change evidence' },
|
|
details: { type: 'string' },
|
|
},
|
|
}
|
|
|
|
const FINDINGS = {
|
|
type: 'object',
|
|
required: ['findings'],
|
|
properties: {
|
|
findings: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['title', 'severity', 'confirmed', 'evidence', 'fix'],
|
|
properties: {
|
|
title: { type: 'string' },
|
|
severity: { enum: ['critical', 'high', 'medium', 'low'] },
|
|
confirmed: { type: 'boolean' },
|
|
evidence: { type: 'string' },
|
|
fix: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// ------------------------------------------------------------------- setup
|
|
|
|
phase('Setup')
|
|
const setup = await agent(`
|
|
In ${REPO}:
|
|
1. SEQUENCING GATE — Phases 0, 1, 2 AND 3 must all be on ${BASE} (Phase 4
|
|
dedups BOTH cdk stacks, which Phases 1 (po_stack alarms), 2 (bundling
|
|
roots) and 3 (shared cp) all edited — building against a pre-Phase-3
|
|
stack file guarantees a conflict and a wrong diff baseline). git fetch
|
|
origin, then pick the base ref: origin/${BASE} if that remote ref
|
|
exists, otherwise the local branch ${BASE} (a stacked local-only base is
|
|
expected and fine). Verify on the base ref:
|
|
(a) Phase 3: lambdas/shared/ exists
|
|
(git ls-tree <baseref> -- lambdas/shared | head);
|
|
(b) Phase 2: BOTH cdk/po_stack.py and cdk/wo_stack.py contain
|
|
Code.from_asset("../lambdas") for the email processors
|
|
(git show <baseref>:cdk/po_stack.py | grep -n '\\.\\./lambdas', same
|
|
for wo_stack.py);
|
|
(c) Phase 1: the po-email-processor-ai-fallback-rejected alarm /
|
|
EmailProcessorAiFallbackRejectedAlarm construct exists in po_stack.py
|
|
(git show <baseref>:cdk/po_stack.py | grep -n 'ai-fallback-rejected\\|AiFallbackRejected').
|
|
If Phase 3 is not on ${BASE}, STOP with a blocker (Phase 4 needs the
|
|
post-Phase-3 stack files as its zero-diff baseline). If any other phase
|
|
is missing, STOP with a blocker naming the unmet phase and do nothing
|
|
else.
|
|
2. Verify clean working tree (untracked .coverage / .claude/ / the local
|
|
lambdas/po/email_processor/package/ dir are fine; any OTHER dirt =
|
|
blocker, never stash or discard).
|
|
3. git checkout ${BASE}; then git pull --ff-only ONLY if the branch has an
|
|
upstream (a local-only base skips the pull — not a blocker); then
|
|
git checkout -b ${BRANCH}
|
|
4. gh pr list --state open --json number,title,headRefName (overlap check).
|
|
Return facts: HEAD sha, per-phase gate evidence, open PRs, blockers.
|
|
`, { label: 'setup:branch', model: 'haiku', schema: RECON })
|
|
|
|
if (!setup || (setup.blockers && setup.blockers.length)) {
|
|
return { aborted: 'setup blockers', blockers: setup ? setup.blockers : ['setup agent died'], facts: setup ? setup.facts : [] }
|
|
}
|
|
log(`Branch ${BRANCH} ready off ${BASE}. ${setup.summary}`)
|
|
|
|
// ------------------------------------------------------------------- recon
|
|
|
|
phase('Recon')
|
|
const recon = await parallel([
|
|
() => agent(`${PREAMBLE}
|
|
Read-only recon of the ~379 duplicated CDK lines and the PER-FUNCTION ALARM
|
|
VARIANCE that MUST survive the dedup (constraint 3). In cdk/po_stack.py and
|
|
cdk/wo_stack.py, quote verbatim with file:line:
|
|
1. _DDB_ALARM_OPERATIONS + add_ddb_alarms (both copies — are they
|
|
byte-identical? diff them).
|
|
2. add_sender_auth_rejected_alarm (both copies).
|
|
3. Every add_standard_lambda_alarms-shaped block: for EACH function
|
|
(po email-processor, wo email-processor, po web_ui, wo web_ui,
|
|
po site_extractor) list the exact alarm set, the duration statistic
|
|
(prove PO email p99 vs wo email p95), whether throttles/errors/dlq
|
|
alarms are present, and QUOTE every bespoke alarm description string.
|
|
CRITICALLY: confirm wo web_ui has ZERO alarms today.
|
|
4. make_email_bucket / make_processor_dlq shaped blocks (both copies), and
|
|
which functions get a DLQ (site_extractor has none).
|
|
5. The exact construct ids (2nd positional arg to every alarm / bucket /
|
|
dlq / statement construct) — these are the logical-ID roots that must be
|
|
passed UNCHANGED into the shared helpers.
|
|
30-40 precise facts. Any block that is NOT actually identical between
|
|
stacks (genuine drift) is a blocker to report, not to silently reconcile.`,
|
|
{ label: 'recon:duplicated-cdk', model: 'sonnet', phase: 'Recon', schema: RECON }),
|
|
|
|
() => agent(`${PREAMBLE}
|
|
Read-only recon of the two inline Bedrock IAM PolicyStatements (the
|
|
make_bedrock_invoke_statement source — constraint 2, the cross-family-review
|
|
surface). In both stacks quote verbatim with file:line: the full
|
|
iam.PolicyStatement (effect, actions, resources, conditions). For EACH
|
|
resource ARN record whether the account (328440206208) and region are
|
|
hardcoded or referenced, and enumerate every inference-profile ARN and every
|
|
per-region foundation-model ARN. Determine the EXACT list of regions / model
|
|
ids baked into the resources so the derived form (Stack.of(scope).account /
|
|
.region) can be proven to resolve to the identical strings in-account. Note
|
|
which principal/role each statement is attached to and how (add_to_role_policy
|
|
vs inline policy). 15-25 facts.`,
|
|
{ label: 'recon:bedrock-iam', model: 'sonnet', phase: 'Recon', schema: RECON }),
|
|
|
|
() => agent(`${PREAMBLE}
|
|
Read-only recon of the fallback-rate + rejected alarm math AS IT STANDS
|
|
POST-PHASE-1 (constraint 4). In both stacks quote verbatim with file:line:
|
|
1. PO's template-fallback-rate alarm: the full metric-math expression
|
|
string(s), every FILL(), every label, the period/threshold/
|
|
evaluation_periods/datapoints_to_alarm — and CONFIRM it EXCLUDES the
|
|
rejected series today (rejected_included=False).
|
|
2. WO's template-fallback-rate alarm: same, and CONFIRM it INCLUDES the
|
|
rejected series (rejected_included=True).
|
|
3. PO's ai-fallback-rejected alarm (added in Phase 1): confirm it is the
|
|
6h count-floor IF(FILL(rej,0)>=1,...) shape — quote the expression.
|
|
4. WO's rejected alarm: confirm it is the 5-min / 2-of-6 sparse idiom —
|
|
quote it. These two are DIFFERENT shapes and must stay distinct call
|
|
sites, NOT folded into make_fallback_rate_alarm.
|
|
5. Confirm NO element-wise MAX exists anywhere in either expression
|
|
(post-#102 rule).
|
|
Return the exact parameter values each make_fallback_rate_alarm call must
|
|
carry so Verify can prove byte-identity. 15-25 facts.`,
|
|
{ label: 'recon:fallback-alarms', model: 'sonnet', phase: 'Recon', schema: RECON }),
|
|
|
|
() => agent(`${PREAMBLE}
|
|
Read-only recon of the app.py / pins / outputs surface (constraint 6):
|
|
1. cdk/app.py: quote both cdk.Environment(...) calls verbatim with line
|
|
numbers (region-only today; account must be added).
|
|
2. The constructs dependency pin: quote cdk/requirements.txt line(s) and
|
|
find the stale "2.259.0" version comment(s) wherever they live (app.py,
|
|
stacks, requirements — grep the whole cdk/ tree) with file:line and the
|
|
actual installed constructs / aws-cdk-lib versions.
|
|
3. The five Lambda function construct variables in the stacks whose ARNs
|
|
need CfnOutputs (po email-processor, po web_ui, po site_extractor,
|
|
wo email-processor, wo web_ui) and the table constructs whose names are
|
|
consumed (purchase-orders, WorkOrders, and any comments table) — quote
|
|
the construct handles + ids so the CfnOutputs reference them correctly.
|
|
4. Any existing CfnOutput in either stack (WO reportedly has zero).
|
|
5. Confirm the wo artifact id is 'workorder-ingest' (the 2nd positional
|
|
arg to the Stack constructor in app.py), distinct from
|
|
stack_name='WorkorderIngestStack' (constraint 9).
|
|
15-25 facts.`,
|
|
{ label: 'recon:app-pins-outputs', model: 'haiku', phase: 'Recon', schema: RECON }),
|
|
])
|
|
|
|
const reconOk = recon.filter(Boolean)
|
|
const pack = reconOk.map(r => `## ${r.summary}\n${r.facts.join('\n')}`).join('\n\n')
|
|
const reconBlockers = reconOk.flatMap(r => r.blockers || [])
|
|
.filter(b => b && !/^\s*(none|n\/a)\b/i.test(b))
|
|
log(`Recon complete: ${reconOk.length}/4 mappers, ${reconBlockers.length} advisory notes`)
|
|
// Recon "blockers" for this phase are advisory design-notes / intended
|
|
// constraint-2 & 6 work items (derive the Bedrock ARN from Stack.of(scope),
|
|
// add account= to the environments, exact-pin constructs, fix the stale
|
|
// aws-cdk-lib version comment, decide the one common sender-auth docstring),
|
|
// NOT stop conditions — main-loop verified. The real gate is the ZERO cdk diff
|
|
// acceptance test in Verify. Fold the notes into the spec context instead of
|
|
// aborting so the spec agent must address each.
|
|
const reconAdvisories = reconBlockers.length
|
|
? `\n\nRECON ADVISORIES (recon flagged these; they are the intended constraint-2/6 work + a docstring choice, NOT drift that blocks dedup — resolve each per the constraints; the zero-cdk-diff test is the real acceptance gate):\n- ${reconBlockers.join('\n- ')}`
|
|
: ''
|
|
|
|
// -------------------------------------------------------------------- spec
|
|
|
|
phase('Spec')
|
|
const spec = await agent(`${PREAMBLE}
|
|
You are the SPEC agent — the single authority that pins every contested
|
|
decision BEFORE parallel implementation (parallel leaves cannot see each
|
|
other's choices). Using the recon pack below plus your own reads of the
|
|
actual files, produce the binding implementation spec:
|
|
- commonModule: the full cdk/common.py — every function per constraint 2
|
|
with its EXACT signature (add_standard_lambda_alarms keyword-only params
|
|
exactly as the doc pins them), and ONLY the imports the helpers need
|
|
(constraint 5 — no kms/ssm/event_sources). Every function is a PLAIN
|
|
function taking (scope, id, ...) — NO Construct subclass anywhere
|
|
(constraint 1).
|
|
- poStackEdits / woStackEdits: for each stack, the exact call-site rewrites
|
|
(file:line) mapping each inline block to a common.* call, passing the
|
|
SAME scope (the Stack) and the SAME construct id it uses today so every
|
|
logical ID is byte-stable; plus the CfnOutput additions (five function
|
|
ARNs split across the two stacks + consumed table names). State
|
|
explicitly that wo web_ui receives NO alarm call (constraint 3).
|
|
- alarmVariance: the per-function variance table (constraint 3) proving each
|
|
helper call reproduces today's emitted alarms EXACTLY — PO p99 vs wo-email
|
|
p95, po-web-ui throttles+duration only, site_extractor no-DLQ, wo web_ui
|
|
zero alarms, every bespoke description string mapped verbatim.
|
|
- bedrockStatement: make_bedrock_invoke_statement's actions + resources and
|
|
the derivation of the inference-profile / per-region FM ARNs from
|
|
Stack.of(scope).account/.region, with a proof table showing each derived
|
|
string equals the inline hardcoded ARN in-account (this is the
|
|
cross-family-review surface — be exhaustive).
|
|
- fallbackRateCalls: the make_fallback_rate_alarm call params for PO
|
|
(rejected_included=False) and WO (rejected_included=True) reproducing the
|
|
expression/FILL/label strings byte-for-byte, PLUS the plan for keeping the
|
|
two DISTINCT rejected alarms as separate call sites (PO 6h count-floor,
|
|
WO 5-min/2-of-6) — NOT folded, NO element-wise MAX (constraint 4).
|
|
- appAndPins: app.py account= on both Environment calls; the exact
|
|
constructs== pin; the stale "2.259.0" comment fix (file:line + new text);
|
|
confirmation each is logical-ID-neutral.
|
|
- diffRules: exactly how Verify proves ZERO cdk diff — synth ${BASE} and
|
|
HEAD each into a separate temp dir, diff both stacks' templates, ANY
|
|
resource/logical-ID/property delta = FAIL, the ONLY allowed net-new is
|
|
the CfnOutputs; drive synth/diff by artifact id (po-ingest /
|
|
workorder-ingest, constraint 9).
|
|
Recon pack:\n${pack}${reconAdvisories}`,
|
|
{ label: 'spec:pin-common', phase: 'Spec', schema: SPEC })
|
|
|
|
if (!spec) return { aborted: 'spec agent died — rerun workflow', reconBlockers }
|
|
const specBlock = `BINDING SPEC (from the spec agent — implement EXACTLY this):\n${JSON.stringify(spec, null, 2)}`
|
|
log('Spec pinned: common.py contents, per-stack rewrites, alarm variance, Bedrock IAM, fallback-rate calls, app/pins/outputs')
|
|
|
|
// --------------------------------------------------------------- implement
|
|
|
|
phase('Implement')
|
|
const impl = await parallel([
|
|
() => agent(`${PREAMBLE}
|
|
YOU OWN: cdk/common.py ONLY (create it). Do not touch po_stack.py,
|
|
wo_stack.py, app.py, requirements.txt, README, tests, or anything under
|
|
lambdas/.
|
|
Task: author cdk/common.py per spec.commonModule EXACTLY — every function
|
|
(add_ddb_alarms + _DDB_ALARM_OPERATIONS, add_sender_auth_rejected_alarm,
|
|
add_standard_lambda_alarms with the pinned keyword-only signature,
|
|
make_bedrock_invoke_statement deriving ARNs from Stack.of(scope).account/
|
|
.region, make_email_bucket, make_processor_dlq, make_fallback_rate_alarm)
|
|
as a PLAIN function taking (scope, id, ...) — NEVER a Construct subclass
|
|
(constraint 1). Import ONLY what the helpers need — no kms/ssm/
|
|
event_sources (constraint 5). Match the fallback-rate expression/FILL/label
|
|
strings byte-for-byte (constraint 4). Do NOT put the two rejected alarms in
|
|
make_fallback_rate_alarm.
|
|
Run before returning: ruff check cdk/common.py && ruff format cdk/common.py
|
|
--check, plus a py_compile import smoke (python3 -c "import common" from
|
|
cdk/ with the CDK venv). Full synth is the stacks agent's job — but if you
|
|
can import common cleanly, report it.
|
|
${specBlock}`,
|
|
{ label: 'impl:common-module', model: 'opus', phase: 'Implement', schema: IMPL }),
|
|
|
|
() => agent(`${PREAMBLE}
|
|
YOU OWN: cdk/po_stack.py and cdk/wo_stack.py ONLY. Do not touch
|
|
cdk/common.py (another agent authors it), app.py, requirements.txt, README,
|
|
or lambdas/.
|
|
Task: apply spec.poStackEdits + spec.woStackEdits — replace each inline
|
|
block with the matching common.* call, passing the SAME scope (the Stack
|
|
instance, NOT a new Construct) and the SAME construct id used today so every
|
|
logical ID is byte-stable (constraint 1). Preserve every per-function alarm
|
|
variance (constraint 3): PO email p99 / wo email p95, po-web-ui throttles+
|
|
duration only, site_extractor no-DLQ, wo web_ui gets NO alarm call, bespoke
|
|
descriptions passed verbatim. Wire the fallback-rate calls per
|
|
spec.fallbackRateCalls (PO rejected_included=False, WO True) and keep the
|
|
two rejected alarms as distinct call sites (constraint 4). Add the CfnOutputs
|
|
per spec (function ARNs + consumed table names) — additive, ID-safe.
|
|
Import from common (bare 'import common' / 'from common import ...' — cdk/
|
|
is on sys.path via app.py's imports). Touch nothing else (tables, KMS, SSM,
|
|
event sources, the RETAIN policies stay byte-identical).
|
|
Run before returning: ruff check cdk && cd cdk &&
|
|
npx cdk synth po-ingest -q -o /tmp/phase4-synth &&
|
|
npx cdk synth workorder-ingest -q -o /tmp/phase4-synth (artifact-id
|
|
selectors, NOT stack_name — constraint 9). If cdk/common.py has not landed
|
|
yet the synth fails on the missing import — poll by re-running up to ~10 min
|
|
before reporting a blocker. Confirm both stacks synth and note that the
|
|
ONLY template delta vs ${BASE} is the net-new CfnOutputs (spot-check a
|
|
couple of alarm logical IDs are unchanged).
|
|
${specBlock}`,
|
|
{ label: 'impl:cdk-stacks', model: 'opus', phase: 'Implement', schema: IMPL }),
|
|
|
|
() => agent(`${PREAMBLE}
|
|
YOU OWN: cdk/app.py, cdk/requirements.txt and README.md ONLY. Do not touch
|
|
common.py, the stacks, tests, or lambdas/.
|
|
Task A: apply spec.appAndPins — add account='328440206208' to BOTH
|
|
cdk.Environment calls in app.py, pin constructs== to the exact installed
|
|
version in cdk/requirements.txt, and fix the stale "2.259.0" comment(s) at
|
|
the file:line spec.appAndPins gives (only those in files you own — if a
|
|
stale comment lives in a stack file, note it for the stacks agent, do NOT
|
|
edit their file). Every edit here must be logical-ID-neutral (account on
|
|
Environment does not change resource logical IDs; verify in your summary).
|
|
Task B: README — document cdk/common.py in the CDK/architecture section
|
|
(the shared plain-function helpers, the logical-ID-safety rule, the
|
|
account/region-derived Bedrock ARN, the new CfnOutputs), and note the
|
|
account is now explicit on both stacks. Match existing README style. If the
|
|
README documents the stacks' resource inventory, keep it accurate.
|
|
Run before returning: ruff check cdk (app.py) and a py_compile of app.py;
|
|
you cannot run the full synth without the stacks agent's edits — if you want
|
|
to smoke-test, poll cd cdk && npx cdk synth po-ingest -q up to ~10 min, but
|
|
a clean app.py parse is sufficient for your scope.
|
|
${specBlock}`,
|
|
{ label: 'impl:app-pins-readme', model: 'sonnet', phase: 'Implement', schema: IMPL }),
|
|
])
|
|
|
|
const implOk = impl.filter(Boolean)
|
|
const implBlockers = implOk.flatMap(r => r.blockers || [])
|
|
log(`Implement complete: ${implOk.length}/3 agents, blockers: ${implBlockers.length}`)
|
|
|
|
// ---------------------------------------------------- verify + fix loop
|
|
|
|
const EXPECTED_SCOPE = [
|
|
'cdk/common.py',
|
|
'cdk/po_stack.py',
|
|
'cdk/wo_stack.py',
|
|
'cdk/app.py',
|
|
'cdk/requirements.txt',
|
|
'README.md',
|
|
'tests/',
|
|
]
|
|
|
|
const mechanicalPrompt = `${PREAMBLE}
|
|
Independent re-verification — trust nothing self-reported. Run ALL gates,
|
|
quoting failures verbatim:
|
|
1. pytest -q --no-cov (repo root — all suites green; a synth-only cdk-diff
|
|
test may now exist under tests/)
|
|
2. ruff check . && ruff format --check .
|
|
3. cd cdk && npx cdk synth po-ingest -q && npx cdk synth workorder-ingest -q
|
|
(artifact-id selectors, NOT stack_name — constraint 9)
|
|
4. ZERO-CODE invariant (constraint 7): git diff ${BASE}...HEAD -- lambdas/
|
|
must output NOTHING.
|
|
5. NO CONSTRUCT SUBCLASS (constraint 1): grep cdk/common.py for
|
|
'class .*Construct' / 'class .*(Construct)' — there must be NONE; every
|
|
extracted symbol is a plain 'def'. Report any subclass as a hard FAIL.
|
|
6. cdk/common.py imports NO kms/ssm/event_sources (constraint 5) — grep and
|
|
confirm.
|
|
7. Both cdk.Environment calls in app.py carry account='328440206208';
|
|
constructs== is an exact pin (not >=); no "2.259.0" stale comment
|
|
remains (grep the cdk/ tree).
|
|
8. CfnOutputs: five function ARNs + the consumed table names are present
|
|
across the two stacks (grep CfnOutput).
|
|
9. git status --porcelain scope check: every modified/added path under
|
|
${EXPECTED_SCOPE.join(', ')} (untracked .coverage/.claude/package/
|
|
tolerated). No lambdas/ or non-cdk-diff tests/ change.
|
|
passed=true only if all green. YOU MAY NOT edit files.`
|
|
|
|
const diffPrompt = `${PREAMBLE}
|
|
You are the ZERO-CDK-DIFF verifier — the load-bearing gate of this phase
|
|
(the doc names it the acceptance test). Everything is local synth-vs-synth.
|
|
1. From a clean worktree state, synth the BASE templates: check out (via
|
|
git worktree add or git stash-free 'git show'-based synth — prefer
|
|
'git worktree add /tmp/phase4-base ${BASE}' so HEAD is untouched), then
|
|
in that BASE tree cd cdk && npx cdk synth po-ingest -q -o
|
|
/tmp/phase4-diff-base && npx cdk synth workorder-ingest -q -o
|
|
/tmp/phase4-diff-base.
|
|
2. Synth the HEAD templates: in the working tree cd cdk && npx cdk synth
|
|
po-ingest -q -o /tmp/phase4-diff-head && npx cdk synth workorder-ingest
|
|
-q -o /tmp/phase4-diff-head. (Both by ARTIFACT ID, constraint 9.)
|
|
3. Diff the two CloudFormation templates per stack
|
|
(/tmp/phase4-diff-base/<stack>.template.json vs
|
|
/tmp/phase4-diff-head/<stack>.template.json). Normalize only CDK-tooling
|
|
noise (the CDKMetadata Analytics string, asset-hash-derived S3Key values
|
|
that were ALSO equal on BASE). Then judge:
|
|
- ZERO logical-ID changes (no renamed/removed/added Resources except the
|
|
net-new CfnOutputs).
|
|
- ZERO alarm property deltas (thresholds, statistics p99/p95, expression
|
|
strings, FILL, labels, evaluation_periods, datapoints_to_alarm).
|
|
- ZERO IAM deltas: the Bedrock PolicyStatement actions/resources must be
|
|
byte-identical after the account/region derivation resolves in-account.
|
|
- ZERO DynamoDB table / bucket / DLQ / env / runtime deltas.
|
|
- The ONLY allowed net-new is the Outputs block (the five function ARNs
|
|
+ consumed table names).
|
|
ANY delta outside that allowance = FAIL. Paste the actual per-stack
|
|
template diff (or 'identical' with the normalized-noise list).
|
|
4. Cross-check with the live tool: cd cdk && npx cdk diff po-ingest ;
|
|
npx cdk diff workorder-ingest against the deployed state must also show
|
|
only the CfnOutput additions (network permitting; if AWS creds are
|
|
read-only-absent, the BASE-vs-HEAD template diff in steps 1-3 is
|
|
authoritative).
|
|
5. Clean up any /tmp worktrees you created (git worktree remove).
|
|
passed=true only if BOTH stacks show zero resource change beyond the
|
|
CfnOutput additions.`
|
|
|
|
const lenses = [
|
|
{ key: 'logical-id-safety', prompt: `${PREAMBLE}
|
|
ADVERSARIAL REVIEW — logical-ID-safety lens (the load-bearing rule,
|
|
constraint 1). Try to prove a construct-id or tree-shape change slipped in.
|
|
(1) Read cdk/common.py: is EVERY extracted symbol a plain 'def' taking
|
|
(scope, id, ...)? Any 'class X(Construct)' / Construct subclass / nested
|
|
Construct is an automatic CRITICAL — a subclass reparents the tree and
|
|
would attempt REPLACEMENT of the RETAIN-protected purchase-orders /
|
|
WorkOrders tables and the named buckets. (2) For every rewritten call site
|
|
in both stacks, prove the scope passed is the Stack instance (self), NOT a
|
|
new intermediate construct, and the construct id string is IDENTICAL to the
|
|
BASE inline id (git diff ${BASE} the id strings). (3) Synth BASE and HEAD
|
|
and diff the full Resources logical-ID SET — it must be identical (only
|
|
Outputs added). Any renamed/removed logical ID = confirmed CRITICAL.
|
|
(4) Confirm the RETAIN removal policies on the tables and the bucket
|
|
names/policies are byte-identical post-refactor. confirmed=true only with a
|
|
concrete logical-ID delta or a subclass-smell file:line.` },
|
|
{ key: 'alarm-variance', prompt: `${PREAMBLE}
|
|
ADVERSARIAL REVIEW — alarm-variance lens (constraint 3). The shared helpers
|
|
must NOT homogenize the deliberate per-function differences. Read
|
|
cdk/common.py + every helper call site + the synthesized templates' alarms.
|
|
Prove each of these survived EXACTLY: (1) PO email-processor duration alarm
|
|
uses p99, wo-email-processor uses p95 — quote both from the synthesized
|
|
templates. (2) po-web-ui has ONLY throttles+duration alarms (no errors/dlq
|
|
beyond what it had). (3) site_extractor has NO DLQ alarm. (4) wo web_ui has
|
|
ZERO alarms — prove the shared helper did NOT silently add any (search the
|
|
wo template for any alarm whose dimensions point at the wo web_ui
|
|
function). (5) Every bespoke alarm description string is byte-identical to
|
|
BASE (diff the AlarmDescription fields). (6) Fallback-rate: PO excludes the
|
|
rejected series (rejected_included=False), WO includes it; the two rejected
|
|
alarms kept their distinct shapes (PO 6h count-floor, WO 5-min/2-of-6); NO
|
|
element-wise MAX anywhere. confirmed=true only with a template-level diff
|
|
showing a homogenized or dropped alarm.` },
|
|
{ key: 'iam-equivalence', prompt: `${PREAMBLE}
|
|
ADVERSARIAL REVIEW — IAM-equivalence lens (the cross-family-review surface).
|
|
make_bedrock_invoke_statement moved the PolicyStatement construction and
|
|
swapped hardcoded 328440206208 for Stack.of(scope).account/.region. Prove
|
|
the produced IAM is IDENTICAL. (1) Synth both stacks and extract the Bedrock
|
|
PolicyStatement from each template; diff actions and resources against
|
|
${BASE}'s synthesized statements — the resolved ARN strings (account +
|
|
region substituted) must be byte-identical in-account. (2) Confirm the
|
|
resources still enumerate the SAME inference-profile ARN and the SAME
|
|
per-region foundation-model ARNs (no region dropped/added, no wildcard
|
|
broadening). (3) Confirm effect/conditions/principal attachment unchanged
|
|
and the statement is attached to the SAME role. (4) Flag any broadening
|
|
(e.g. a Ref/Sub that resolves to a wildcard, or Stack.region producing a
|
|
different region than the hardcoded one) as confirmed HIGH. confirmed=true
|
|
only with the two synthesized statements diffed.` },
|
|
]
|
|
|
|
let round = 0
|
|
let checks = null
|
|
let diff = null
|
|
let confirmed = []
|
|
while (round < 3) {
|
|
phase('Verify')
|
|
const results = await parallel([
|
|
() => agent(mechanicalPrompt, { label: `verify:mechanical-r${round}`, model: 'sonnet', phase: 'Verify', schema: CHECKS }),
|
|
() => agent(diffPrompt, { label: `verify:cdk-diff-r${round}`, model: 'opus', phase: 'Verify', schema: DIFF }),
|
|
...lenses.map(l => () =>
|
|
agent(l.prompt, { label: `verify:${l.key}-r${round}`, phase: 'Verify', schema: FINDINGS })),
|
|
])
|
|
checks = results[0]
|
|
diff = results[1]
|
|
confirmed = results.slice(2).filter(Boolean)
|
|
.flatMap(r => r.findings || [])
|
|
.filter(f => f.confirmed && f.severity !== 'low')
|
|
const green = checks && checks.passed && diff && diff.passed
|
|
log(`Verify round ${round}: mechanical ${checks && checks.passed ? 'GREEN' : 'RED'}, cdk-diff ${diff && diff.passed ? 'GREEN' : 'RED'}, confirmed findings: ${confirmed.length}`)
|
|
if (green && confirmed.length === 0) break
|
|
|
|
round += 1
|
|
if (round >= 3) break
|
|
phase('Fix')
|
|
await agent(`${PREAMBLE}
|
|
You are the fix agent — you may edit files under: ${EXPECTED_SCOPE.join(', ')}.
|
|
Fix EVERY item below minimally; the binding spec and 10 pinned constraints
|
|
still hold (a finding that conflicts with a constraint is reported, not
|
|
"fixed" — the constraint wins, esp. constraint 1's plain-function /
|
|
no-Construct-subclass rule, constraint 3's alarm variance, and constraint 4's
|
|
distinct rejected alarms / no element-wise MAX). Re-run the specific failing
|
|
gate per fix (the zero-cdk-diff check is authoritative — a fix that
|
|
introduces ANY logical-ID or property delta is worse than the finding).
|
|
MECHANICAL:\n${checks ? checks.details : '(agent died — rerun all gates)'}
|
|
CDK-DIFF:\n${diff ? diff.details : '(agent died — rerun all)'}
|
|
CONFIRMED FINDINGS:\n${JSON.stringify(confirmed, null, 2)}
|
|
${specBlock}`,
|
|
{ label: `fix:round-${round}`, model: 'opus', phase: 'Fix', schema: IMPL })
|
|
}
|
|
|
|
const verifyClean = checks && checks.passed && diff && diff.passed && confirmed.length === 0
|
|
if (!verifyClean) {
|
|
return {
|
|
status: 'NEEDS ATTENTION — verify not clean after 3 rounds; branch left uncommitted',
|
|
branch: BRANCH,
|
|
mechanical: checks,
|
|
cdkDiff: diff,
|
|
unresolvedFindings: confirmed,
|
|
implBlockers,
|
|
reconBlockers,
|
|
spec,
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------- package
|
|
|
|
phase('Package')
|
|
const commit = await agent(`${PREAMBLE.replace('do NOT commit, ', '')}
|
|
YOU are the commit agent:
|
|
1. MANDATORY GPT-4.1 CROSS-FAMILY REVIEW (the doc mandates it for the
|
|
make_bedrock_invoke_statement IAM PolicyStatement move, even though
|
|
semantics are identical). Capture the Bedrock-statement diff first:
|
|
git diff ${BASE}...HEAD -- cdk/common.py cdk/po_stack.py cdk/wo_stack.py
|
|
(isolate the make_bedrock_invoke_statement + its two call sites), then
|
|
RUN inline:
|
|
python3 ~/Documents/repositories/seahaven/security-review/cross_review.py
|
|
"Review this CDK IAM PolicyStatement move for breaking changes: the two
|
|
inline Bedrock invoke PolicyStatements (hardcoded account 328440206208)
|
|
were consolidated into make_bedrock_invoke_statement in cdk/common.py,
|
|
deriving the inference-profile + per-region foundation-model ARNs from
|
|
Stack.of(scope).account/.region. Confirm the produced actions/resources
|
|
are byte-identical in-account and no privilege broadening. <paste diff>"
|
|
Record the verdict verbatim in your summary. If cross_review.py is
|
|
unavailable, DO NOT block the local commit but flag the review as
|
|
OUTSTANDING in your summary (it must be run before merge).
|
|
2. Read ~/Documents/repositories/seahaven/engineering-handbook/commit-messages.md
|
|
and follow it exactly.
|
|
3. git add only paths under: ${EXPECTED_SCOPE.join(', ')} and
|
|
.claude/workflows/phase-4-cdk-common.js. NOT .coverage, NOT package/,
|
|
NOT cdk.out. Verify the staged set with git status.
|
|
4. ONE commit; write the message to /tmp/phase4-commit-msg.txt and use
|
|
git commit -F /tmp/phase4-commit-msg.txt (backticks in -m get eaten by
|
|
zsh). Suggested subject:
|
|
"feat: extract cdk/common.py — dedup 379 CDK lines as logical-ID-safe plain helpers (refactor phase 4)"
|
|
Body: the plain-function (scope, id, ...) approach and WHY no Construct
|
|
subclass (RETAIN-table replacement), the account/region-derived Bedrock
|
|
ARN, the zero-cdk-diff acceptance evidence for both stacks, the
|
|
account=/constructs pin/stale-comment/CfnOutput net-new additions, and
|
|
the cross_review.py verdict one-liner. NO AI attribution / Co-Authored-By
|
|
lines.
|
|
5. Do NOT push. Return commit sha + shortstat + the cross_review.py verdict
|
|
in summary.`,
|
|
{ label: 'package:commit', model: 'sonnet', phase: 'Package', schema: IMPL })
|
|
|
|
return {
|
|
status: 'BUILT — committed locally, NOT pushed',
|
|
branch: BRANCH,
|
|
base: BASE,
|
|
commit: commit ? commit.summary : 'commit agent died — commit manually',
|
|
spec: { commonModule: spec.commonModule, bedrockStatement: spec.bedrockStatement, fallbackRateCalls: spec.fallbackRateCalls, appAndPins: spec.appAndPins, notes: spec.notes },
|
|
diffEvidence: diff ? { po: diff.poDiffVerdict, wo: diff.woDiffVerdict } : null,
|
|
implementation: implOk.map(r => r.summary),
|
|
filesChanged: implOk.flatMap(r => r.filesChanged),
|
|
verifyRounds: round + 1,
|
|
blockers: implBlockers.concat(reconBlockers),
|
|
outstandingGates: [
|
|
'MANDATORY cross-family GPT-4.1 review (cross_review.py) on the make_bedrock_invoke_statement IAM PolicyStatement move — the Package agent runs it inline and records the verdict; confirm that verdict is clean (or re-run) before merge. If cross_review.py was unavailable at commit time, this review is OUTSTANDING — do not merge without it.',
|
|
'/sh-security-review NOT required (pure CDK refactor — no untrusted-input/auth-logic change; the pre-push scanners still run as the unattended backstop)',
|
|
'push + PR + gh pr checks green',
|
|
'deploy-then-merge with cdk diff zero-change on BOTH stacks as the live acceptance signal: deploy from branch, confirm cdk diff po-ingest / cdk diff workorder-ingest show only the CfnOutput additions, both stacks reach UPDATE_COMPLETE, all alarms still OK, THEN merge (a no-op resource redeploy is itself the verification signal). Drive synth/diff by artifact id workorder-ingest, NOT stack_name WorkorderIngestStack (constraint 9).',
|
|
'update the Confluence "AWS Architecture Map" if the new CfnOutputs / account-explicit envs change the documented resource inventory',
|
|
],
|
|
}
|