7.2 KiB
7.2 KiB
Tasks — Auth requireApiPermission middleware
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
Audit Note (2026-05-02)
Minimal progress — DEFERRED, do not archive.
- No
packages/auth/auth/src/api-keys/require-api-permission/directory exists. grep -rn "requireApiPermission\|requireAnyApiPermission\|requireAllApiPermissions" packages/auth/auth/src/returns zero hits.bunx turbo lint typecheck test --filter=@suaveplan/authis GREEN (161 tests) — but the green is on the existing surface; the new exports promised by this proposal are not implemented.- All 43 tasks remain to be done as written (spec authoring, three-middleware implementation, OTel counter, Zod boundary validation, sibling
.md, README, subpath export, conformance test).
1. Spec authoring
- 1.1 Read
../suaveplan/enterprise/auth.mdend-to-end (confirmrequireApiPermissionis documented at L1-L100 baseline) - 1.2 Read
openspec/specs/auth/spec.md(canonical capability spec; this change introduces it if absent) - 1.3 Read
openspec/AGENTS.mdSections 13 and 14 - 1.4 Read existing
packages/auth/auth/src/api-keys/api-keys.tsandauth-middleware/auth-middleware.tsto confirm theMiddlewareFnandnext(error)patterns - 1.5 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.6 Fill
## Source mappingtable with upstreamapi-keys.tsL487-L506 reference - 1.7 Draft
specs/auth/spec.mdfromopenspec/templates/spec-delta.md - 1.8 Every requirement carries a
**Verified by:**line - 1.9
openspec change validate auth-require-api-permissionexits with no errors
2. Implementation
- 2.1 Create new feature directory
packages/auth/auth/src/api-keys/require-api-permission/ - 2.2 Implement
requireApiPermission(permission, options?): MiddlewareFninrequire-api-permission.ts:- Read
req.apiKey(cast through the samer as Record<string, unknown>pattern used invalidateApiKeyMiddleware). - If absent, call
next(new ApiKeyInvalidError({ message: "API key required for permission check" }))and return. - If present, check
hasPermission(apiKey, permission). On miss, callnext(new PermissionDeniedError({ permission, userId: apiKey.userId })). - On match, call
next().
- Read
- 2.3 Implement
requireAnyApiPermission(permissions, options?): MiddlewareFn— passes ifpermissions.some(p => hasPermission(apiKey, p)) - 2.4 Implement
requireAllApiPermissions(permissions, options?): MiddlewareFn— passes ifpermissions.every(p => hasPermission(apiKey, p)) - 2.5 Emit OTel counter
auth.apikey.permission_check_totaltagged{ result: "granted" | "denied" | "missing-key" } - 2.6 Reject empty
permissionsarray inrequireAnyApiPermission/requireAllApiPermissionsat construction time (throwApiKeyInvalidErrorwithmessage: "permissions array must be non-empty") - 2.7 Write barrel
index.tsre-exporting the three middleware factories - 2.8 Sibling
.md(≥200 words, basic + advanced examples)
3. Wiring
- 3.1 Update
packages/auth/auth/src/index.tsto re-exportrequireApiPermission,requireAnyApiPermission,requireAllApiPermissionsfrom the new feature directory - 3.2 Add subpath export
./api-keys/require-api-permission/require-api-permissiontopackages/auth/auth/package.jsonexports - 3.3 Update root
README.md## Modulessection to link the new co-located.md
4. Tests
- 4.1
packages/auth/auth/src/api-keys/require-api-permission/require-api-permission.test.ts— happy path (apiKey has permission,next()called with no args); missingreq.apiKey(next(ApiKeyInvalidError)); missing permission (next(PermissionDeniedError)); wildcard*permission grants any (next()called with no args) - 4.2
requireAnyApiPermissiontests — passes with subset match; fails when none match; throws on empty array - 4.3
requireAllApiPermissionstests — passes when all match (including via wildcard); fails on partial match; throws on empty array - 4.4
packages/auth/auth/src/api-keys/require-api-permission/require-api-permission.spec-conformance.test.ts—expectTypeOf<ReturnType<typeof requireApiPermission>>().toMatchTypeOf<MiddlewareFn>(); OTel counterauth.apikey.permission_check_totalincrements with the rightresulttag for each branch; works under Express-style(req, res, next)AND Hono-style(c, next)adapter shapes - 4.5 Every
it(...)carries{ timeout: 5000 }
5. Documentation
- 5.1 Sibling
.mdforrequire-api-permission.ts(≥200 words, basic + advanced examples, full API reference covering all three factories) - 5.2 README links the new
.mdunder## Modules - 5.3 Update
api-keys.md## Featuresbullet list to mention the new permission-check middleware (cross-link, do not duplicate the API reference)
6. Pre-archive verification (the 5-gate triple)
- 6.1
bun run check— Biome 0/0/0 across the monorepo - 6.2
bunx turbo lint typecheck test --filter=@suaveplan/auth— green - 6.3
bunx turbo test:coverage --filter=@suaveplan/auth— 100/100/100/100 - 6.4
bun scripts/openspec-conformance.ts @suaveplan/auth— exit 0, noerrorfindings - 6.5
bunx turbo build --filter=@suaveplan/auth— green
7. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 7.1 Generics threaded;
expectTypeOfassertion that the return type matchesMiddlewareFn - 7.2 Zod schemas wired at the public boundary — accept
permission: stringvalidated as non-empty - 7.3 Existing error codes (
AUTH.API_KEY_INVALID,AUTH.PERMISSION_DENIED) thrown from the new production paths; no new codes registered (none needed) - 7.4 Strict-mode tested: explicit
undefinedapiKey vs missing apiKey property both route toApiKeyInvalidError - 7.5 OTel counter
auth.apikey.permission_check_totalemitted from production paths and asserted with a real in-memoryMeterProvider - 7.6 Lifecycle dedup: middleware is a fresh closure per call to the factory; verified that two factory calls produce independent
MiddlewareFninstances - 7.7 No retroactive tick — every box reflects work done in this change
8. Archive
- 8.1 Fold spec delta into
openspec/specs/auth/spec.md(creating it if absent) - 8.2 Move
openspec/changes/auth-require-api-permission/→openspec/archive/<yyyy-mm-dd>-auth-require-api-permission/ - 8.3 Refresh
MANIFEST.md(no package set or dep changes expected, but rerun for safety)