Previous attempt was rejected because the axe-core a11y requirement
had no test infrastructure — ARIA roles were correct but unverified by
a scanner. This adds jest-axe (approved via library-decision trace) and
asserts toHaveNoViolations() for both modal and banner variants.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Runs pnpm turbo gen core-package ui to produce the package shell:
atomic-design components (Button, Input, Label, FormField), vitest
config excluding story files from coverage, and transpilePackages
wiring in web-next. Adds @vitest/coverage-v8 devDep and
label.stories.tsx to satisfy lint/coverage gates.
Also fixes scripts/library-decisions/check.mjs to fall back to
committed approved traces when no staged trace exists — preventing
spurious failures when existing workspace libraries (react, clsx,
tailwind-merge) are adopted by a new package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add @repo/core-dsr and @repo/core-consent as dependencies and wire
dsrRouter + consentRouter into appRouter via the existing router
composition pattern. Integration tests cover all eight procedures
(dsr.export, dsr.delete, dsr.rectify, dsr.restrict, consent.grant,
consent.withdraw, consent.isGranted, consent.getCategories) with auth
and response-shape assertions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add four protocol-agnostic handlers (export, delete, rectify, restrict)
returning normalized { status, body, headers } responses, and a tRPC
dsrRouter via createDsrRouter(binding) following the factory pattern.
Auth checks: requireAuthenticated middleware gates all four procedures;
cascade-hard delete additionally requires admin role. Integration tests
assert happy-path response shapes, UNAUTHORIZED/FORBIDDEN error codes,
and error passthrough from the DSR service layer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Protocol-agnostic handlers (grant, withdraw, isGranted, getCategories)
in core-consent/handlers/ call IConsent methods and return typed results.
consentRouter uses a consent-specific tRPC context (userId + consentFactory)
so each procedure can resolve the per-user IConsent instance at call time.
Auth middleware guards all four procedures and maps UnauthenticatedError →
UNAUTHORIZED via defineErrorMiddleware from core-shared (no local duplicate).
76 tests passing; new handler and router code at 100% branch coverage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the Payload-backed IConsent that reads/writes users.consentState
and emits CONSENT_GRANT/CONSENT_WITHDRAW audit entries via injected auditLog.
Adds RecordingConsent test double in core-testing for unit-test injection.
Adds bindProductionConsent/bindDevSeedConsent DI binders and InMemoryConsent
for dev/seed contexts. Contract tests cover grant/withdraw/isGranted round-trip,
audit entry shape, metadata persistence (bannerVersion/policyVersion/method),
and getCategories reflection of state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add packages/core-consent with ConsentCategory, ConsentState,
UserConsentState types and IConsent interface
- Add withConsent wrapper attaching __consentChecked brand at bind time;
unit tests assert brand attachment and factory passthrough
- Add ConsentChecked<F> type to core-shared/conformance/brands.ts and
isConsentChecked helper to brand-runtime.ts
- Extend FeatureManifest with requiresConsent?: readonly string[] field
- Extend assertFeatureConformance to require __consentChecked brand when
requiresConsent.length > 0; synthetic fixture tests cover pass/fail cases
- Propagate __consentChecked in withSpan PROPAGATED_BRANDS so the outermost
binding carries the brand
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the `conformance/pii-declaration-must-be-complete` ESLint rule at
warn severity. The rule detects `custom: { pii: { ... } }` blocks in
Payload config files and warns when any of the four required sub-fields
(`category`, `purpose`, `exportable`, `restrictable`) is missing.
Incomplete PII declarations can produce incorrect audit reports —
sub-second editor feedback catches the gap before it reaches
compliance/data-map.yml.
- Rule + 7 RuleTester fixtures (complete passes, each missing field
warns, non-pii custom block is no-op, malformed custom.pii is no-op)
- Registered in plugin.js + base.js at "warn"
- Conformance rule count bumped 7 → 8 in CLAUDE.md +
conformance-quickref.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a ./react subpath export to @repo/core-analytics containing
<AnalyticsProvider value={IAnalytics}> and useAnalytics(): IAnalytics.
useAnalytics() throws AnalyticsContextError when called outside a provider.
React Testing Library test verifies track() flows through context using
RecordingAnalytics. Switches vitest config to pick up .tsx test files
via environmentMatchGlobs and extends tsconfig to react-library.json
for JSX support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds `withAnalytics(analytics, factory)` to packages/core-analytics —
mirrors the `withAudit` pattern: thin forwarding closure that attaches
the `__analyzed` brand via `attachBrand` from `@repo/core-shared/conformance`
without mutating the original factory. Exports `Analyzed<F>` type and
`withAnalytics` from the `@repo/core-analytics` root barrel.
Adds `with-analytics.test.ts` asserting brand is present after wrapping,
absent on the original fn, output passes through unchanged, and errors
propagate. Adds `@repo/core-shared` as a production dependency.
Also fixes `scripts/library-decisions/check.mjs` to exempt workspace-protocol
entries (`workspace:*`) from the library trace requirement — internal monorepo
packages are not third-party libraries and were incorrectly gated. Adds a
regression test in `check.test.mjs` covering the exemption.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add the `pnpm turbo gen core-package analytics` generator template and
run it to scaffold the @repo/core-analytics workspace package. The
package lands in placeholder state (empty barrel export) ready for the
IAnalytics + NoopAnalytics implementation in the next commit.
Includes:
- turbo/generators/templates/core-package/analytics/ templates
- turbo/generators/config.ts analytics generator registration
- packages/core-analytics/ placeholder scaffold
- apps/web-next/next.config.mjs transpilePackages entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates the shared schema module for library evaluation traces
(ADR-022 §4): Zod-validated frontmatter with all 8 filter fields and
enum constraints, plus parseTrace/validateTrace exports and a custom
YAML frontmatter parser for the nested trace format.
Also adds docs/library-decisions/_template.md with all 11 required
headings (8 Filter + 3 Prompt) in machine-checkable ADR-022 order.
Adds zod as a root devDependency so the script is runnable directly
from the workspace root without a package context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Lands L3 of the agent-first coverage architecture (ADR-020) — the
mutation-testing layer. Stryker on entities + use-cases (the pure
business-logic surface) catches the third dimension of test quality:
tests that exist + execute the code but assert nothing.
Deps (root devDependencies):
- @stryker-mutator/core ^8.7.0
- @stryker-mutator/vitest-runner ^8.7.0
Shared base: packages/core-testing/stryker.base.json
- testRunner: vitest (uses each feature's vitest.config.ts)
- mutate: src/entities/** + src/application/use-cases/** (excludes
tests, factories, contracts)
- thresholds: high 90 / low 80 / break 80
- reporters: progress + html + json (reports/mutation/{index.html,
mutation.json})
- incremental mode enabled, concurrency 4, timeout 10s
- exposed via @repo/core-testing/stryker.base.json subpath export
Per-feature config: packages/auth/stryker.config.json
- 4-line file that extends the shared base
- Proof-of-concept; other features get a config when L0 unification
closes their existing test gaps
Driver: scripts/coverage/mutate.mjs (zero-dep Node ESM)
- discoverStrykerConfigs: walks packages/* and apps/* for
stryker.config.json
- Supports --filter <name>, --since <ref> (incremental), --json
- Runs Stryker per-feature via node_modules/.bin/stryker run
- Surfaces per-package pass/fail summary; exits 1 on any failure
- Tests: scripts/coverage/mutate.test.mjs (3 tests, all green)
CI: .github/workflows/mutation-nightly.yml
- Cron at 02:30 UTC + workflow_dispatch with filter input
- Uploads reports/mutation/** as artifact (30-day retention)
- On failure, opens a tracking issue labelled mutation-testing
- permissions: contents: read, issues: write
- 60-min timeout (Stryker is slow by design)
Generator: turbo gen feature now scaffolds stryker.config.json from
turbo/generators/templates/feature/stryker.config.json.hbs — new
features ship mutation-ready out of the box.
Guide: docs/guides/coverage.md L3 section fleshed out with run
syntax, config shape, base config inventory, CI behavior, and a
"what you're looking for" primer on mutation scores.
Lockfile churn: pnpm regenerated the lockfile for the new deps;
~5K-line net reduction is collateral (pnpm version drift) but
mechanical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pnpm install on the merged tree pulled in @opentelemetry/api,
api-logs, context-async-hooks, and sdk-trace-base under
packages/core-audit's devDependencies. The merge commit didn't fully
include the resolved lockfile entries; this commit aligns them so CI
installs cleanly.
Adds @opentelemetry/api-logs ^0.55.0 and @opentelemetry/sdk-logs ^0.55.0
to core-shared dependencies in preparation for OtelLogger (Phase 3).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements OtelTracer as ITracer replacement for SentryTracer. Uses
@opentelemetry/api's trace.getTracer + startActiveSpan, maps span.op to
a span attribute, catches exceptions via recordException + setStatus(ERROR),
and filters null attribute values. Five Vitest tests covering name/attributes,
nesting, exception recording, status mapping, and null filtering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The turbo/generators package shipped without `lint` or `typecheck` scripts,
so `pnpm lint` / `pnpm typecheck` at the root silently skipped it. This
masked 2 ESLint errors (unused imports) and 11 TypeScript errors (relative
imports missing the `.js` extension required by `moduleResolution: NodeNext`,
plus JSON imports missing the `with { type: "json" }` attribute).
- Add `lint` and `typecheck` scripts to turbo/generators/package.json so the
turbo pipeline picks them up (lint: 14/14, was 13/13).
- Add `.js` extensions to 7 relative imports across config.test.ts,
lib/core-package-utils.test.ts, lib/snapshot.test.ts, and the 4 e2e tests.
- Add `with { type: "json" }` attributes to 4 snapshot JSON imports in the
e2e tests.
- Remove unused `existsSync` and `splicePluginImportsAt` imports from
lib/core-package-utils.test.ts.
- Declare `@repo/core-typescript` + `typescript` devDependencies so the
generators package can run `tsc --noEmit` for typecheck.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removal across all three consumers:
- apps/storybook: strips @repo/core-ui dep, clears stories glob (now []),
removes globals.css import from preview.ts
- apps/web-next: strips @repo/core-ui dep + transpilePackages entry
- apps/web-tanstack: strips @repo/core-ui dep
- packages/core-ui: deleted entirely (28 files)
No app pages needed surgery — neither web-next nor web-tanstack source
files imported @repo/core-ui directly. The storybook app referenced it
only via the stories glob and the globals.css preview import.
Gates: 40/40 tasks successful, all 4 e2e byte-identical reconstructions
pass (realtime, events, trpc, ui).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds turbo/generators to pnpm-workspace.yaml and declares its actual
dependencies so pnpm turbo boundaries reports zero issues.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registers pnpm turbo gen core-package with an empty choices list and a
dispatch table that throws for unknown names. Adds vitest config + test
script to turbo/generators and adds it to the pnpm workspace so tests
run via pnpm --filter @repo/turbo-generators test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces `next dev` / `next start` with a custom Node http server that
co-hosts Next.js and Socket.IO on the same port, wiring in the
RealtimeHandlerRegistry and session-cookie-based IRealtimeAuthenticator.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend bindProductionAuth and bindDevSeedAuth to 7-arg signatures.
Fix RecordingRealtimeBroadcaster scope type to structurally match ChannelScope.
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'].
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'].
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.