Adds "Asserting spans and captures" section to tdd-workflow.md with
RecordingTracer/Logger usage and inline withSpan wrapping pattern for
direct-injection tests. Adds R49/R50 section to testing-strategy.md
covering the no-sentry guard, contract suite span assertions, and
RecordingTracer/Logger field reference. Adds "TRACER / LOGGER (Plan 10)"
subsection to dependency-flow.md showing the bindAll → feature-container
wiring path. Adds an "src/instrumentation/" section to core-shared/AGENTS.md
documenting the two interfaces, three impl pairs, withSpan helper, scrubbers,
both Next.js + Vite/React init helpers, and the subpath exports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the seven Plan 10 conventions to CLAUDE.md (interfaces in core-shared,
spans at bind time, throw-site capture, PII rules, three Sentry projects,
orthogonal binding). Adds an "Instrumentation conventions" section to
AGENTS.md with repo constructor/method patterns, capture-rules table,
boundary allowlist, and test rules. Appends §16 "Instrumentation & error
capture" to vertical-feature-spec.md (the spec already has 15 sections,
so appending rather than slotting in as §10 to avoid renumbering).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two flat-config blocks to core-eslint/base.js: (1) repo-wide
no-restricted-imports for @sentry/* with the R40 message, (2) an
allowlist override for the only paths permitted to import the Sentry
SDK directly — core-shared/instrumentation/sentry/**, the bind-sentry
DI files, the no-sentry test guards, and apps' instrumentation* /
next.config / vite.config / sentry.*.config files. Patterns use
**/-prefix so they match whether ESLint runs from the repo root or
from inside a sub-package.
Also adds the standard `argsIgnorePattern: "^_"` config (used
throughout the repo) and a Node-globals override for *.mjs/*.cjs/*.js
and *.config.{ts,tsx} so withSentryConfig in next.config.mjs lints
clean. Required adding `globals` as a core-eslint dep.
Adds .github/workflows/sentry-pii-guard.yml — a lightweight CI step
that fails any PR introducing `sendDefaultPii: true` (R31). Excludes
node_modules / dist / .next / .turbo from the grep so vendored SDK
JSDoc examples don't false-positive.
Pre-existing lint nits cleared as part of getting `pnpm lint` green:
- core-testing define-contract-suite.test.ts: void the unused
receivedTracer (mirrors the next test's pattern)
- marketing-pages bind-dev-seed.ts: drop unused MockSiteSettingsRepository
import
- marketing-pages get-site-settings.use-case.ts: drop the now-redundant
eslint-disable for `_input`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds initSentryServerNode + initSentryClientReact to core-shared
(Vite/non-Next variants of the existing init helpers — same R31/R32/R33
posture, R34/R35/R37 replay defaults). Extends no-sentry.ts to mock
@sentry/node + @sentry/react. Wires the web-tanstack server/client
instrumentation entry hooks and adds the R38 PII test.
Spec deviation: web-tanstack has no vite.config.ts yet (placeholder app
per its package.json). The @sentry/vite-plugin dep is added but unused
until the TanStack Start build is wired in a later plan. A minimal
src/vite-env.d.ts shims ImportMetaEnv for the client entry until the
full Vite types land.
@sentry/node and @sentry/react are added to core-shared as optional
peerDependencies so feature packages don't transitively pull them in;
they're also devDependencies of core-shared for typecheck/test runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds apps/cms/instrumentation.ts (server-only — Payload admin client DSN
is out-of-scope per spec §8). Wraps the Payload-wrapped next config with
withSentryConfig. Adds the R38 PII scrubber test. Required adding
@repo/core-shared as a direct dep of cms (was only transitive before).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds apps/web-next/instrumentation.ts (server) and instrumentation-client.ts
(browser) hooks, wraps next.config.mjs with withSentryConfig (R52), and adds
the R38 per-app PII scrubber smoke test.
Spec deviation: extend PII_KEY_SUBSTRINGS with "ipaddress" so keys like
ipAddress trigger key-level redaction (tighter posture than the spec's
substring list; existing scrub.test.ts still passes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends bind-production.test.ts with a vi.mock for @repo/core-shared/instrumentation
that wraps bindSentryInstrumentation and bindNoopInstrumentation in vi.fn (so call
counts are observable while real implementations still run). Adds 4 orthogonality
tests covering: DSN absent + production env, DSN set + dev env, Sentry + dev seed,
Noop + production — verifying instrumentation choice is independent of repo mode.
Total: 6 original + 4 new = 10 passing tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Adds DI binder functions for instrumentation. bindNoopInstrumentation binds
NoopTracer + NoopLogger; bindSentryInstrumentation calls initSentryServer then
binds SentryTracer + SentryLogger. Both are idempotent (unbind-first). Adds
inversify + reflect-metadata as devDependencies so container tests compile.
Re-exports from instrumentation barrel. 6 new tests (3+3).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
33-task TDD plan implementing the R31–R55 spec across 9 phases:
foundation interfaces (NoopTracer/NoopLogger/withSpan), Sentry adapters
(SentryTracer/SentryLogger with __sentryReported double-report guard,
beforeSend + beforeSendTransaction PII scrubbers), DI binders + bindAll
Rule 0 dispatcher (orthogonal to USE_DEV_SEED/NODE_ENV), test
infrastructure (RecordingTracer/RecordingLogger + no-sentry guard),
per-feature wiring for all 5 features (blog/auth/marketing-pages/
navigation/media), contract suite span assertions, three-app integration
(web-next/cms/web-tanstack including @sentry/node + @sentry/react +
@sentry/vite-plugin variants), ESLint boundary rule + CI grep gate, and
docs + HTML updates (data-flow-explainer §06, di-explainer
instrumentation symbols, ADR-014).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Defines the post-Plan-9 instrumentation subsystem: vendor-agnostic
Tracer/Logger interfaces in core-shared, full-depth tracing
(procedure → controller → use-case → repository) with use-case +
controller spans applied via withSpan at DI binding time and explicit
tracer.startSpan in every repository method, throw-site error capture
with __sentryReported double-report guard, and hard PII rules
(sendDefaultPii:false, default-mask replay, beforeSend/beforeSendTransaction
scrubbers, opaque user IDs only, build-time CI grep gate).
Three apps in scope (web-next, cms, web-tanstack), each with its own
Sentry project and DSN. Instrumentation binding is orthogonal to
USE_DEV_SEED/NODE_ENV repo binding. Optional dev-mode Sentry: NoopTracer
default, real SDK initializes only if DSN env is set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each folder/file in the feature anatomy gets its own card with:
- Brief one-line description
- Two-column pros/cons list (3-4 items each)
Covers 22 parts grouped into 21 cards: entities/models, entities/errors,
application/repositories interfaces, application/services interfaces,
application/use-cases, infrastructure real repo, infrastructure mock
repo, infrastructure services, interface-adapters/controllers, di/
(symbols, module, container, bind-production, bind-dev-seed),
integrations/api/procedures, integrations/api/router, integrations/cms,
ui, __factories__, __contracts__, __seeds__, src/index.ts.
Each card uses the same paper/cream palette as the rest of the page;
pros/cons differentiated by color (forest-green + / oxblood −) and a
JetBrains-Mono leading character.
Verdict section renumbered to §06; contents nav updated to 6-column
grid.
- CLAUDE.md Key Conventions: 'App bootstrap' rule rewritten as 'Three
binding modes per feature' — describes USE_DEV_SEED + NODE_ENV
resolution order and the new ./di/bind-dev-seed export.
- AGENTS.md (root): exports list now mentions ./ui + ./di/bind-dev-seed;
Per-feature public-API surface table gains a row; Apps section shows
the bindAll() dispatcher with three-rule logic.
- docs/architecture/vertical-feature-spec.md §6: file shape now
includes bind-dev-seed.ts, bind-dev-seed.test.ts, __seeds__/dev.ts;
package.json exports list updated to include ./di/bind-dev-seed.
- docs/architecture/data-flow-explainer.html: anatomy tree gains
__seeds__/ row; LAYERS.di description updated with new binders +
cross-link to di-explainer.html; new LAYERS.seeds entry; public-
surface card expanded to six subpaths.
- docs/superpowers/refactor-logs/2026-05-06-input-output-unification.md
§7: new 'Post-Plan-9: dev-seed binders' entry summarizing the rollout
(commits, per-feature additions, app wiring, tests, turbo, docs).
- bind-production.test.ts: dispatcher tests use vi.stubEnv (typesafe
way to test process.env in TypeScript 5+ with @types/node read-only
process.env types). 4 dispatcher tests + 2 bindAllProduction tests
= 7 tests total.
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.
Earlier commit 74b88f7 attempted this but the Edit failed silently
without a prior Read. ESLint's turbo/no-undeclared-env-vars rule was
still flagging the warning.
ESLint flagged that USE_DEV_SEED was read in apps/web-next without being
declared in root turbo.json globalEnv — added it.
DI explainer's conditions table previously listed 'Production server
(NODE_ENV=production)' as a scenario, but bindAll() only checks
USE_DEV_SEED — NODE_ENV is not read anywhere in source. Fixed the table
and the prod-mode flag string to match what the code actually does.
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.
Mirrors the canonical blog pattern landed earlier on this branch.
Per feature:
- src/__seeds__/dev.ts — lazy buildDev<Entities>() function using the
feature's existing factory for sensible defaults
- src/di/bind-dev-seed.ts — bindDevSeed<Feature>() async function that
rebinds the repo symbol(s) to a populated MockXRepository via
.toConstantValue
- src/di/bind-dev-seed.test.ts — 3+ tests per feature (populates,
reachable by id/slug, idempotent)
- package.json — adds ./di/bind-dev-seed subpath export
Tests + use cases continue to construct mocks directly; the seed never
runs from a *.test.ts path. App boot wiring (USE_DEV_SEED env branch)
follows in a separate commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sets the canonical pattern for all 5 features. Three new files:
- src/__seeds__/dev.ts — buildDevArticles() function returning 3
realistic articles (welcome, vertical-feature-architecture,
wip-post) built from articleFactory with id/slug/title/status
overrides only.
- src/di/bind-dev-seed.ts — bindDevSeedBlog() async function that
unbinds IArticlesRepository, constructs MockArticlesRepository,
seeds it via buildDevArticles(), and rebinds via .toConstantValue.
- src/di/bind-dev-seed.test.ts — 3 tests: populated repo, welcome
article reachable by slug, idempotent (callable twice).
package.json adds the ./di/bind-dev-seed subpath export, parallel to
./di/bind-production.
Tests + use cases continue to construct MockArticlesRepository
directly — they never go through bindDevSeedBlog. The seed only attaches
when called explicitly at app boot.
User pushback: 'I already need mock data for dev and for testing at
runtime' — i.e., the dual role of the mock is obvious, not surprising.
Earlier framing was condescending.
Reframe around 'runtime reach':
- Section blurb leads with 'three artifacts at different distances
from runtime'
- Three-roles card intro: same neighborhood, different reach. Mock is
reached by runtime; contract + factory only by tests.
- 'The mock has two jobs' → 'The mock is reached from two directions
— both legitimate, neither is the test version'.
- Contract framing now leads with its real purpose: it tests the mock
alongside the real impl so you can trust the mock as a runtime
artifact.
User asked: 'arent mocks also related to testing?' — yes, but the mock
plays two roles where the contract and factory play one each. §04 now
opens with that framing.
What's new:
- Section title bumped to 'Mocks, contracts & factories'
- New three-roles diagram at the top of §04 visualizing the chain:
IArticlesRepository → MockArticlesRepository + ArticlesRepository
→ articlesRepositoryContract → articleFactory
- Two ordered lists explaining the mock's dual job (DI default binding
+ direct test fake) and the contract/factory's single test-only roles
- Three new expandable code examples:
· the mock as DI binding (real BlogModule code)
· the mock as direct test fake (use-case test, no container)
· the contract running against both impls (proof-of-parity)
- New CSS for .three-roles-diagram, .role-* boxes, .role-arrow with
vertical connector lines, .role-jobs lists with counter-leading
decimal numbering
The existing Contracts and Factories cards stay in place below — they
provide the deeper detail on each individual artifact.
Single-file HTML at docs/architecture/data-flow-explainer.html.
Self-contained — Google Fonts (Fraunces + JetBrains Mono) is the only
external resource; all interactivity is vanilla JS, all diagrams are
inline SVG/CSS.
Five sections:
01. Feature anatomy — clickable folder tree, layer detail card swaps
to explain entities / application / infrastructure / interface-
adapters / di / integrations / ui / __factories__ / __contracts__
/ public surface.
02. Request flow — step-through pipeline (12 stages, including the
success/error fork) with prev/next/play and a feature picker
(auth, blog, marketing-pages, navigation, media). Each feature
swaps real code snippets, the procedures.ts error map, and the
use-case/controller pattern.
03. Dependency injection — interactive DI canvas with default/prod
toggle so the user sees the same symbol resolving to mock vs
real Payload-backed impl. Real module.ts and bind-production.ts
code blocks below.
04. Contracts and factories — expandable sections with the actual
defineContractSuite + defineFactory code from packages/blog.
05. Verdict — short answer (yes), tradeoffs, badge row.
Editorial aesthetic: cream paper, deep ink, oxblood accent, Fraunces
display + JetBrains Mono code. Subtle paper noise + ruled-line bg.
No CDN scripts beyond fonts.
Doc-pass agent flagged §9.4 still listed only '.', './cms', './api' as
the allowed public surface. ./ui was added in Plan 9 and
./di/bind-production in Plan 5; both belong in the canonical list.
§1 worked example rewritten to direct factory injection (no container
rebinding). §4 mocking decision tree updated. New §sections for R25
output-validation tests, R26 router error-mapping tests, R27/R28
presenter-shape tests. §9 contract-suite paths use the post-Plan-8
.mock.ts suffix.
Refactor log doc-update checklist: tdd-workflow.md ticked.
Each per-feature AGENTS.md now reflects the post-Plan-9 layout:
entity/error paths, public-API split (./ui), use-case schemas, presenter
pattern, feature-scoped tRPC error map, and feature-specific
errors-to-codes table.
core-testing/AGENTS.md gains a Plan 9 test-patterns section documenting
R25 (output validation), R26 (router error mapping), R27/R28
(presenter shape) test obligations.
auth: documents real PayloadUsersRepository + AuthenticationService and
the deferred session methods.
media: documents the full Clean Architecture scaffold introduced in
Plan 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>