Adds retention-purge.job.ts to packages/core-shared/src/payload/retention-purge/.
Walks every Payload collection's custom.retention.purgeSchedule, registers one
scheduled job per collection via IJobQueue, queries rows past their activeRetention
period (by createdAt for from-creation, updatedAt for from-last-access), and applies
pseudonymize (null PII fields) or hard-delete per postDeletion.action. Emits one
IAuditLog.record entry per processed row; gracefully skips when auditLog is absent.
Unit tests cover: schedule registration, trigger-type routing, hard-delete branch,
pseudonymize branch, audit emission, graceful auditLog skip, no-activeRetention
short-circuit, and input validation error cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Amends ADR-022 §9 with the `is-sub-processor` / `processes-pii` discriminated
union spec, including the five conditional fields required when a library is a
true GDPR sub-processor. Updates the evaluate-library skill to prompt for these
fields during every trace authoring pass and adds the updated frontmatter
template. Backfills all nine existing library-decision traces with the new
fields; payload gets `processes-pii: true` (self-hosted CMS that stores user
data); all pure in-process libraries get `false / false`.
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>
Ambient declaration files have no runtime code so v8 coverage
never generates DA records for them. Without an allowlist entry,
coverage:diff reports no-coverage-data for every .d.ts in the
diff. Add /\.d\.ts$/ to ALLOWED_GLOBS with a companion test.
Also configure @vitest/coverage-v8 for core-shared and add
targeted vitest exclusions for infrastructure files that are not
unit-testable (DI symbols, interface files, tRPC context, Sentry
SDK init) — bringing core-shared into the L2 aggregate and making
the L1 diff gate enforce coverage on new executable code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces PiiCategory, DataProcessingPurpose, RetentionTrigger,
RetentionAction, FieldPii, FieldRetention, PAYLOAD_AUTH_PII_DEFAULTS,
PurgeSchedule, and CollectionRetention in core-shared/payload/.
Augments payload's FieldCustom and CollectionCustom interfaces via
ambient declaration so downstream collection configs gain typed
custom.pii and custom.retention / custom.authPii fields.
Credential fields (password, salt, hash, resetPasswordToken,
resetPasswordExpiration, loginAttempts, lockUntil, apiKey, apiKeyIndex)
are null in PAYLOAD_AUTH_PII_DEFAULTS to exclude security material
from DPA mapping. Adds @vitest/coverage-v8 and coverage exclusions
for boilerplate infrastructure files so coverage:diff is gated on
new executable code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generated 1 epic + 8 stories under docs/work/epics/ from the approved
PRD docs/work/prds/compliance-manifests-pii-retention-subprocessors.prd.md.
Stories cover type primitives, ESLint rule, ADR-022 amendment, retention
purge job, template backfill, three generator scripts, pre-commit + CI
integration, and docs/compliance/ reference files. Ready for
`pnpm work dispatch --execute` to begin implementation.
Implementation seed for ADR-025 Epic A: declarative PII inventory +
retention + sub-processor manifests with three generators, pre-commit +
CI drift detection, background purge job, and ADR-022 trace
frontmatter extension for sub-processor fields (discriminated union).
Eleven user stories ordered for the decomposer; five open questions
with recommendations. Status: approved — ready for pnpm work decompose.
ADR-025 plans 4 epics that raise the template's DPA/GDPR coverage from
~50% (ADR-017/018/022/023/024) to ~80%. Adds two optional cores
(core-dsr, core-consent), a fourth conformance channel (rate-limit),
three new manifest fields, three new generators with CI drift gates,
and a docs/compliance/ + compliance/ split for templates vs evidence.
Documents the audit ↔ DSR sibling-not-overlap distinction (audit
records access; DSR acts on the underlying data). Four explicit
deferrals with revisit triggers (RBAC, MFA, breach detection, Art. 22).
Glossary updated with DSR, Consent, Rate-limit, PII inventory, Retention
policy, Sub-processor, docs/compliance/ vs compliance/ entries.
Add the seventh ESLint rule (no-undeclared-analytics-event) to the rule
table in conformance-quickref.md and the drift patterns section. Update
the rule count in CLAUDE.md from six to seven. Add core-analytics to the
optional-cores table in template-tiers.md pointing at ADR-024 and the
new analytics guide.
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 analytics?: Analytics generic field to both BindContext and
BindProductionContext in bind-context.ts, mirroring the pattern used by
IEventBus, IAuditLog, and IJobQueue. AnalyticsProtocol already existed
in bind-protocols.ts and is re-exported from the @repo/core-shared/di
barrel via the existing wildcard export.
Also adds a type-level test for AnalyticsProtocol in bind-protocols.test.ts
to match test coverage for the other protocol types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the existing Audited check: when a use case declares
analyticsEvents.length > 0 and the bound function lacks the __analyzed
brand, assertFeatureConformance throws ConformanceError at boot time.
Adds three synthetic conformance tests: passes when brand present +
events declared, throws naming Analyzed when events declared + brand
missing, passes when events empty + brand absent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add AnalyticsProtocol to bind-protocols, extend WireUseCaseOptions with
optional analytics field, and compose the __analyzed brand inline in
wireUseCase (innermost, before withAudit) when analytics is provided.
Propagate __analyzed through withCapture and withSpan PROPAGATED_BRANDS
so the outermost container binding carries the brand for boot-time
assertion checks (Story 05).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add optional `analyticsEvents?: readonly string[]` to `UseCaseManifest`
in `define-feature.ts` so manifests can declare which analytics events a
use case emits. Field defaults to absent (treated as []) — all existing
manifests remain valid without changes.
Update the feature generator template to emit `analyticsEvents: []` so
newly scaffolded features are analytics-declaration-ready from day one.
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>
Adds the `Analyzed<F>` phantom-type brand to brands.ts and the
`isAnalyzed` type-guard to brand-runtime.ts, mirroring the existing
Instrumented/Captured/Audited pattern. Exports both from the
conformance index so Story 04 (wireUseCase) can key off the brand
without a circular dep.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements IAnalytics with recorded arrays (tracked, identified,
pageViewed) and flush() that drains the buffer. Mirrors the pattern
established by RecordingAuditLog; inline type aliases avoid a
build-graph cycle with @repo/core-analytics.
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>
Generated 1 epic + 9 stories under docs/work/epics/ from the approved
PRD docs/work/prds/product-analytics-channel.prd.md. Stories follow the
sequencing hints from the PRD (brand+wrapper → manifest+wireUseCase →
assertFeatureConformance → BindContext+ESLint → React provider → docs).
Ready for `pnpm work dispatch --execute` to begin implementation.
Implementation seed for ADR-024. Nine user stories, ordered sequencing
hints for the decomposer (brand+wrapper → manifest+wireUseCase →
assertFeatureConformance → BindContext+ESLint → React provider → docs).
Status: approved — ready for `pnpm work decompose --execute` to generate
the epic + stories under `docs/work/epics/product-analytics-channel/`.
ADR-024 codifies IAnalytics as an optional-core channel parallel to
IAuditLog. Mirrors the audit pattern (inline emission, manifest field,
brand at bind time, ESLint cross-check) with three deliberate
divergences: no `mutates` gate on the brand check, flush() on the
interface for batched server-side SDKs, and a React provider scaffold
in core-analytics/react so server + client share the same contract.
PII boundary is explicitly distinct from ADR-017's observability policy:
traits-allowed at the interface, consumer owns consent + retention.
Glossary updated with IAnalytics and withAnalytics entries.
Catches manifest use cases that aren't wired through wireUseCase(...) in
bind-production.ts / bind-dev-seed.ts. wireUseCase is the canonical helper
that attaches __instrumented / __captured / __audited brands — skipping
it produces an unbranded binding that assertFeatureConformance would
reject at boot. This rule shifts that detection from ~3s (boot) to <1s
(lint), keeping the layered conformance pattern: TS brands (compile),
ESLint (lint), boot assertion (dev), smoke tests (CI).
CLAUDE.md + conformance-quickref.md updated for the new rule (5 → 6).
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.
The previous layout placed epic folders directly under docs/work/
alongside prds/ and _system/. Tightening: epics now live in their
own docs/work/epics/ subfolder, peer to prds/ and _system/. Same
shape as the existing prds/ bucket.
Final docs/work/ layout:
README.md
prds/<slug>.prd.md
_system/_state.json
epics/<slug>/_epic.md + <story-folder>/_story.md
Renames (git mv preserves history):
- docs/work/binder-wrap-helper/
-> docs/work/epics/binder-wrap-helper/
- docs/work/library-evaluation-policy/
-> docs/work/epics/library-evaluation-policy/
- docs/work/ci-security-and-supply-chain/
-> docs/work/epics/ci-security-and-supply-chain/
Tooling updates:
- state-builder.mjs walks workRoot/epics/ directly; SKIP_FOLDERS
obsoleted (no more sibling folders to filter out).
- dispatch.mjs's findNextTask, tickStoryBulletInEpic, and
flipEpicDoneIfAllStoriesDone all join with "epics" segment.
- prd-ship.mjs's deriveShippingCommits walks workRoot/epics/ and
git-logs docs/work/epics/<epic>/.
- decomposer.prompt.md emits epics under docs/work/epics/<epic-id>/.
- handoff + grill-with-docs glossary references updated.
- Glossary entry for Epic updated.
Reserved future shape: when a task-tracker integration (ClickUp,
Linear) ships, the epics/ subfolder hosts <task-id>-<slug>/
folders. Today it just hosts bare slugs.
Convention shift: epic folders + PRD filenames + frontmatter id
fields are now bare slugs. The created: timestamp (Phase 2) carries
the date; folder names don't repeat it. A future <task-id>-<slug>
shape (e.g. ClickUp) lands cleanly when that integration ships.
Renames (git mv preserves history):
- docs/work/2026-05-13-binder-wrap-helper/
-> docs/work/binder-wrap-helper/
- docs/work/2026-05-14-library-evaluation-policy/
-> docs/work/library-evaluation-policy/
- docs/work/2026-05-14-ci-security-and-supply-chain/
-> docs/work/ci-security-and-supply-chain/
- docs/work/prds/2026-05-13-binder-wrap-helper.prd.md
-> docs/work/prds/binder-wrap-helper.prd.md
- docs/work/prds/2026-05-13-coverage-architecture.prd.md
-> docs/work/prds/coverage-architecture.prd.md
- docs/work/prds/2026-05-14-library-evaluation-policy.prd.md
-> docs/work/prds/library-evaluation-policy.prd.md
- docs/work/prds/2026-05-14-ci-security-and-supply-chain.prd.md
-> docs/work/prds/ci-security-and-supply-chain.prd.md
Frontmatter updates inside the renamed files: epic id, epic prd,
story epic, PRD id, PRD builds-on all drop date prefixes.
System folder + state file move:
- New docs/work/_system/ holds framework-managed state.
- docs/work/_state.json -> docs/work/_system/_state.json.
- state-builder.mjs adds _system to SKIP_FOLDERS.
- cli.mjs + state-sync-guard.mjs + .husky/pre-commit point at the
new path.
template-reset-v1 epic deleted entirely (one-off cleanup epic from
the pre-date-convention era; status was already done).
Generator-template updates (so new artifacts ship in the right
shape):
- .sandcastle/decomposer.prompt.md emits bare-slug folder names +
ISO created: timestamp.
- .claude/skills/to-prd/SKILL.md template uses bare-slug filename +
bare-slug id field + ISO created: timestamp.
Doc reference updates: glossary, runbook, agent-first-workflow-
and-conformance, reviewer prompt, ADR-020, ADR-022, ADR-023 all
point at the new paths/slugs.
- New scripts/work/bump-updated-timestamps.mjs stamps the `updated:`
frontmatter field to the current ISO 8601 UTC timestamp on every
staged docs/work/**/*.md file. Idempotent; adds the field after
`created:` if missing.
- .husky/pre-commit invokes the bump script as step 2 (before
rebuild-state) so _state.json sees the fresh timestamp.
- Backfill all existing work docs (4 PRDs + 3 epics + 21 stories):
* created: promoted from \`YYYY-MM-DD\` -> ISO timestamp using
git log --diff-filter=A on each file (first-commit date for
stories that had no \`created:\` line, midnight UTC for PRDs
and epics that had date-only created).
* updated: added from \`git log -1 --format=%aI\` on each file
(last-commit timestamp); will be re-stamped to "now" by the
pre-commit hook on this commit.
Stories that had no \`created:\` line now get one.
- Add tickStoryBulletInEpic(workRoot, epic, story) helper that finds
the bullet in the parent epic's `## Stories` section linking to the
given story folder and flips `- [ ]` to `- [x]`. Idempotent.
- applyApprovedState now ticks the parent epic bullet whenever a
story flips to status: done (alongside the existing per-task tick
and epic-status flip). Epic file gets staged on either ticked-or-
flipped, not just flipped.
- Backfill all 3 existing epics: 21 bullets ticked to match their
already-done story statuses (binder-wrap-helper x3, library-
evaluation-policy x9, ci-security-and-supply-chain x9).