export const meta = { name: 'phase-0-deploy-guards', description: 'Phase 0 of the procurement-ingest refactor (docs/refactor-evaluation.md): healthcheck early-return in both email processors, synchronous post-deploy smoke script wired into cd-cdk, PO cp-allowlist replaced with a non-recursive glob, and an AST bundle-consistency test. Built and adversarially verified on a branch off main, committed locally, never pushed (push is gated on /sh-security-review in the main loop).', phases: [ { title: 'Setup', detail: 'clean-tree check, branch feature/phase-0-deploy-guards off up-to-date main', model: 'haiku' }, { title: 'Recon', detail: '4 parallel read-only mappers over handlers, CDK/alarms/CI, and test infra', model: 'haiku' }, { title: 'Implement', detail: 'handlers on opus; smoke script, CDK glob + AST test on sonnet — disjoint file ownership, one branch', model: 'opus' }, { title: 'Verify', detail: 'mechanical gates (pytest/ruff/synth/scope) on sonnet + 3 adversarial fable lenses' }, { title: 'Fix', detail: 'opus fixer applies confirmed findings, full re-verify, max 3 rounds', model: 'opus' }, { title: 'Package', detail: 'README update, GPT-4.1 cross-family review of the diff, single commit via -F (no push)', model: 'sonnet' }, ], } // ---------------------------------------------------------------- constants const REPO = '/Users/adammoussa/Documents/repositories/seahaven/procurement-ingest' const BRANCH = 'feature/phase-0-deploy-guards' // Pinned contract (memory lesson: define the shared contract BEFORE the // parallel fan-out — parallel leaves can't see each other's choices). const CONTRACT = ` HEALTHCHECK CONTRACT — pinned, do not deviate or "improve": - Trigger: top-level direct-invoke payload only. In lambda handler ("handler" in handler.py), the VERY FIRST statements: if the event is a dict and event.get("healthcheck") is True -> return {"healthcheck": "ok"} immediately. - Placement: BEFORE any boto3/S3 use, BEFORE ses_auth, BEFORE iterating event["Records"]. It must not create any accept path for mail: real mail events are S3 ObjectCreated events whose top-level keys AWS controls ("Records"); email content can never set a top-level event key. - Telemetry: the healthcheck branch emits NO EMF metrics and NO log line whose text could match the sender-auth-rejected metric-filter pattern (read the filter pattern in cdk/*_stack.py before choosing any log text; safest is a single log line exactly "healthcheck ok" or no logging). That alarm pages at >=1 match, so two deploys in ~30 min must not page. - SMOKE SCRIPT CONTRACT: scripts/post-deploy-smoke.sh invokes BOTH functions (po-email-processor, workorder-email-processor) with payload '{"healthcheck": true}' using: aws lambda invoke --invocation-type RequestResponse. It must check the FunctionError field of the response (an init ImportError returns HTTP 200 + FunctionError=Unhandled — exit-code checks false-pass) AND that the payload equals {"healthcheck": "ok"}. Non-zero exit on any failure; set -euo pipefail; region us-east-1. ` const PREAMBLE = ` You are one of several agents building refactor Phase 0 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; read-only aws CLI calls are also unnecessary). Authoritative spec: docs/refactor-evaluation.md, section "Phase 0". Work ONLY in the files you are told you own. Other agents are concurrently editing other files in this same working tree — do not read-depend on or modify their files. ${CONTRACT} 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 IMPL = { type: 'object', required: ['filesChanged', 'testsAdded', 'summary', 'checksRun'], properties: { filesChanged: { type: 'array', items: { type: 'string' } }, testsAdded: { type: 'array', items: { type: 'string' } }, summary: { type: 'string' }, checksRun: { type: 'string', description: 'exact commands run + pass/fail' }, blockers: { type: 'array', items: { type: 'string' } }, }, } const CHECKS = { type: 'object', required: ['passed', 'details'], properties: { passed: { type: 'boolean' }, details: { type: 'string', description: 'per-gate results; verbatim failure output' }, scopeViolations: { type: 'array', items: { 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', description: 'true only with concrete file:line evidence' }, evidence: { type: 'string' }, fix: { type: 'string' }, }, }, }, }, } const TEXT = { type: 'object', required: ['summary'], properties: { summary: { type: 'string' }, verdict: { type: 'string' } }, } // ------------------------------------------------------------------- setup phase('Setup') const setup = await agent(` In ${REPO}: verify the working tree is clean apart from untracked .coverage and docs/refactor-evaluation.md (if anything ELSE is dirty, STOP and report a blocker — do not stash or discard anything). Then: git fetch origin && git checkout main && git pull --ff-only git checkout -b ${BRANCH} Also run: gh pr list --state open --json number,title,headRefName (memory lesson: a branch cut fresh from main misses fixes sitting in unmerged PRs — list them so the orchestrator can flag overlaps). Return facts: current HEAD sha, branch created y/n, open PR list, blockers. `, { label: 'setup:branch', model: 'haiku', schema: RECON }) if (setup && setup.blockers && setup.blockers.length) { return { aborted: 'setup blockers', blockers: setup.blockers, openPRs: setup.facts } } log(`Branch ${BRANCH} ready. ${setup ? setup.summary : ''}`) // ------------------------------------------------------------------- recon phase('Recon') const recon = await parallel([ () => agent(`${PREAMBLE} Read-only recon of lambdas/po/email_processor/handler.py and its tests dir. Report: exact def line of the lambda handler; the first statements it executes (S3 fetch? ses_auth call? Records iteration?) with line numbers; how existing handler tests load the module and fake AWS (loader idiom, moto import-order invariant in _po_parser_support.py); where a healthcheck test would naturally live; any existing early-return branches. 10-20 precise facts.`, { label: 'recon:po-handler', model: 'haiku', phase: 'Recon', schema: RECON }), () => agent(`${PREAMBLE} Read-only recon of lambdas/wo/email_processor/handler.py and its tests dir. Same report shape: handler def line, first statements executed with line numbers, test loader idiom (_wo_parser_support.py), where a healthcheck test lives, existing early returns. 10-20 precise facts.`, { label: 'recon:wo-handler', model: 'haiku', phase: 'Recon', schema: RECON }), () => agent(`${PREAMBLE} Read-only recon of cdk/po_stack.py, cdk/wo_stack.py and .github/workflows/. Report: (1) the exact PO bundling command incl. the cp allowlist at po_stack.py:~245-256 and the full file list of lambdas/po/email_processor/*.py so the glob replacement can be proven identical-output; (2) the exact sender-auth-rejected metric FILTER PATTERNS in both stacks (quote them verbatim) and their alarm eval windows; (3) deploy.yaml's inputs to the cd-cdk reusable workflow — fetch the pinned workflow file with 'gh api repos/Sea-Haven-Industries/.github/contents/.github/workflows/cd-cdk.yaml?ref=fd60e4c9041784f666ac0fdefb9bec3c7fbf5143' (base64 -d the content) and confirm whether a post-deploy-script input exists and its semantics (cwd, when it runs, failure handling). If it does NOT exist, report that as a blocker with the closest available mechanism. (4) WO bundling command for comparison. 15-25 precise facts.`, { label: 'recon:cdk-ci', model: 'sonnet', phase: 'Recon', schema: RECON }), () => agent(`${PREAMBLE} Read-only recon of test infrastructure: pytest.ini, tests/ (repo root), tests/conftest.py, how CI (.github/workflows/ci.yaml) invokes pytest/ruff. Report: how a NEW repo-root test file (tests/test_bundle_consistency.py) would be collected; what it can import; whether tests/ has helpers for locating lambda dirs; the ruff invocation used in CI. 8-15 precise facts.`, { label: 'recon:tests-ci', model: 'haiku', phase: 'Recon', schema: RECON }), ]) const reconOk = recon.filter(Boolean) const reconBlockers = reconOk.flatMap(r => r.blockers || []) const pack = reconOk.map(r => `## ${r.summary}\n${r.facts.join('\n')}`).join('\n\n') log(`Recon complete: ${reconOk.length}/4 mappers, ${reconBlockers.length} blockers`) // --------------------------------------------------------------- implement phase('Implement') const implTasks = [ { label: 'impl:po-healthcheck', model: 'opus', prompt: `${PREAMBLE} YOU OWN: lambdas/po/email_processor/handler.py and NEW test file(s) under lambdas/po/email_processor/tests/ ONLY. Task: add the healthcheck early-return branch to the PO handler exactly per the pinned contract. Add tests: (1) {"healthcheck": true} returns {"healthcheck": "ok"} with ZERO S3 calls, ZERO ses_auth calls, ZERO DynamoDB writes, ZERO metric emission (assert via monkeypatch/fakes per the existing loader idiom — preserve the moto-before-handler import ordering); (2) a normal S3 mail event is completely unaffected (an existing golden-path test still passing is necessary but ALSO assert a mail event containing the string "healthcheck" in its email body does NOT take the branch). Run before returning: ruff check lambdas/po, ruff format lambdas/po --check, pytest lambdas/po/email_processor/tests -q --no-cov. Recon context:\n${pack}` }, { label: 'impl:wo-healthcheck', model: 'opus', prompt: `${PREAMBLE} YOU OWN: lambdas/wo/email_processor/handler.py and NEW test file(s) under lambdas/wo/email_processor/tests/ ONLY. Task: identical healthcheck branch + tests for the WO handler, per contract, mirroring the PO task one-for-one but using the WO test loader idiom. Run before returning: ruff check lambdas/wo, ruff format lambdas/wo --check, pytest lambdas/wo/email_processor/tests -q --no-cov. Recon context:\n${pack}` }, { label: 'impl:smoke-script', model: 'sonnet', prompt: `${PREAMBLE} YOU OWN: scripts/post-deploy-smoke.sh (new) and .github/workflows/deploy.yaml ONLY. Task: write the synchronous smoke script per the pinned SMOKE SCRIPT CONTRACT (both functions, RequestResponse, FunctionError field check + payload check, set -euo pipefail, executable bit, shellcheck-clean). Wire it into deploy.yaml via the cd-cdk reusable workflow's post-deploy-script input per the recon facts below — if recon reported that input missing, implement the closest mechanism recon identified and record a blocker note instead of inventing workflow inputs. Do NOT restructure deploy.yaml otherwise. Run before returning: bash -n scripts/post-deploy-smoke.sh, and python3 -c "import yaml,sys;yaml.safe_load(open('.github/workflows/deploy.yaml'))". Recon context:\n${pack}` }, { label: 'impl:bundle-glob-ast', model: 'sonnet', prompt: `${PREAMBLE} YOU OWN: cdk/po_stack.py (ONLY the email-processor bundling command block, ~lines 241-258) and tests/test_bundle_consistency.py (new) ONLY. Task A: replace the four-file cp allowlist with a non-recursive glob: "cp ./*.py /asset-output/". Keep the pip install line untouched. Update the warning comment to explain the glob + that the AST test now enforces consistency. PROVE identical output: list lambdas/po/email_processor/*.py (non-recursive) and confirm the set equals {handler, ses_auth, template_parser, derived_fields}.py plus any other top-level .py that SHOULD ship; if extras exist (e.g. prompts or __init__), state explicitly whether shipping them is a no-op and why. tests/ and package/ are directories, so a non-recursive ./*.py glob never matches them. Task B: write tests/test_bundle_consistency.py: for EACH pipeline (po, wo), ast-parse email_processor/handler.py, collect top-level "import X" / "from X import ..." names, filter to first-party siblings (X.py exists in the same dir), and assert the bundling guarantee ships them — for PO: assert the po_stack.py bundling command contains the glob "cp ./*.py"; for WO: assert its bundling command copies them (read wo_stack.py to see its current cp -r form and assert accordingly). The test must FAIL if someone reverts the glob to an allowlist missing a sibling — include a unit-level check that simulates an allowlist command string missing derived_fields.py and asserts the detection logic catches it. No AWS/boto3/synth in the test — pure ast + file reads, fast. Run before returning: ruff check cdk tests, pytest tests/test_bundle_consistency.py -q --no-cov. Recon context:\n${pack}` }, ] const impl = await parallel(implTasks.map(t => () => agent(t.prompt, { label: t.label, model: t.model, phase: 'Implement', schema: IMPL }))) const implOk = impl.filter(Boolean) const implBlockers = implOk.flatMap(r => r.blockers || []) log(`Implement complete: ${implOk.length}/4 agents, blockers: ${implBlockers.length}`) // ---------------------------------------------------- verify + fix loop const EXPECTED_SCOPE = [ 'lambdas/po/email_processor/handler.py', 'lambdas/po/email_processor/tests/', 'lambdas/wo/email_processor/handler.py', 'lambdas/wo/email_processor/tests/', 'scripts/post-deploy-smoke.sh', '.github/workflows/deploy.yaml', 'cdk/po_stack.py', 'tests/test_bundle_consistency.py', ] const mechanicalPrompt = `${PREAMBLE} Independent re-verification (trust-but-verify — do not rely on implementers' self-reports). Run ALL of, reporting each verbatim on failure: 1. pytest -q --no-cov (repo root — all 3 roots, expect ~568+new all green) 2. ruff check . 3. ruff format --check . 4. npx cdk synth po-ingest -q && npx cdk synth workorder-ingest -q (run inside cdk/; selectors are ARTIFACT IDs, not stack_name) 5. bash -n scripts/post-deploy-smoke.sh; test -x scripts/post-deploy-smoke.sh 6. git status --porcelain — every modified/added path must fall under: ${EXPECTED_SCOPE.join(', ')} (plus untracked .coverage, docs/refactor-evaluation.md, .claude/workflows/). List violations. 7. Grep the healthcheck branch in both handlers: confirm it precedes any S3 get_object and any ses_auth call by line number. YOU MAY NOT edit any file. passed=true only if every gate is green and scope is clean.` const lenses = [ { key: 'security', prompt: `${PREAMBLE} ADVERSARIAL REVIEW — security lens. Try to REFUTE the safety of this diff (git diff main). Attack: (1) can any S3/SES-delivered email reach the healthcheck branch or any other new early-return (top-level key forgery via event shape, weird typing like event={"healthcheck":"true"} vs True)? (2) does placement before ses_auth weaken fail-closed behavior in ANY path? (3) does the smoke script or deploy.yaml change introduce injection (unquoted vars, payload echoed into shell)? (4) does the AST test import or execute handler code (it must not)? confirmed=true ONLY with a concrete exploit sketch + file:line.` }, { key: 'telemetry', prompt: `${PREAMBLE} ADVERSARIAL REVIEW — telemetry/alarm lens. Read the sender-auth-rejected metric FILTER PATTERNS in cdk/po_stack.py and cdk/wo_stack.py verbatim, then try to prove a healthcheck invocation (incl. Lambda platform START/REPORT lines and any new log text) produces a filter match — that alarm pages at >=1. Also verify: zero EMF emission on the healthcheck path; ParseMethod metric contract untouched (PO emits ai_fallback BEFORE the Bedrock call — must not have moved); no alarm/math changes snuck into po_stack.py beyond the bundling block. confirmed=true only with file:line evidence.` }, { key: 'bundling', prompt: `${PREAMBLE} ADVERSARIAL REVIEW — bundling/CI lens. (1) Enumerate lambdas/po/email_processor/*.py and prove the new glob ships EXACTLY the right set vs the old allowlist {handler,ses_auth,template_parser, derived_fields}.py — flag any extra top-level .py whose shipping is NOT a proven no-op, and confirm tests/ + package/ cannot match a non-recursive ./*.py. (2) Mutation-test the AST consistency test: temporarily copy the detection logic and feed it an allowlist string missing derived_fields.py — does it fail? (do this in /tmp scratch, not by editing repo files). (3) Smoke script: does it actually catch FunctionError on HTTP 200 (trace the aws CLI output handling — --query vs jq parsing), and does a missing function name or region default break it? (4) deploy.yaml: is the post-deploy wiring consistent with the cd-cdk reusable workflow's actual input names (re-fetch the pinned file via gh api if needed)? confirmed=true only with evidence.` }, ] let round = 0 let checks = 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 }), ...lenses.map(l => () => agent(l.prompt, { label: `verify:${l.key}-r${round}`, phase: 'Verify', schema: FINDINGS })), ]) checks = results[0] confirmed = results.slice(1).filter(Boolean) .flatMap(r => r.findings || []) .filter(f => f.confirmed && (f.severity === 'critical' || f.severity === 'high' || f.severity === 'medium')) const mechanicalGreen = checks && checks.passed log(`Verify round ${round}: mechanical ${mechanicalGreen ? 'GREEN' : 'RED'}, confirmed findings: ${confirmed.length}`) if (mechanicalGreen && confirmed.length === 0) break round += 1 if (round >= 3) break phase('Fix') await agent(`${PREAMBLE} You are the fix agent — you may edit any Phase-0-owned file listed here: ${EXPECTED_SCOPE.join(', ')}. Fix EVERY item below with the minimal change; do not expand scope; keep the pinned contract intact. Re-run the specific failing check/test for each fix. MECHANICAL FAILURES:\n${checks ? checks.details : '(mechanical agent died — rerun everything)'} CONFIRMED FINDINGS:\n${JSON.stringify(confirmed, null, 2)}`, { label: `fix:round-${round}`, model: 'opus', phase: 'Fix', schema: IMPL }) } const verifyClean = checks && checks.passed && confirmed.length === 0 if (!verifyClean) { return { status: 'NEEDS ATTENTION — verify not clean after 3 rounds; branch left uncommitted', branch: BRANCH, mechanical: checks, unresolvedFindings: confirmed, implBlockers, reconBlockers, openPRs: setup ? setup.facts : [], } } // ----------------------------------------------------------------- package phase('Package') const readme = await agent(`${PREAMBLE} YOU OWN: README.md only. Document (in the style of the existing README): the {"healthcheck": true} direct-invoke contract on both processors, the post-deploy smoke gate (what it checks, that FunctionError is the signal), and the PO bundling glob + AST consistency test (replacing the allowlist note if one exists). Same-commit README updates are a handbook requirement. Run: ruff format --check . still clean (README is md, but confirm no stray edits). Return filesChanged.`, { label: 'package:readme', model: 'sonnet', phase: 'Package', schema: IMPL }) const crossReview = await agent(`${PREAMBLE} The healthcheck branch adds a new event-contract field to both Lambda handlers — the handbook mandates a cross-family review for handler-signature / event-shape changes. Run exactly: cd ${REPO} && git diff main > /tmp/phase0.diff python3 ~/Documents/repositories/seahaven/security-review/cross_review.py \ "Review this diff for breaking changes. Context: procurement-ingest refactor Phase 0 per docs/refactor-evaluation.md — healthcheck early-return added to both email-processor handlers (new top-level event field, direct-invoke only), post-deploy smoke script, PO bundling cp-allowlist replaced with ./*.py glob, AST bundle-consistency test. Diff follows: $(cat /tmp/phase0.diff)" (cross_review.py is stateless/one-shot — the diff must be inline.) Return its verdict VERBATIM in summary, and set verdict to one of: PASS / FIX / BLOCK based on its highest finding. Do not fix anything yourself.`, { label: 'package:cross-review', model: 'sonnet', phase: 'Package', schema: TEXT }) const commit = await agent(`${PREAMBLE.replace('do NOT commit, ', '')} YOU are the commit agent. Steps: 1. Read ~/Documents/repositories/seahaven/engineering-handbook/commit-messages.md and follow it exactly. 2. git add: the Phase-0 files (${EXPECTED_SCOPE.join(', ')}), README.md, AND docs/refactor-evaluation.md (untracked evaluation report — must land with this first refactor PR or it is lost) AND .claude/workflows/phase-0-deploy-guards.js. Do NOT add .coverage. Verify with git status that nothing unexpected is staged. 3. ONE commit. Write the message to /tmp/phase0-commit-msg.txt and use git commit -F /tmp/phase0-commit-msg.txt (backticks in -m get eaten by zsh). Suggested subject: "feat: deploy-pipeline guards — healthcheck, smoke gate, bundle glob + AST test (refactor phase 0)". NO AI attribution / Co-Authored-By lines. 4. Do NOT push. Return the commit sha + shortstat in summary.`, { label: 'package:commit', model: 'sonnet', phase: 'Package', schema: IMPL }) return { status: 'BUILT — committed locally, NOT pushed', branch: BRANCH, commit: commit ? commit.summary : 'commit agent died — commit manually', crossFamilyReview: crossReview ? { verdict: crossReview.verdict, detail: crossReview.summary } : 'NOT RUN — outstanding', implementation: implOk.map(r => r.summary), filesChanged: implOk.flatMap(r => r.filesChanged).concat(readme ? readme.filesChanged : []), verifyRounds: round + 1, blockers: implBlockers.concat(reconBlockers), openPRsAtBranchTime: setup ? setup.facts : [], outstandingGates: [ '/sh-security-review (MANDATORY before push — handler = untrusted-input surface); run in the main loop on the committed diff', 'if cross-family verdict is FIX/BLOCK: resolve, re-run cross_review.py on the amended diff', 'push + PR + gh pr checks green', 'deploy-then-merge: deploy from branch, smoke green, one real PO + WO email each showing ParseMethod=template, all alarms green, THEN merge', ], }