Each feature's bind-production.ts already ends with
assertFeatureConformance(), but that check only fired on `pnpm dev`.
Adds a smoke test per feature that invokes the binder with a Noop ctx,
so missing __instrumented / __captured / __audited brands now fail
under `pnpm test` (and therefore in CI), closing the gap between the
TypeScript brand layer and the boot-time runtime assertion.
Closes the per-layer threshold gaps surfaced by the 2026-05-13 PRD
audit. After this commit all five features pass their declared
100%/100%/95%/100% bands on entities + use-cases + controllers.
media (was: missing @vitest/coverage-v8 + missing vitest config block +
one controller at 86.66% lines / 75% branches)
- Added @vitest/coverage-v8 dev dep
- Applied the standard helper-driven vitest config
- Declared the coverage section in feature.manifest.ts
- Added 2 tests to list-media.controller.test.ts covering the
InputParseError branch (unknown fields + invalid limit)
- Now: 16 files / 80 tests / 97.12% / controllers 100%
marketing-pages (was: get-site-settings.controller at 93.54% lines /
90.9% branches)
- Added 1 test to get-site-settings.controller.test.ts covering the
InputParseError branch on unknown fields
- Now: 22 files / 68 tests / 95.66% / controllers 100%
navigation (was: entities/errors/common.ts at 50% function hits +
get-header.controller at 86.66% lines / 80% branches)
- Root cause: InputParseError class never instantiated in any test
- Added 2 tests to get-header.controller.test.ts covering the
InputParseError branch + verifying the Zod cause is preserved.
One test exercises both gap files at once (controller throws,
InputParseError class is constructed).
- Wired navigation/vitest.config.ts through the shared helper
- Declared the coverage section in feature.manifest.ts
- Now: 11 files / 45 tests / 98.04% / entities + controllers 100%
All 5 features now drive thresholds from the manifest via the helper.
The duplication problem the keystone eliminates is gone.
Repo-wide via `pnpm coverage:aggregate`:
- statements 95.87% (lh 2994 / lf 3123)
- branches 88.91% (brh 433 / brf 487)
- functions 100% (fnh 142 / fnf 142)
- lines 95.87%
`pnpm coverage:diff -- --base HEAD~1` reports status: pass.
coverage/summary.json refreshed in the same commit so the trend
captures the post-unification state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrates blog and marketing-pages to the same pattern auth landed in
f7baa8b: vitest config consumes vitestThresholdsFromBands(
DEFAULT_COVERAGE_BANDS) instead of the duplicated hand-written
thresholds block, and each manifest declares its coverage section
explicitly.
Verified semantically identical to the previous hand-written
thresholds — the same numbers come out the other end of the helper.
No new regressions:
- blog: 89 tests, 96.33% overall, all bands green
- marketing-pages: 67 tests, 95.28% overall — controllers/ shows a
real 93.54% lines / 90.9% branches gap that has been there since
before this refactor (the previous hand-written threshold was the
same 100%/95%). This is one of the L0 unification work items
listed in the PRD's findings; capturing here as the third feature
with real test gaps (navigation, media, marketing-pages).
Three of five features now drive their vitest thresholds from the
manifest helper: auth ✓, blog ✓, marketing-pages ✓. Navigation and
media stay on the legacy config until L0 unification closes their
test gaps (touching them would expose the same failures and add
nothing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AGENTS.md bind-production code block: shows the slim default state
(no @repo/core-events / @repo/core-realtime imports, BindProductionContext
with no generic args) with a comment pointing to the scaffold workflow.
The previous block showed a fully-wired post-scaffold state without
signaling that none of those packages exist in main.
- bind-protocols.test.ts: top-of-file comment clarifies what these tests
actually verify (protocol shapes have required methods) vs what the
spec text might suggest (full assignability of optional packages'
interfaces — that's verified by the e2e reconstruction tests, not here).
- core-package-generator.md: drops two stale "Until Phases 3-6 land"
parentheticals — the phases shipped.
- config.test.ts: extends the choices assertion to cover all 4 names
(realtime, events, trpc, ui).
- marketing-pages bind-* comments: reverse the inverted optional/required
language. queue (IJobQueue) is from core-shared and always present;
bus is the optional one (from @repo/core-events when scaffolded).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- di-explainer.html: update stale JS data strings from positional-arg
form (bindAllProduction(config), bindProductionBlog(config,tracer,...))
to ctx-arg form (bindAllProduction(deps), bindProductionBlog(ctx)) with
a note on ctx shape.
- auth/sign-up.use-case: change bus param from IEventBus to
EventBusProtocol|undefined; guard bus.publish with if(bus) so the use
case is safe when core-events is absent (Phase 3+).
- auth/bind-production + bind-dev-seed: drop as IEventBus cast and unused
IEventBus import; ctx.bus is now passed directly (typed as
EventBusProtocol|undefined).
- marketing-pages/bind-production + bind-dev-seed: drop as IJobQueue cast
and unused IJobQueue import; wrap event-handler DI block in if(queue)
guard so the handler is only bound when core-jobs is wired.
- core-shared/src/index.ts: add `export * from "./di"` as the plan
specified (subpath export alone is no longer the only access path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three issues uncovered by the full pnpm typecheck/test/boundaries pass
and resolved here:
- core-testing was importing IEventBus / IJobQueue from core-events /
core-shared, creating two boundary violations (tooling → core) and a
build-graph cycle. Inlined the type aliases (mirroring how
RecordingTracer / RecordingLogger handle ITracer / ILogger).
recording-event-bus.test.ts replaces defineEvent() with an inline
descriptor literal so no runtime import is needed either. core-events
and core-shared are removed from core-testing dependencies.
- turbo.json: typecheck and test no longer dependsOn ^typecheck / ^build.
Each package's tsc / vitest resolves cross-package types via
node_modules independently, and dropping the topological dep avoids the
spurious cycle warning that appeared once core-testing started
importing core-events / core-shared.
- turbo.json: feature.dependencies.allow gains "feature". Cross-feature
event flow (ADR-015) requires a consumer feature to import the
publisher's event contract directly. The dangerous form (importing
the publisher's handler/use-case/repo) is still blocked by E1's
no-handler-reexport ESLint rule and the missing public exports.
- TaskConfig<"slug-string"> → TaskConfig<{ input; output }> in the gen
job task template (and the shipped send-welcome-email.task.ts) since
runtime-generated slugs aren't keys of TypedJobs['tasks'].
sendWelcomeEmailJob takes IMailerService, validates the typed input
(userId + email), and delegates to mailer.sendWelcome. The
onAuthUserSignedUpHandler now takes IJobQueue and enqueues
"marketing-pages.send-welcome-email" with the event payload.
Both binders bind a RecordingMailerService at the IMailerService
symbol (production placeholder until a real adapter ships) and pass
mailer / queue into the wrapped factories. Dev-seed additionally
queue.register()s the slug so the in-memory queue dispatches via
the wrapped job; production relies on the generated Payload event-task
to resolve the wrapped handler from the container.
Generated handler + Payload event-task via gen event consume,
threaded through symbols / both binders / cms re-export at the
configured anchors. bus.subscribe wires the in-memory delivery in
dev-seed; the __events.auth.user.signed-up.marketing-pages Payload
task closes the production-bus loop.
Also fixes two generator-level issues found during Phase 8:
- Drop publisher prompt's `when` clause so --args can supply the
4th positional argument (Plop limitation: --args cannot bypass
conditional prompts). Validate runs only in consume mode.
- Switch event-task.ts.hbs from TaskConfig<"slug-string"> to
TaskConfig<{ input: ...; output: object }> since runtime-generated
event slugs are not keys of TypedJobs['tasks'].
Plan 10 documented R44 (capture at originating-throw layer) but only the
R43 repo leg was wired. captureException had zero call sites in any
controller or use-case body. This commit closes the gap.
Mechanism:
- Extract __sentryReported flag helpers into core-shared/instrumentation/
reported-flag.ts. SentryLogger switches to importing them; RecordingLogger
carries an inlined copy (tooling → core boundary disallows the import).
- Add withCapture(logger, tags, fn) higher-order wrapper paralleling
withSpan. On throw: capture-with-tags, mark, re-throw. Bail if the flag
was already set — covers the bubbled-from-repo case so each error
surfaces in the logger exactly once with the inner-most layer's tags.
- Apply withSpan(withCapture(factory)) in every feature's bind-production
and bind-dev-seed: auth (3 use cases × 3 controllers), blog (3×3),
marketing-pages (2×2), navigation (1×1), media (3×3). Span is outermost
so the errored span timing reflects the capture-and-rethrow.
- RecordingLogger.captureException now also honours the flag — test
capture counts stay honest when both repo and outer layer wrap.
Tests:
- packages/core-shared/src/instrumentation/with-capture.test.ts —
4 cases covering success, capture-on-throw, mark-on-capture, no-double
via the flag.
- packages/blog/tests/r44-no-double-capture.test.ts — 3 cases: repo throw
→ 1 capture with repo tags; controller parse fail → 1 capture with
controller tags; success → 0 captures.
Verification: pnpm test 26/26, pnpm lint 15/15, pnpm typecheck 14/14.
Docs: ADR-014 and the refactor log gain a "Post-merge follow-up" section
recording the gap, the fix, and the underlying lesson (don't describe
intent as shipped state — grep first).
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>
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>
Spec reviewer caught a systemic gap during Task 6 (navigation) review:
Plan 8 left every feature's domain error class with a constructor that
didn't set this.name = '<ClassName>'. Plan 9 spec R6 requires it.
Fixed across all 10 error files:
- auth: AuthenticationError, UnauthenticatedError, UnauthorizedError, InputParseError
- blog: ArticleNotFoundError, InputParseError
- marketing-pages: PageNotFoundError, InputParseError
- navigation: HeaderNotFoundError, InputParseError
- media: MediaNotFoundError, InputParseError
Functionally a no-op — defineErrorMiddleware uses instanceof, not name —
but ensures correct serialization, stack traces, and JSON inspection.
Refactor log: §7
Spec: R6
All 5 features (auth, blog, marketing-pages, navigation; media has no
entities yet) now follow Lazar's pattern:
- entities/<x>.ts → entities/models/<x>.ts
- entities/errors.ts → entities/errors/<domain>.ts + errors/common.ts
Updates all import paths across factories, contracts, tests, use cases,
controllers, repositories, integrations, and src/index.ts exports.
navigation divergence: had no errors.ts; errors/header.ts +
errors/common.ts added as new forward-looking stubs.
Refactor log: docs/superpowers/refactor-logs/2026-05-05-lazar-pattern-conformance.md
Spec: §5, §9.3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Deprecate mockPayloadModule with throw guard (hoisting incompatible)
- Replace `as never` with stubPayloadConfig in payload-articles test (consistency)
- Tighten pages contract to use toHaveLength (exact assertions)
- Header contract: define CONTRACT_HEADER_SEED, assert item count + order
Reviewer: superpowers:code-reviewer (Task 4 of Plan 7).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each repository interface now has a contract suite under
src/__contracts__/. Both Mock and Payload implementations run the
same suite, eliminating mock-vs-real drift. Payload impls back the
contract with an in-memory stub via vi.mock('payload') + a small
buildPayloadStub helper.
Spec: §5.2, §6.4
Adds src/__factories__/<entity>.factory.ts to auth, blog, marketing-pages,
navigation, media. Each factory uses defineFactory from @repo/core-testing
with stable date defaults (2026-01-01) so snapshot diffs reflect SUT
behavior only. Refactors mechanical inline-fixture tests to use factories.
Also adds vitest.config.ts and tsconfig path alias to @repo/media (lacked
both), and adds @repo/core-testing devDependency to @repo/media.
Spec: §5.1, §6.3
All public pages (site settings, header, pages, articles) now bypass
Payload's access control checks when reading, as they should be publicly
accessible without authentication. This fixes 403 Forbidden errors on
homepage and article rendering.