mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 07:13:13 +00:00
Some checks are pending
Deploy / deploy (push) Waiting to run
* feat: deploy-pipeline guards — healthcheck, smoke gate, bundle glob + AST test (refactor phase 0)
Deploys of po-email-processor and workorder-email-processor had no
verification step, so an init-time ImportError in the bundled zip
could ship silently and only surface on the next real S3 event. This
adds a synchronous post-deploy smoke gate wired into the deploy
workflow: both Lambdas are invoked with {"healthcheck": true} and the
FunctionError field is checked, since an Unhandled init error still
returns HTTP 200 on RequestResponse invokes and would false-pass a
plain exit-code check.
The healthcheck branch is the first statement in each handler, before
any boto3/S3 use or ses_auth, and only fires on a top-level direct
invoke ("healthcheck" is not a key AWS ever sets on a real S3
ObjectCreated event, so mail content can't reach this path). It emits
no EMF metrics and no log text that could match the
sender-auth-rejected metric filter, so two deploys in one window
won't trip the alarm.
Separately, the PO stack's asset bundling copied a hand-maintained
four-file allowlist into the zip, so every new sibling module
handler.py imports had to be added by hand or the deploy shipped a
Lambda that ImportErrors at cold start (bit us for template_parser in
PR #105 and nearly for derived_fields in PR #2). Replaced it with a
non-recursive ./*.py glob so top-level source files ship
automatically while tests/ and the stale package/ dir still cannot,
and added an AST-based bundle-consistency test that parses each
handler's first-party imports and fails CI if the bundling command
would omit any of them (a revert to an incomplete allowlist, or code
moved into a subdirectory the glob doesn't cover).
Includes the refactor-evaluation report that scoped this phase.
* fix: review nits — unambiguous bundling-command extraction, smoke payload-parse message, dead asserts
- tests/test_bundle_consistency.py: _extract_bundling_command now collects
all command=[...] matches and demands exactly one per stack file, instead
of silently returning whichever ast.walk visits first if a second bundled
function is ever added.
- scripts/post-deploy-smoke.sh: distinguish an unparseable response payload
from a payload mismatch so the failure message says what actually happened
(the previous "could not parse" branch was unreachable — the inline python
always exited 0).
- test_po_healthcheck.py: drop the substring assertions on stdout that were
dead behind the stricter `captured.out == ""` assertion; keep the stderr
filter-pattern check.
Review follow-up on PR #107; no behavior change to any shipped code path.
428 lines
22 KiB
JavaScript
428 lines
22 KiB
JavaScript
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',
|
|
],
|
|
}
|