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

Commit 2bbddb9

Browse files
z23ccclaude
andcommitted
feat(skills): add 12 engineering skills across 4 domains (v0.1.41)
High-frequency (6): - flow-code-observability: structured logging, distributed tracing, metrics (RED/USE) - flow-code-database: migration safety, N+1 detection, indexing, cursor pagination - flow-code-error-handling: retry with backoff, circuit breaker, graceful degradation - flow-code-containerization: multi-stage Docker, K8s probes, security hardening - flow-code-i18n: ICU messages, Intl APIs, RTL support, locale formatting - flow-code-state-management: state classification ladder, server state, Context pitfalls Architecture (6): - flow-code-realtime: WebSocket/SSE/polling selection, reconnection, heartbeat - flow-code-auth: OAuth+PKCE, JWT lifecycle, RBAC, resource-level authorization - flow-code-caching: cache-aside, HTTP caching, TTL/invalidation strategies - flow-code-monitoring: SLO/SLI, four golden signals, alerting rules, runbook template - flow-code-documentation: ADRs, README structure, API docs, changelog, doc-as-code - flow-code-microservices: bounded contexts, saga, event-driven, data ownership Skill guide updated with categorized table (Core/Frontend/Backend/Infra/Quality). Total engineering skills: 24. Total plugin skills: 49. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8bff08a commit 2bbddb9

15 files changed

Lines changed: 2139 additions & 3 deletions

File tree

bin/flowctl

-16 Bytes
Binary file not shown.

flowctl/crates/flowctl-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "flowctl-cli"
3-
version = "0.1.40"
3+
version = "0.1.41"
44
description = "CLI entry point for flowctl"
55
edition.workspace = true
66
rust-version.workspace = true

skills/flow-code-auth/SKILL.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
name: flow-code-auth
3+
description: "Use when implementing authentication, authorization, OAuth, JWT, RBAC, or session management. Covers token lifecycle, permission models, and secure session handling."
4+
tier: 2
5+
user-invocable: true
6+
---
7+
<!-- SKILL_TAGS: auth,authentication,authorization,jwt,oauth,rbac -->
8+
9+
# Authentication & Authorization
10+
11+
## Overview
12+
13+
Authentication (who are you?) and authorization (what can you do?) are separate concerns. Implement them in separate layers, test them independently, and never trust the client.
14+
15+
## When to Use
16+
17+
- Adding login/signup flows
18+
- Implementing OAuth (Google, GitHub, etc.)
19+
- Designing role-based access control (RBAC)
20+
- Managing JWT tokens or sessions
21+
- Adding API key authentication
22+
- Implementing multi-tenant access control
23+
24+
## Authentication Patterns
25+
26+
### Session-Based (Server-Side)
27+
28+
```
29+
Client Server
30+
│── POST /login ──────>│ Verify credentials
31+
│<── Set-Cookie: sid ──│ Create session in store
32+
│── GET /api (cookie) ─>│ Lookup session by cookie
33+
│<── 200 data ─────────│
34+
```
35+
36+
**Best for:** Traditional web apps, SSR frameworks (Next.js, Rails, Django).
37+
38+
### Token-Based (JWT)
39+
40+
```
41+
Client Server
42+
│── POST /login ──────>│ Verify credentials
43+
│<── { accessToken, ──│ Sign JWT
44+
│ refreshToken } │
45+
│── GET /api ──────────>│ Verify JWT signature
46+
│ Authorization: │ (no DB lookup needed)
47+
│ Bearer <token> │
48+
│<── 200 data ─────────│
49+
```
50+
51+
**Best for:** APIs, SPAs, mobile apps, microservices.
52+
53+
### Token Lifecycle
54+
55+
```
56+
Access Token: short-lived (15 min), stored in memory
57+
Refresh Token: long-lived (7-30 days), stored in httpOnly cookie
58+
API Key: no expiry, stored server-side, revocable
59+
60+
Flow:
61+
1. Login → get access + refresh tokens
62+
2. Access token expires → use refresh token to get new pair
63+
3. Refresh token expires → force re-login
64+
4. Logout → revoke refresh token server-side
65+
```
66+
67+
**Rules:**
68+
- Access tokens in memory (not localStorage — XSS vulnerable)
69+
- Refresh tokens in httpOnly secure cookies (not accessible to JS)
70+
- Rotate refresh tokens on every use (detect theft)
71+
- Maintain a revocation list for compromised tokens
72+
73+
### OAuth 2.0 / OIDC
74+
75+
```
76+
1. Redirect user to provider: /authorize?client_id=...&redirect_uri=...&scope=openid
77+
2. User authenticates with provider
78+
3. Provider redirects back with authorization code
79+
4. Server exchanges code for tokens (server-to-server, code never exposed to client)
80+
5. Server creates session or issues own JWT
81+
```
82+
83+
**Rules:**
84+
- Always use Authorization Code flow (not Implicit — deprecated)
85+
- Use PKCE for public clients (SPAs, mobile)
86+
- Validate `id_token` signature and claims (issuer, audience, expiry)
87+
- Store provider tokens server-side (never send to client)
88+
- Request minimum scopes needed
89+
90+
## Authorization Patterns
91+
92+
### RBAC (Role-Based Access Control)
93+
94+
```typescript
95+
// Define roles and permissions
96+
const ROLES = {
97+
admin: ['read', 'write', 'delete', 'manage_users'],
98+
editor: ['read', 'write'],
99+
viewer: ['read'],
100+
} as const;
101+
102+
// Check permission (not role!)
103+
function requirePermission(permission: string) {
104+
return (req, res, next) => {
105+
const userPerms = ROLES[req.user.role];
106+
if (!userPerms?.includes(permission)) {
107+
return res.status(403).json({ code: 'FORBIDDEN', message: 'Insufficient permissions' });
108+
}
109+
next();
110+
};
111+
}
112+
113+
// Usage: check permission, not role
114+
app.delete('/api/posts/:id', requirePermission('delete'), deletePost);
115+
```
116+
117+
**Rule:** Check permissions, not roles. Roles map to permissions, but code should check `canDelete`, not `isAdmin`.
118+
119+
### Resource-Level Authorization
120+
121+
```typescript
122+
// ALWAYS check ownership — not just authentication
123+
async function updatePost(userId: string, postId: string, data: Partial<Post>) {
124+
const post = await db.posts.findById(postId);
125+
if (!post) throw new NotFoundError();
126+
if (post.authorId !== userId && !hasPermission(userId, 'admin')) {
127+
throw new ForbiddenError(); // Authenticated but not authorized
128+
}
129+
return db.posts.update(postId, data);
130+
}
131+
```
132+
133+
### Multi-Tenant Isolation
134+
135+
```typescript
136+
// Every query scoped to tenant
137+
function getTenantPosts(tenantId: string, userId: string) {
138+
return db.posts.findMany({
139+
where: { tenantId, authorId: userId }, // Always include tenantId
140+
});
141+
}
142+
143+
// Middleware enforces tenant context
144+
function tenantMiddleware(req, res, next) {
145+
req.tenantId = extractTenantFromToken(req);
146+
if (!req.tenantId) return res.status(403).json({ code: 'NO_TENANT' });
147+
next();
148+
}
149+
```
150+
151+
## Common Rationalizations
152+
153+
| Rationalization | Reality |
154+
|---|---|
155+
| "We'll add auth later" | Every unauthenticated endpoint becomes tech debt. Add auth from day one. |
156+
| "Just check if user is admin" | Check permissions, not roles. Roles change; permissions are the contract. |
157+
| "JWT in localStorage is fine" | Any XSS vulnerability = stolen tokens. Use httpOnly cookies for refresh tokens. |
158+
| "OAuth is too complex for now" | OAuth libraries handle the complexity. Don't build your own auth system. |
159+
| "We trust our API clients" | Never trust clients. Validate permissions server-side for every request. |
160+
161+
## Red Flags
162+
163+
- Passwords stored as plaintext or MD5/SHA1
164+
- JWT stored in localStorage (XSS-vulnerable)
165+
- Authorization checks only in the UI (not server-side)
166+
- Role checks instead of permission checks (`if (user.role === 'admin')`)
167+
- No refresh token rotation (theft undetectable)
168+
- OAuth Implicit flow (deprecated, use Authorization Code + PKCE)
169+
- Missing resource-level auth (authenticated users can access any resource)
170+
- Hardcoded API keys in source code
171+
- No session expiry or token expiry
172+
173+
## Verification
174+
175+
- [ ] Authentication and authorization in separate middleware layers
176+
- [ ] Passwords hashed with bcrypt/scrypt/argon2
177+
- [ ] Access tokens short-lived (15 min), refresh tokens in httpOnly cookies
178+
- [ ] OAuth uses Authorization Code + PKCE (not Implicit)
179+
- [ ] Authorization checks permissions, not roles
180+
- [ ] Resource-level auth verifies ownership (not just authentication)
181+
- [ ] Multi-tenant queries always scoped by tenant ID
182+
- [ ] Token revocation mechanism exists (logout invalidates server-side)
183+
- [ ] No secrets or tokens in localStorage
184+
185+
**See also:** [Security Checklist](../../references/security-checklist.md) for broader security patterns.

skills/flow-code-caching/SKILL.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
name: flow-code-caching
3+
description: "Use when adding caching layers — HTTP cache, CDN, Redis, in-memory, or application-level. Covers cache invalidation, TTL strategy, and cache-aside patterns."
4+
tier: 2
5+
user-invocable: true
6+
---
7+
<!-- SKILL_TAGS: caching,redis,cdn,performance -->
8+
9+
# Caching Strategies
10+
11+
## Overview
12+
13+
Caching is a trade-off: speed vs. staleness. Every cache needs an invalidation strategy before it's built. The two hardest problems in computer science: cache invalidation, naming things, and off-by-one errors.
14+
15+
## When to Use
16+
17+
- API responses are slow and data changes infrequently
18+
- Same data requested by many users (product catalog, config)
19+
- Expensive computations (aggregations, reports, search results)
20+
- Static assets (images, CSS, JS bundles)
21+
- Rate-limited external APIs (cache responses to reduce calls)
22+
23+
**When NOT to use:**
24+
- Data that must be real-time (financial transactions, live chat)
25+
- User-specific data that's rarely re-requested
26+
- Write-heavy workloads (cache invalidation overhead > benefit)
27+
28+
## Cache Layers (Top to Bottom)
29+
30+
```
31+
Browser Cache ──> CDN ──> Reverse Proxy ──> App Cache ──> Database
32+
(client) (edge) (nginx/Varnish) (Redis) (source)
33+
```
34+
35+
Each layer closer to the user is faster but harder to invalidate.
36+
37+
## HTTP Caching
38+
39+
```typescript
40+
// Static assets: long cache + content hash in filename
41+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
42+
// File: /assets/app.a1b2c3.js (hash changes on content change)
43+
44+
// API responses: short cache + revalidation
45+
res.setHeader('Cache-Control', 'private, max-age=60, stale-while-revalidate=300');
46+
res.setHeader('ETag', computeETag(data));
47+
48+
// Sensitive data: never cache
49+
res.setHeader('Cache-Control', 'no-store');
50+
```
51+
52+
| Directive | Use For |
53+
|-----------|---------|
54+
| `public, max-age=31536000, immutable` | Hashed static assets (JS, CSS, images) |
55+
| `private, max-age=60` | User-specific API data |
56+
| `public, max-age=300, stale-while-revalidate=3600` | Shared API data (product catalog) |
57+
| `no-store` | Auth tokens, PII, financial data |
58+
| `no-cache` | Always revalidate (ETag/Last-Modified) |
59+
60+
## Application Cache (Redis / In-Memory)
61+
62+
### Cache-Aside Pattern
63+
64+
```typescript
65+
async function getUser(userId: string): Promise<User> {
66+
// 1. Check cache
67+
const cached = await redis.get(`user:${userId}`);
68+
if (cached) return JSON.parse(cached);
69+
70+
// 2. Cache miss → fetch from DB
71+
const user = await db.users.findById(userId);
72+
if (!user) throw new NotFoundError();
73+
74+
// 3. Populate cache with TTL
75+
await redis.set(`user:${userId}`, JSON.stringify(user), 'EX', 300);
76+
return user;
77+
}
78+
79+
// Invalidate on write
80+
async function updateUser(userId: string, data: Partial<User>) {
81+
await db.users.update(userId, data);
82+
await redis.del(`user:${userId}`); // Delete, don't update (simpler)
83+
}
84+
```
85+
86+
### Write-Through Pattern
87+
88+
```typescript
89+
// Write to cache AND database together
90+
async function updateProduct(id: string, data: Partial<Product>) {
91+
const product = await db.products.update(id, data);
92+
await redis.set(`product:${id}`, JSON.stringify(product), 'EX', 3600);
93+
return product;
94+
}
95+
```
96+
97+
## Invalidation Strategies
98+
99+
| Strategy | How | Best For |
100+
|----------|-----|----------|
101+
| **TTL (Time-to-Live)** | Cache expires after N seconds | Data where staleness is acceptable |
102+
| **Event-driven** | Invalidate on write/update event | Data that must be fresh after mutation |
103+
| **Version key** | Append version to cache key | Bulk invalidation (clear all product caches) |
104+
| **Purge on deploy** | Clear cache on new deployment | Config, templates, feature flags |
105+
106+
**Golden Rule:** `cache.del()` on write is simpler and safer than `cache.set()` on write. Let the next read repopulate.
107+
108+
## Cache Key Design
109+
110+
```typescript
111+
// Good: predictable, scoped, versioned
112+
`user:${userId}`
113+
`products:list:page=${page}:sort=${sort}`
114+
`config:v${version}:${tenantId}`
115+
116+
// Bad: unpredictable or unbounded
117+
`data_${Date.now()}` // Never hit, always miss
118+
`search:${fullQueryString}` // Unbounded cardinality
119+
```
120+
121+
**Rules:**
122+
- Include all parameters that affect the response
123+
- Use delimiters consistently (`:` for Redis keys)
124+
- Set maximum key count (eviction policy: LRU, LFU)
125+
- Monitor hit rate (< 80% hit rate means cache isn't helping)
126+
127+
## Common Rationalizations
128+
129+
| Rationalization | Reality |
130+
|---|---|
131+
| "Cache everything" | Caching data that changes every second wastes memory and adds invalidation complexity. |
132+
| "TTL handles invalidation" | Stale data for the TTL duration may be unacceptable. Use event-driven for critical data. |
133+
| "We'll figure out invalidation later" | Invalidation IS the design. A cache without an invalidation strategy is a bug factory. |
134+
| "In-memory cache is enough" | In-memory caches don't survive restarts and aren't shared across instances. Use Redis for shared state. |
135+
136+
## Red Flags
137+
138+
- Cache without TTL or invalidation strategy
139+
- Caching user-specific data with `public` Cache-Control
140+
- Cache key doesn't include all varying parameters (serving wrong data)
141+
- No cache eviction policy (memory grows unbounded)
142+
- Caching mutable data without write-through or invalidation
143+
- `no-cache` confused with `no-store` (they're different)
144+
- Missing `Vary` header for content negotiation (language, encoding)
145+
146+
## Verification
147+
148+
- [ ] Every cache entry has TTL or explicit invalidation strategy
149+
- [ ] Cache keys include all parameters that affect the response
150+
- [ ] Write operations invalidate affected cache entries
151+
- [ ] Static assets use content-hashed filenames + immutable cache
152+
- [ ] Sensitive data uses `Cache-Control: no-store`
153+
- [ ] Cache hit rate monitored (target > 80%)
154+
- [ ] Eviction policy configured (LRU/LFU, max memory)
155+
- [ ] Graceful degradation when cache is unavailable (fall through to DB)
156+
157+
**See also:** [Performance Checklist](../../references/performance-checklist.md) for broader optimization patterns.

0 commit comments

Comments
 (0)