The audit-logs collection was never registered (record() would throw),
bindAudit/createAuditErasureHook were unused, and DSR cascade-hard never
touched the audit trail (audit finding A6). core-cms now registers the
collection and wires a req-scoped afterDelete erasure hook on users;
bindAllProduction binds the audit log into consent/DSR; cascade-hard
pseudonymizes the subject's audit entries; the action select accepts
the full AuditAction enum so consent/DSR entries pass validation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The retention purge job gated its whole body on activeRetention while
every collection declares only postDeletion, and no app ever called
registerRetentionPurgeJobs — retention was dead end to end (audit
findings A2 + A3). The DSR soft delete now stamps a deletedAt tombstone
on postDeletion collections (kept distinct from processingRestrictedAt
so an Art. 18 restriction never feeds the purge), the job grace-purges
tombstoned rows past postDeletion.duration with the declared action,
core-cms injects the tombstone field + Payload task definitions, and
bindAllProduction enqueues the first purge cycle at boot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tRPC createContext was () => ({}) — the mounted dsr/consent routers
401'd every call and the dsr singleton stub threw (audit finding A11).
createTrpcContext now accepts an app resolveUser hook; web-next resolves
the session cookie through the auth feature's validateSession (denylist
included) plus a role snapshot, and threads bindProductionDsr/Consent
(or dev-seed) bindings into every request. The dsr router resolves its
binding from ctx.dsrBinding per request instead of a throwing proxy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bindAllProduction injected NoopRateLimit, so the sign-in budgets the
auth manifest declares were never enforced in production (audit finding
A4/B3). The production ctx now binds InMemoryRateLimit seeded from the
manifest's rateLimit budgets (manifest stays the source of truth);
dev-seed intentionally keeps Noop so local iteration never throttles.
A regression test drives sign-in through the REAL auth production
binder + app router and asserts the 6th failed attempt returns
TOO_MANY_REQUESTS while other IPs stay unaffected. In-memory counters
are per-process; multi-instance deployments need a shared IRateLimit
backend.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the boolean bound flag with a cached promise so concurrent
callers (layout + page server components) await the same binding
operation. Prevents DI resolution before containers are populated.
- Add USE_DEV_SEED=false branch to bindAll dispatcher
- Use dotenv-cli to inject root .env into all Turbo tasks
- Add globalDependencies for .env cache invalidation
- Add withRateLimit(rateLimit, fn) in rate-limit/with-rate-limit.ts,
attaching the RateLimited brand at DI bind time
- Extend wireUseCase to accept optional rateLimit?: IRateLimit and
compose withRateLimit innermost (before analytics/audit); propagate
__rateLimited through analytics + audit inline wrappers
- Extend withSpan and withCapture PROPAGATED_BRANDS to include
__rateLimited so the outermost binding carries the brand
- Extend assertFeatureConformance to require __rateLimited brand when
manifest.useCases[name].rateLimit.length > 0; refactored into
helper functions to stay within complexity thresholds
- Add rateLimit?: IRateLimit to BindContext; default to NoopRateLimit
in web-next bindAllProduction and bindAllDevSeed aggregators
- Unit tests for withRateLimit brand attachment, factory passthrough,
and composition; synthetic fixture tests for conformance errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
apps/web-next/bind-production.ts imports bindOtelInstrumentation instead
of bindSentryInstrumentation. Test mock tracks both names via the same
spy so existing assertions on bindSentryInstrumentation still pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five fixes surfaced by the branch-wide code review on the realtime layer:
- server.ts: replace dynamic `import("@repo/auth/di/container")` with a
static top-of-file import. The dynamic-import workaround from 6a0ac63 is
no longer needed once the root tsconfig + TSX_TSCONFIG_PATH expose
decorator metadata to tsx; verified by booting `pnpm dev` clean.
- server.ts: correct the inline structural type for `validateSession` to
match the real `IAuthenticationService` contract (non-nullable, throws
on invalid session) and wrap the call in try/catch so unauthenticated
bubbles to a `null` return instead of dead-code `result ? ... : null`.
- bind-production.ts: extract `maybeRegisterRealtimePing()` that wraps the
built-in ping inbound handler in the same `withSpan(withCapture(...))`
sandwich the realtime-handler generator emits (R41–R44), so the
proof-of-life channel models the convention rather than registering raw.
- bind-production.test.ts: add 4 tests for the `REALTIME_PING_DISABLED`
env-gate (registered when unset in both binders, not registered when
"true", treated as enabled when "1").
- docs/guides/realtime.md: correct the integration-test reference at
line 285 — the test does not call `bindAllDevSeed()`; it builds the
Socket.IO server inline and exercises gates 1+2 only (gates 3+4 live in
socket-io-realtime-server.test.ts).
- adr-016: add a "Known follow-ups" section recording 6 lower-priority
refinements deferred from this branch (bridge stub test scaffolding,
registry register/registerChannel precedence, channel-template dot
constraint, server bare catch{}, BindAllDeps Partial widening, AGENTS.md
anchor count phrasing).
CI gates: lint 0 errors / 4 warnings (pre-existing turbo.json warnings),
typecheck clean, 24 web-next tests pass (was 20; 4 new env-gate tests),
boundaries 0 issues across 504 files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends bindAll/bindAllProduction/bindAllDevSeed to accept BindAllDeps
and thread realtime + realtimeRegistry as the 6th and 7th positional args
into each per-feature binder. Adds bindRealtimeBridge stub (empty allowlist
in v1). bindAll accepts optional deps with InMemoryRealtimeBroadcaster
fallback so existing page-level callers stay type-safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
resolveEventsAndJobsProduction wires PayloadJobsEventBus + PayloadJobQueue
against the bootstrapped Payload instance; resolveEventsAndJobsDevSeed
wires InMemoryEventBus + InMemoryJobQueue. Each per-feature binder now
receives (bus, queue) so Phase 7 generators can subscribe handlers and
register job tasks at the <gen:event-handlers>/<gen:jobs> anchors.
Introduces resolveInstrumentation() in apps/web-next/src/server/bind-production.ts:
reads WEB_NEXT_SENTRY_DSN; if set, calls bindSentryInstrumentation; else Noop.
Both bindAllProduction and bindAllDevSeed call resolveInstrumentation() at the
top. Exports __resetBindStateForTests() and __getInstrumentationForTests() for
test helpers. Adds inversify + reflect-metadata as direct dependencies of web-next.
Existing 6 tests still pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three-rule resolution order in bindAll() (first match wins):
1. USE_DEV_SEED === 'true' → bindAllDevSeed (explicit override)
2. NODE_ENV === 'production' → bindAllProduction
3. otherwise → bindAllDevSeed (developer default)
Rationale: 'pnpm dev' should boot the app without requiring Payload to
be running locally — dev seed is the more useful default for non-
production environments. Production servers explicitly set
NODE_ENV=production and get the real binding. The USE_DEV_SEED override
remains the escape hatch (force seed in any NODE_ENV — e.g. staging
preview, design review).
bind-production.test.ts grows from 3 tests to 8 — covers the dispatcher
matrix:
- USE_DEV_SEED='true' wins even when NODE_ENV='production'
- NODE_ENV='production' (no override) → production
- NODE_ENV='development' → dev seed (default)
- NODE_ENV unset → dev seed (default)
- USE_DEV_SEED='false' treated as not-set (only the literal 'true' triggers)
- Pre-existing 'binds all five repos' test now also asserts bindProductionMedia
di-explainer.html conditions table + mode flag strings updated to match
the new three-rule logic.
apps/web-next/src/server/bind-production.ts now exports three functions:
- bindAllProduction() — production-only binders
- bindAllDevSeed() — dev-seed-only binders (NEW, calls all 5 features)
- bindAll() — dispatcher that branches on USE_DEV_SEED env var
All page/route callers (page.tsx, about/page.tsx, blog/[slug]/page.tsx,
api/trpc/[trpc]/route.ts) updated from bindAllProduction → bindAll so the
env flag actually has effect.
docs/architecture/di-explainer.html (NEW): standalone interactive page
explaining the di/ folder file-by-file, the loading sequence (8 stages),
the three binding kinds (.to / .toDynamicValue / .toConstantValue), an
interactive three-mode picker showing how the same blogContainer state
differs across default/dev-seed/production, a conditions table, and a
final card on how tests bypass DI entirely. Sister page to
data-flow-explainer.html.
Refactor log entry + canonical doc updates follow in subsequent commits.