Skip to content

Refactor/backend stack split - relocate resolver domains to satellites#43

Open
gibbsie wants to merge 3 commits into
mainfrom
refactor/backend-stack-split
Open

Refactor/backend stack split - relocate resolver domains to satellites#43
gibbsie wants to merge 3 commits into
mainfrom
refactor/backend-stack-split

Conversation

@gibbsie

@gibbsie gibbsie commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

The backend stack sat at 500 resources — the hard CloudFormation ceiling — blocking any new resolver, rule, or table. This relocates two resolver domains to dedicated satellite stacks attached to the same GraphQL API, using the pattern already proven by the governance and arbiter stacks. Backend drops to 278 resources (44% headroom). No stateful resource moves.

  • test(backend): seven machine-checkable restructuring gates + committed synth baseline, each proven by negative tests (doctored templates fail their gate: removed stateful id, weakened deletion policy, tampered mapping template, broadened IAM, double-attached field)
  • refactor(backend): projects domain → citadel-projects (14 Lambdas, 10 data sources, 23 resolvers, 4 rules, 2 alarms; 500→375)
  • refactor(backend): registry domain → citadel-registry (97 resources incl. the 28-field agent-record resolver, agent import, registry sync, fabricator handlers; 375→278)

What the gates prove (all green, run npm run split:gates)

  1. Backend template diff is removals-only; every retained resource byte-identical on key properties.
  2. All 39 stateful resources (20 tables incl. 6 retained governance tables, Cognito pool, GraphQL API+schema, event bus, buckets, AgentCore registry) keep logical ids and deletion policies.
  3. Every schema field has exactly one resolver across all stacks — none lost, none double-attached.
  4. Relocated Lambda/data-source roles are subset-or-equal to baseline; agent-import's Secrets Manager and STS statements proven byte-identical by dedicated tests.
  5. Relocated resolvers' request/response mapping templates hash-match baseline.

Deliberate choices

  • Moved Lambdas carry no pinned function names (kills recreation collisions); sole exception is the agent-record resolver, which keeps its baseline name for by-name invokers.
  • publish/unpublish resolvers stay in backend (they target a gateway-owned function; moving them would double-attach or cycle).
  • Deferred to a later batch: agent-config resolver and gateway-registration handler (~30 resources).

Testing

Full backend suite 4,775 passed / 0 failed; 9-stack synth with cdk-nag clean; doc-claims verifier green; gates green post-rebase onto main.

Deploy (operator — read before merging)

This PR deploys in TWO ordered steps per phase, with a brief dark window per moved domain (those GraphQL fields 5xx between the two deploys):

  1. cdk deploy citadel-backend-<env> (detaches moved resolvers)
  2. cdk deploy citadel-projects-<env> then citadel-registry-<env> Rehearse with the projects stack in dev first; schedule the registry deploy off-peak (hot path). ROLLBACK is the reverse order — destroy/revert the satellite FIRST, then redeploy backend. Never cdk deploy --all for rollback: backend-first re-attachment fails on duplicate field resolvers. Smoke checks after each phase: getProject/sendMessage (projects); getApp/importAgent/registry record CRUD (registry).

Oliver Gibbs added 3 commits July 26, 2026 06:49
Seven gates protect any future movement of resources between stacks: a removals-only template diff proving retained resources stay byte-identical, logical-id pins with deletion-policy assertions on all 39 stateful resources, cross-stack resolver parity (every schema field attached exactly once), IAM privilege equivalence proving relocated roles never broaden beyond their baseline, behavioral equivalence on resolver mapping templates and data-source config, plus the existing doc-claims and infrastructure-lint gates — all runnable as one command against a committed synth baseline.

Each gate is proven by negative tests: doctored templates with a removed stateful resource, weakened deletion policy, modified key schema, tampered mapping template, broadened IAM statement, and double-attached field each fail their gate.
…tack

Relocates the project, conversation, document, assessment, and design-progress resolver Lambdas — 14 functions, 10 data sources, 23 resolver attachments, 4 event rules, 2 alarms — from the backend stack into a new projects stack that attaches to the same GraphQL API, following the established satellite pattern. The backend stack drops from 500 resources (the CloudFormation ceiling) to 375. Relocated functions carry no pinned names, eliminating recreation collisions; every stateful resource keeps its logical id and policies in place.

All seven restructuring gates pass over the populated move manifest: removals-only template diff, 39 stateful logical-id pins, cross-stack resolver parity, IAM privilege equivalence for relocated roles, and byte-level behavioral equivalence of resolver mapping templates. Deploy order is backend first then projects; rollback is the reverse — the choreography is documented in the operator plan.
Relocates the agent registry domain — the 28-field agent-record resolver covering app lifecycle, API keys, access control, and metrics, plus agent import, registry sync, agent code, and fabricator request handling — 97 resources in all — into a new registry stack attached to the same GraphQL API. The backend stack drops from 375 to 278 resources, 44 percent below the CloudFormation ceiling. All seven restructuring gates pass across both relocated domains: template diffs are removals-only, all 39 stateful resources keep their logical ids and policies, every schema field stays attached exactly once, and relocated resolver templates byte-match their baselines.

The agent-import role's Secrets Manager and STS statements are proven byte-identical to baseline by dedicated equality tests, not merely subset-equal. App publish and unpublish resolvers stay in the backend stack because they target a gateway-owned function; the agent-config resolver and gateway-registration handler are documented deferrals for a later batch.

test("no other moved Lambda has functionName pinning (auto-named, invoked via in-stack grantInvoke)", () => {
const fns = template.findResources("AWS::Lambda::Function");
for (const [logicalId, fn] of Object.entries(fns)) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant