usecase-must-be-wired returned {} when the manifest parsed to null,
silently disabling the error-level gate when the manifest existed but
could not be read. It now reports unparseableManifest on Program in
that case; a genuinely missing manifest stays a no-op (that is
feature-must-have-manifest's job).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A manifest written as `{...} satisfies FeatureManifest` (or the combined
`as const satisfies` idiom) parsed to null, silently no-oping the
error-level conformance rules. unwrapAsConst now strips TSAsExpression
and TSSatisfiesExpression in a loop. The file also carried a verbatim
second copy of its own parser for parseManifestFully; both public entry
points now share one implementation. The template's field set (audits,
rateLimit, requiresConsent) is preserved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two CLAUDE.md conventions had no mechanical gate, so both drifted:
entity models shipped without sibling tests, and feature test files
imported src modules via `../` instead of the `@/` alias.
- `entity-must-have-test` — every entities/models/<x>.ts needs a sibling
<x>.test.ts (errors and barrels excluded).
- `no-relative-parent-import-in-tests` — feature test files must import
src via `@/`, not `../`. Scoped to feature packages; core packages are
governed by their own generator templates.
Both register at warn level, bringing the conformance rule count to 15.
The rule only matched bus.publish("string-literal", ...), but the
canonical pattern that `gen event` prescribes is
bus.publish(eventDescriptor, payload) — an imported identifier, never a
literal. The rule therefore never fired on real code, which is how the
auth signUp publish drifted from its manifest undetected.
Add `_event-ast.js`: resolves a `bus.publish(<identifier>)` argument by
following the import to the event-contract file and extracting the name
from either `defineEvent("...", schema)` or an inline `{ name }` object.
Unresolvable arguments are skipped, so the rule never false-positives.
Adds the `no-undeclared-rate-limit` ESLint rule (warn severity) that
enforces rate-limit drift at lint time:
- Warns when rateLimit.consume("X", _) is called inside a use-case but
"X" is absent from manifest.useCases[name].rateLimit
- Warns when a declared rateLimit budget has no matching consume call
in the use-case body (unusedDeclaration)
- Is a no-op outside use-case files
Extends _manifest-ast.js to extract the rateLimit[] field from both
parseManifestUseCases and parseManifestFully. Updates _manifest-ast
tests to include the new field in expected shapes. Registers the rule
at warn severity in plugin.js and base.js. Adds RuleTester fixtures
for all four cases (declared+matching, undeclared, unused, non-use-case).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the conformance ESLint layer with the consent-check rule:
- `no-undeclared-consent-check` (warn): `consent.isGranted("X")` in a
use-case file must match a category declared in `manifest.requiresConsent`;
also warns when requiresConsent is declared but no isGranted call is found.
- `_manifest-ast.js`: adds `parseManifestFully` which extracts top-level
`name`, `requiredCores`, `requiresConsent`, and per-use-case maps from the
manifest AST; `requiresConsent` extraction tested in `_manifest-ast.test.js`.
- `_rule-context.js` / `_rule-schema.js`: shared helpers extracted from the
existing per-rule files so the new rule can resolve use-case name + feature
root without duplication.
- Existing rules (`no-undeclared-audit`, `no-undeclared-event-publish`,
`no-undeclared-analytics-event`) updated to use the shared helpers.
- `plugin.js` + `base.js` register the rule at warn severity.
- CLAUDE.md + conformance-quickref.md: rule count advanced from 11 → 12.
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>
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).
AST-aware ESLint rule that catches auditLog.record({ type: "X" }) calls
in use-case files where X is not declared in the matching
manifest.useCases[name].audits array.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AST-aware ESLint rule that catches bus.publish("X") calls in use-case
files where X is not declared in the matching manifest.useCases[name].publishes array.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- no-direct-socket-io: extend allowlist to cover apps/*/src/**/*.test.ts so
the realtime-ping e2e integration test can import socket.io/socket.io-client
directly; add two valid test cases to keep the rule's own test suite green
- tsconfig.json (root): add root-level tsconfig with experimentalDecorators +
emitDecoratorMetadata and no "include" so tsx 4.21.0's createFilesMatcher
resolves decorator config for all workspace packages, not just web-next's
own source tree
- web-next dev script: pass TSX_TSCONFIG_PATH=../../tsconfig.json so the
custom Node server uses the root tsconfig for all modules it loads
- next.config.mjs: add @repo/core-events and @repo/core-realtime to
transpilePackages so Next.js webpack can resolve their workspace source files
- server.ts: replace static authContainer import with a dynamic import inside
IRealtimeAuthenticator.authenticate so Inversify decorators are applied only
after bindAll() has already populated the container
All CI gates pass: lint (0 errors), typecheck, 20 tests (incl. realtime-ping
e2e), boundaries (0 issues).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>