feat(inbox): extract the inbox into its own deployable (phases A–C: pre-refactors, seam, additive project)#950
Conversation
Pre-refactors for the inbox deployable split. Four relocations, all
mechanical moves with import sweeps:
verification-deadline joins @packages/domain/user; effective-access
and the subscription read provider join @packages/subscription-access
(every deployable that gates on access composes the decision locally
from the same lookup — the moved provider gains its own fake-client
test since the package enforces 100% coverage); etagMatches, the poll
budget (MAX_POLLS + parsePollParam), and QuerystringFeatureToggle join
@packages/web-shell (the toggle now takes a structural {query} source,
keeping the shell express-free); initS3ReadContent joins
@packages/article-store; and the six inbox DynamoDB/S3 adapters move
to a new @packages/inbox-store consumed by hutch's composition roots,
the pipeline mains, and the delete-account worker.
…ployable The SES receipt topic is auto-named, so its ARN is the one genuinely deploy-time value the upcoming inbox stack must read via StackReference (every other seam value is config-duplicated per the infrastructure doctrine). Exported ahead of the inbox project so the output is persisted in both stage checkpoints before its first deploy reads it.
New projects/inbox serving /inbox from its own Lambda via API Gateway route precedence (blog-site/web-embed recipe), plus inbox-prefixed copies of the three email-pipeline consumers subscribing to the same SES topic and platform-bus events as hutch's originals. Hutch is untouched: this deploy opens a deliberate double-delivery overlap window — every handler is replay-idempotent, so duplicates converge — and a follow-up removes hutch's consumers to close it. The explicit inbox-* rule names matter: EventBridge PutRule is an upsert, so default names would silently steal hutch's live rules. The web app reproduces hutch's gate chain (auth redirect, verification status with session self-heal, write-access, locked page) from the extracted packages plus small local copies, wired through an env-free createInboxApp factory; route tests run against an in-project harness that mints sessions directly instead of driving /signup. 29 suites, 218 tests, 100% coverage on all four axes. Architecture snapshot 2026-07-10-bec433e1 documents the overlap-window state of both event flows. Staging stack is initialised; prod stack init needs operator credentials before this merges.
|
@claude Review this PR. Use your Read tool to read the local file Context
|
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
3 similar comments
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
Claude finished @FagnerMartinsBrack's task in 8m 28s —— View job Review complete — PR #950 (inbox deployable extraction)
Outcome: 0 High · 0 Medium · 2 Low. This is an exceptionally well-constructed, additive PR. Key safety claims independently verified:
Structured review is emitted as the run output for the auto-apply workflow. |
High Priority Issues (Must Fix)None found Medium Priority IssuesNone found Low Priority Suggestions1.
|
pulumi stack init prod ran against the prod state backend; both stage stacks now exist, clearing the first-deploy operator step.
|
@claude Review this PR. Use your Read tool to read the local file Context
|
|
Claude finished @FagnerMartinsBrack's task in 5m 11s —— View job Re-review complete — PR #950 @
|
High Priority Issues (Must Fix)None found Medium Priority IssuesNone found Low Priority Suggestions1. Quantify the deliberate double-delivery window's fan-out before mergingThis is informational, not a defect — the overlap is intentional and correctness-safe — but the magnitude is worth stating so the window is sized deliberately. This PR is purely additive: it does not touch hutch's SNS subscription or EventBridge rules, so hutch's full inbound-mail pipeline stays live while inbox adds parallel consumers. The result is multiplicative fan-out per inbound email during the window:
Every write is idempotent (conditional 2.
|
… read The rebase onto main picked up the inbox extraction (#950, 963345c), which moved initDynamoDbSubscriptionRead out of hutch into @packages/subscription-access. Git's rename detection reapplied this PR's listAllSubscriptionRows addition onto the relocated file, but left two loose ends the rebase could not see: - The reconcile CLI still imported initDynamoDbSubscriptionRead from the now-deleted hutch-local path. Repoint it to @packages/subscription-access, matching app.ts and the composed dynamodb-subscription-providers. - listAllSubscriptionRows coverage lived only in hutch's composed test, so the relocated function body was uncovered by its new package's own suite. Add the matching unit tests (single page, ExclusiveStartKey pagination, empty table) to subscription-access — restoring 100% coverage there. No Stripe logic changed; this only re-integrates the PR's additions with main's module move. Full `pnpm check` green (42 projects / 60 tasks). Co-authored-by: Fayner Brack <FagnerMartinsBrack@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts the inbox subdomain into
projects/inbox— its own Lambda behind hutch's API Gateway via route precedence (blog-site/web-embed recipe) — to isolate the nx cache graph: an inbox-only change now affects["inbox"](19s check) instead of invalidating hutch's 135s chain, and hutch changes no longer re-run inbox's tests. Verified both directions withnx show projects --affected --files=….This PR is additive and safe to merge/deploy on its own. Hutch is untouched except four pre-refactor package extractions and one new stack output. Merging opens a deliberate double-delivery overlap window (both hutch's and inbox's pipeline consumers process inbound mail; all handlers are replay-idempotent — see architecture snapshot
.architecture/2026-07-10-bec433e1/). The stacked cleanup PR closes the window.Contents (three commits)
verification-deadline→@packages/domain/user;effective-access+ subscription read →@packages/subscription-access;etagMatches/poll budget/feature toggle →@packages/web-shell;initS3ReadContent→@packages/article-store; new@packages/inbox-store(6 adapters).inboxNotificationTopicArn(the only genuinely deploy-time cross-stack value; everything else is config-duplicated per the infra doctrine)./inboxweb app with hutch's full gate chain reproduced (auth redirect, verification self-heal, write access, locked page), the threeinbox-*-prefixed pipeline consumers (explicit rule names — EventBridge PutRule is an upsert and default names would steal hutch's live rules), Pulumi program + stage configs, post-deploy smoke (303→/login assert). 29 suites / 218 tests / 100% coverage on all four axes.encryptionsaltline it adds toPulumi.prod.yaml)requireOutput(inboxNotificationTopicArn), re-run the failed job once hutch's deploy completes (one-click; second attempt reads the persisted output).After deploy
/inboxserves frominbox-web-handlerimmediately (route precedence; hutch's router remains as shadowed fallback = instant rollback by reverting this PR).inbox-*-dlq-topicSNS email subscriptions from the alert inbox (one-time clicks).scripts/post-deploy.sh(commented; needs one-time smoke-address seeding).Open-PR impact (audited at creation)
No open PR touches moved inbox paths. Wiring-file overlap (may need rebase after this merges): #943, #935, #934, #918, #870. The conflict-fixer automation handles textual conflicts post-merge; I'll sweep for semantic breaks after each merge.