Skip to content

feat(disruption): executor core — builders + orchestration + DDB deps (ADR-031) [#1419] - #1640

Closed
susumutomita wants to merge 4 commits into
feat/1419-action-schema-validatefrom
feat/1419-executor-dispatch-core
Closed

susumutomita wants to merge 4 commits into
feat/1419-action-schema-validatefrom
feat/1419-executor-dispatch-core

Conversation

@susumutomita

@susumutomita susumutomita commented Jun 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

The complete non-deploy core of the ADR-031 cross-account disruption executor (#1419). Three pure/testable modules under disruption-executor-handler/ — no deploy, no new SDK dependency. The only remaining pieces all deploy real fault injection into competitor accounts, so they stay your review/deploy decision.

Stacked on #1639 (DisruptionAction type). Base feat/1419-action-schema-validate; retarget to main after #1639 merges.

Modules

  1. dispatch-command.ts — pure builders. buildDisruptionDispatch / buildRevertDispatch → { kind, target, documentName?, params }. targetRef/functionRef resolved only from stackOutputs keys (loud throw if unresolved); {{key}} substituted only from fired parameters (value-less placeholder throws — runtime twin of feat(disruption): action contract parser + declaration-time enforcement [#1419] #1639's allow-list); revert reuses inject kind/target with action.revert overrides (ADR-029 INV-2).
  2. execute.ts — orchestration. executeDisruptionAction(detail, deps): resolve action (unknown_disruption/no_action passthrough) → claim EXEC#{requestId}#{teamId} (duplicate short-circuit) → resolve deployment (no_deployment no-op) → inject → schedule revert. Every I/O boundary injected (describe-stack-handler DI), so it's a pure decision function.
  3. executor-store.ts — the DDB deps (no new SDK dep): claimExecution (conditional Put, mirrors disruption-fire's REQUEST# claim) + resolveDeployment (GSI1 TENANT# + event/team/problem filter, COMPLETE rows only, parsed stackOutputs — mirrors leaderboard-score-events).

Owner-gated remainder (deploys real fault injection)

  • sendDispatch — DisruptionDispatch → SSM SendCommand / Lambda Invoke / CFn UpdateStack via assumed creds (needs @aws-sdk/client-lambda).
  • scheduleRevert — aws-scheduler one-shot (new IAM).
  • the Lambda + EventBridge rule + scoped sts:AssumeRole CDK construct, + reference-problem E2E.

Building those is fine; deploying them (AdministratorAccess fault injection into third-party accounts) is your call. Precedent for shipping core ahead of wiring: #1606.

Test plan

  • 26 new unit tests, infra full suite green: dispatch builders (11), orchestration (6: ok ordering / no_action / unknown / duplicate / no_deployment / send-fails-no-revert), DDB deps (9: claimed / duplicate / error / TTL / GSI shape / non-COMPLETE / missing-field / empty outputs / empty result).
  • tsc --noEmit, biome, make harness (no findings), check-no-conflicts: all clean.

Regression analysis

Net-new, zero production call sites — three new modules in a new dir; nothing imports them at runtime yet (the entry + CDK construct are the owner-reviewed deploy step). No Lambda/IAM/event/existing-file change. Type-only dep on #1639; rebases onto main cleanly after it merges. Throw paths are fail-loud by design and covered.

Physical impact

NO-OP on CloudFormation / deployed artifacts. Pure TypeScript + tests; no construct, Lambda, IAM, DynamoDB, or EventBridge change. Nothing deployed or invocable until the handler entry + construct land.

Relates #1419

…ADR-031) [#1419]

The unit-testable heart of the ADR-031 executor (migration step 2→3): given a
fired disruption's `action` + already-folded `parameters` + the team's
`stackOutputs`, produce the SDK-agnostic inject + revert descriptors. Pure — no
AWS calls (AssumeRole / SendCommand / Invoke / UpdateStack are the handler's job,
deferred to the reviewed wiring step). Builds on the action contract from the
parser/validator PR.

- dispatch-command.ts: buildDisruptionDispatch + buildRevertDispatch →
  DisruptionDispatch { kind, target, documentName?, params }.
  - targetRef / functionRef resolved ONLY from stackOutputs keys (no arbitrary
    resource ids reach the competitor account; loud throw if unresolved).
  - {{key}} substituted only from fired parameters; a value-less placeholder
    throws (never sends a literal {{key}} cross-account) = runtime twin of the
    validator's declaration-time allow-list.
  - revert reuses the inject kind/target, overridden by action.revert
    documentName / paramTemplate (ADR-029 INV-2 recovery). afterSeconds is left
    to the handler/scheduler.
- 11 unit tests: all 3 kinds, functionRef fallback, nested substitution,
  non-string leaves, both throw paths, revert variants.

This PR is stacked on the parser/validator PR (#1639, for the DisruptionAction
type). The live handler (AssumeRole + send + schedule revert + EXEC# idempotency)
+ the CDK construct (Lambda + EventBridge rule + IAM) are the next step — kept
separate because deploying fault injection into competitor accounts is the
owner's review/deploy decision.

Relates #1419

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23b53701-7bf5-447a-b284-256b438cd58f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1419-executor-dispatch-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.36%. Comparing base (5f49fa7) to head (286b24d).

Additional details and impacted files
@@                         Coverage Diff                          @@
##           feat/1419-action-schema-validate    #1640      +/-   ##
====================================================================
+ Coverage                             93.32%   93.36%   +0.03%     
====================================================================
  Files                                   391      394       +3     
  Lines                                 10763    10821      +58     
  Branches                               3303     3324      +21     
====================================================================
+ Hits                                  10045    10103      +58     
  Misses                                  222      222              
  Partials                                496      496              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…1419]

Adds the executor's decision logic on top of the pure dispatch builders: given a
*DisruptionFired detail it resolves the action from the catalog, claims per-team
idempotency (EXEC#{requestId}#{teamId}), resolves the team's deployment, injects
the built dispatch, and schedules the revert (ADR-029 INV-2).

Every I/O boundary (catalog / idempotency claim / deployment resolve / AssumeRole
+ send / revert schedule) is injected as a dep, mirroring describe-stack-handler's
DI pattern, so the orchestration is a pure decision function unit-tested with
mocks (6 cases: ok ordering, no_action passthrough, unknown_disruption,
duplicate short-circuit, no_deployment, send-fails-so-no-revert).

The concrete dep impls (SDK command mapping, aws-scheduler revert, the GSI
deployment query) + the Lambda/EventBridge/IAM CDK construct deploy real fault
injection into competitor accounts, so they remain the owner's review/deploy step.

Relates #1419

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@susumutomita susumutomita changed the title feat(disruption): pure dispatch core for cross-account executor (ADR-031) [#1419] feat(disruption): executor core — dispatch builders + orchestration (ADR-031) [#1419] Jun 2, 2026
…ent [#1419]

The DDB-side concrete deps for the executor orchestration, following existing
patterns with no new SDK dependency:
- claimExecution: EXEC#{requestId}#{teamId} conditional Put (per-team idempotency
  vs EventBridge at-least-once), mirroring disruption-fire's REQUEST# claim.
- resolveDeployment: GSI1(TENANT#) + eventId/teamId/problemId filter, returns the
  COMPLETE deployment's cross-account info + parsed stackOutputs, mirroring
  leaderboard-score-events' query. Skips non-COMPLETE / cross-account-incomplete rows.

9 unit tests (mocked ddb): claimed / duplicate(CCF) / error-propagate / custom TTL;
GSI query shape / non-COMPLETE skip / missing-field skip / empty stackOutputs / empty result.

Still owner-gated (deploy of real fault injection): the SDK-send dep, the
aws-scheduler revert dep, and the Lambda/EventBridge/IAM CDK construct.

Relates #1419

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@susumutomita susumutomita changed the title feat(disruption): executor core — dispatch builders + orchestration (ADR-031) [#1419] feat(disruption): executor core — builders + orchestration + DDB deps (ADR-031) [#1419] Jun 2, 2026
…mposes [#1419]

Integration fix surfaced while wiring the handler entry: ExecutorDeps.scheduleRevert
omitted `detail`, but the concrete scheduleRevert (#1642) needs it to build the
idempotent schedule name (EXEC# twin = requestId/teamId) and the revert invocation
payload. The dep is built once at module load, so detail must arrive at call time —
executeDisruptionAction now passes it: scheduleRevert(detail, revert, target, afterSeconds).

Without this the inject-time orchestration and the revert scheduler did not type-compose
in the real wiring. execute.test.ts updated to assert detail is forwarded.

Relates #1419

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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