Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

feat(permissions): @Public() decorator + route-gating policy (prep for #47) - #52

Merged
pascal-klesse merged 4 commits into
mainfrom
feat/route-gating-guardrails
May 2, 2026
Merged

feat(permissions): @Public() decorator + route-gating policy (prep for #47)#52
pascal-klesse merged 4 commits into
mainfrom
feat/route-gating-guardrails

Conversation

@pascal-klesse

Copy link
Copy Markdown
Member

Summary

Lands the guardrails so AI agents (and humans) cannot ship unguarded
routes by accident:

  • @Public("<reason>") decorator at src/core/permissions/public.decorator.ts
    required-reason argument forces explicit consent at the route site
  • Project-level CLAUDE.md "Route gating policy" — every route is gated,
    @Public(), or path-allowlisted; no fourth option
  • Skill update .claude/skills/wiring-permissions.md — decision flow +
    when @Public() is appropriate + migration diff + test recipe

Not in this PR

This PR ships the prerequisites those rely on (the decorator + the
documented policy). Issue #47 then enforces it across all controllers
and adds the CI check.

Test plan

  • Story test for the decorator: metadata shape, reason required, type-guard
  • All quality gates pass locally: lint, format, test:types, test:unit, build:dev-portal, test:e2e (2580/2580), test:coverage (public.decorator.ts 100% on lines/functions/statements/branches; core/permissions 94.39% / 96.29% lines stays above the 90% gate), build
  • CI green

🤖 Generated with Claude Code

Ralph Loop added 4 commits May 2, 2026 22:24
Pins the metadata shape and `isPublicRoute()` reader for the
guardrail decorator that lands the explicit-consent token for
route gating (prep for Issue #47).
Adds the explicit-consent token an HTTP handler can carry to opt out
of permission gating. Metadata-only — runtime gating still happens via
the existing JWT middleware path-allowlist + CanGuard. The required
`reason` argument forces an explanation at every decoration site so
the future audit / CI gate (Issue #47) can surface why a route is
public.

The `isPublicRoute()` type-guard recognises only the literal boolean
`true` so a JSON-roundtripped value cannot pose as consent.
Documents the rule that every HTTP handler is `@Can()`-gated,
`@Public()`-consented, or path-allowlisted — with no fourth option.
Issue #47 will enforce this at build time; this commit lands the
written policy that tooling and humans can both reference.
Surfaces the route-gating policy in the agent workflow:

- Decision flow at the top — pick @can() vs @public() vs allowlist
  before writing the route body
- "When @public() is appropriate" checklist (health, OAS, anonymous
  webhooks with own HMAC, marketing pages)
- Migration diff showing how to fix an unguarded handler either way
- Test recipe for the @public() metadata using
  PUBLIC_ROUTE_METADATA_KEY + isPublicRoute() type-guard
- Cross-link to project CLAUDE.md and Issue #47
@pascal-klesse
pascal-klesse merged commit fb7af1d into main May 2, 2026
10 checks passed
@pascal-klesse
pascal-klesse deleted the feat/route-gating-guardrails branch May 4, 2026 12:08
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant