start-vibing-stacks 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: observability
3
- version: 1.0.0
4
- description: Structured logging, correlation IDs, OpenTelemetry tracing, error tracking (Sentry), metrics, and PII redaction. Invoke when adding logging, instrumentation, or debugging production issues.
3
+ version: 2.0.0
4
+ description: "Structured logging, correlation IDs, OpenTelemetry tracing (semconv 1.41+, including GenAI conventions for Anthropic / OpenAI / AWS Bedrock / Azure AI / MCP), error tracking (Sentry), metrics, PII redaction, audit log separation. Invoke when adding logging, instrumentation, AI/LLM observability, or debugging production issues."
5
5
  ---
6
6
 
7
7
  # Observability — Logs, Traces, Metrics
@@ -285,6 +285,75 @@ Cardinality rule: never tag with user_id, email, or unbounded values — explode
285
285
 
286
286
  ---
287
287
 
288
+ ## 6.5. AI / LLM Observability — OTel GenAI Semantic Conventions *(2026)*
289
+
290
+ OpenTelemetry GenAI conventions (semconv ≥ 1.37, current ≥ 1.41) standardize how LLM calls are traced. They cover: **model spans**, **agent spans**, **events** (inputs/outputs), and provider-specific conventions for **Anthropic, OpenAI, AWS Bedrock, Azure AI Inference, and MCP (Model Context Protocol)**.
291
+
292
+ > Status: Development (not yet stable). Set `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental` to opt in to current attribute names. **Do not use deprecated `gen_ai.prompt` / `gen_ai.completion` attributes** — they were removed in 1.28 in favor of log-based events.
293
+
294
+ ### Required attributes on every LLM span
295
+
296
+ | Attribute | Example |
297
+ |---|---|
298
+ | `gen_ai.system` | `"anthropic"`, `"openai"`, `"aws.bedrock"`, `"azure.ai.inference"` |
299
+ | `gen_ai.operation.name` | `"chat"`, `"text_completion"`, `"embeddings"` |
300
+ | `gen_ai.request.model` | `"claude-opus-4-5"`, `"gpt-5"`, `"sonnet-4-5"` |
301
+ | `gen_ai.response.model` | The actual model that served the request (may differ on Bedrock/Gateway) |
302
+ | `gen_ai.usage.input_tokens` | Numeric |
303
+ | `gen_ai.usage.output_tokens` | Numeric |
304
+ | `gen_ai.response.finish_reasons` | `["stop"]`, `["length"]`, `["tool_calls"]` |
305
+
306
+ ### Recording inputs/outputs as **events** (not attributes)
307
+
308
+ ```ts
309
+ import { trace, SpanKind } from '@opentelemetry/api';
310
+ const tracer = trace.getTracer('llm');
311
+
312
+ await tracer.startActiveSpan('chat anthropic', { kind: SpanKind.CLIENT }, async (span) => {
313
+ span.setAttributes({
314
+ 'gen_ai.system': 'anthropic',
315
+ 'gen_ai.operation.name': 'chat',
316
+ 'gen_ai.request.model': 'claude-opus-4-5',
317
+ });
318
+
319
+ // Inputs/outputs go as EVENTS, not attributes — keeps spans cheap, allows redaction
320
+ span.addEvent('gen_ai.user.message', { 'gen_ai.message.content': redact(userMsg) });
321
+
322
+ const res = await anthropic.messages.create({ /* ... */ });
323
+
324
+ span.setAttributes({
325
+ 'gen_ai.response.model': res.model,
326
+ 'gen_ai.usage.input_tokens': res.usage.input_tokens,
327
+ 'gen_ai.usage.output_tokens': res.usage.output_tokens,
328
+ 'gen_ai.response.finish_reasons': [res.stop_reason],
329
+ });
330
+ span.addEvent('gen_ai.assistant.message', { 'gen_ai.message.content': redact(res.content) });
331
+ span.end();
332
+ return res;
333
+ });
334
+ ```
335
+
336
+ ### MCP (Model Context Protocol) spans
337
+
338
+ For agentic flows that call MCP servers:
339
+ - `gen_ai.system`: `"mcp"`
340
+ - `gen_ai.tool.name`: tool invoked
341
+ - Span kind: `CLIENT` (your agent → MCP server)
342
+
343
+ ### Cost & token metrics
344
+
345
+ ```ts
346
+ const meter = metrics.getMeter('llm');
347
+ const tokensUsed = meter.createCounter('gen_ai.client.token.usage', { unit: '{token}' });
348
+
349
+ tokensUsed.add(res.usage.input_tokens, { 'gen_ai.system': 'anthropic', 'gen_ai.token.type': 'input', 'gen_ai.request.model': 'claude-opus-4-5' });
350
+ tokensUsed.add(res.usage.output_tokens, { 'gen_ai.system': 'anthropic', 'gen_ai.token.type': 'output', 'gen_ai.request.model': 'claude-opus-4-5' });
351
+ ```
352
+
353
+ **PII rules still apply** — redact user content in events the same way you redact request bodies in HTTP logs.
354
+
355
+ ---
356
+
288
357
  ## 7. Health Checks
289
358
 
290
359
  Two endpoints, distinct semantics:
@@ -331,6 +400,7 @@ Different retention (often longer), different access (security team), different
331
400
  - [ ] Sentry initialized with `beforeSend` scrubber
332
401
  - [ ] Tracing initialized at process start (before app code)
333
402
  - [ ] `/healthz` + `/readyz` endpoints exist
403
+ - [ ] LLM calls emit `gen_ai.*` attributes + token-usage metrics; user content redacted
334
404
 
335
405
  ## FORBIDDEN
336
406
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: openapi-design
3
- version: 1.0.0
4
- description: OpenAPI 3.1 spec design — resource naming, status codes, problem+json (RFC 9457) errors, pagination, versioning, security schemes, idempotency, deprecation. Stack-agnostic. Invoke when designing or documenting any HTTP/REST API that publishes an OpenAPI spec, regardless of framework (Fastify, Hono, FastAPI, Laravel, Express, etc.).
3
+ version: 2.0.0
4
+ description: "OpenAPI 3.1 / 3.2 spec design — resource naming, status codes, problem+json (RFC 9457) errors, pagination, versioning, security schemes, idempotency, deprecation. Covers OpenAPI 3.2 (Sept 2025) deltas: native Server-Sent Events (SSE), JSON Lines streaming, hierarchical tags (parent/kind), QUERY HTTP method, additionalOperations, querystring location, $self document URI, OAuth2 device flow. Fully backward-compatible with 3.1 (no breaking changes). Stack-agnostic. Invoke when designing or documenting any HTTP/REST API that publishes an OpenAPI spec, regardless of framework (Fastify, Hono, FastAPI, Laravel, Express, etc.)."
5
5
  ---
6
6
 
7
- # OpenAPI Design — Universal API Contract
7
+ # OpenAPI Design — Universal API Contract (3.1 + 3.2)
8
8
 
9
9
  **Invoke before adding a new endpoint, before publishing a public API, and during code review of any route handler.**
10
10
 
@@ -12,6 +12,27 @@ description: OpenAPI 3.1 spec design — resource naming, status codes, problem+
12
12
 
13
13
  This skill covers **what the spec should look like**. For **how to generate it** in your stack, see the per-framework skill (`fastify-api`, `hono-api`, `fastapi-patterns`, `laravel-patterns`, etc.).
14
14
 
15
+ ## Version policy (2026)
16
+
17
+ - **OpenAPI 3.2** (Sept 2025): adopt for new specs — fully backward-compatible with 3.1, just bump the `openapi` field. **Zero breaking changes.**
18
+ - **OpenAPI 3.1** (Feb 2021): full JSON Schema 2020-12 — still fine for stable APIs.
19
+ - **OpenAPI 3.0**: legacy. Migrate when convenient.
20
+
21
+ ### What 3.2 adds (cheat sheet)
22
+
23
+ | Feature | Use when |
24
+ |---|---|
25
+ | **Server-Sent Events (SSE)** as a first-class media type | Long-running progress, AI streaming responses, live dashboards |
26
+ | **JSON Lines** (`application/jsonl`) | Streaming row-oriented data (logs, exports) |
27
+ | **Multipart streaming** | Upload progress with metadata |
28
+ | **Hierarchical tags** (`parent`, `kind`, `summary`) | Replaces vendor-extensions `x-tagGroups`, `x-displayName`. Native nav for large APIs. |
29
+ | **`QUERY` HTTP method** | Safe & idempotent reads with a request body (search APIs that exceed URL limits) |
30
+ | **`additionalOperations`** field | Document custom HTTP verbs without spec extensions |
31
+ | **`querystring` parameter location** | Describe an entire query string as a single typed parameter |
32
+ | **`$self`** document URI | Solves multi-document `$ref` resolution |
33
+ | **OAuth2 Device Authorization flow** | CLI / IoT auth |
34
+ | **Discriminator `defaultMapping`** | Cleaner polymorphic schemas |
35
+
15
36
  ---
16
37
 
17
38
  ## 1. Resource Naming
@@ -52,6 +73,93 @@ The action sub-resource pattern (`/orders/{id}/cancel`) is the escape hatch when
52
73
 
53
74
  ---
54
75
 
76
+ ## 2.5. Streaming Responses *(OpenAPI 3.2)*
77
+
78
+ Stop documenting streams as `200 application/json` with hand-rolled extensions. 3.2 lets you describe SSE, JSON Lines, and multipart streams natively.
79
+
80
+ ### Server-Sent Events (SSE)
81
+
82
+ ```yaml
83
+ paths:
84
+ /chat/{id}/stream:
85
+ get:
86
+ summary: Stream assistant tokens as they generate
87
+ responses:
88
+ '200':
89
+ description: SSE stream
90
+ content:
91
+ text/event-stream:
92
+ schema:
93
+ type: object
94
+ properties:
95
+ event: { type: string, enum: [token, tool_call, done] }
96
+ data: { type: object }
97
+ ```
98
+
99
+ ### JSON Lines (newline-delimited JSON)
100
+
101
+ ```yaml
102
+ paths:
103
+ /exports/{id}:
104
+ get:
105
+ responses:
106
+ '200':
107
+ description: One JSON object per line
108
+ content:
109
+ application/jsonl:
110
+ schema:
111
+ $ref: '#/components/schemas/Order'
112
+ ```
113
+
114
+ Use SSE for **client subscribes to live updates** (chat tokens, progress); JSON Lines for **bulk row export** (logs, analytics dumps). Document the terminator (`event: done` for SSE; EOF for JSON Lines) so codegen clients know when to stop reading.
115
+
116
+ ---
117
+
118
+ ## 2.6. Hierarchical Tags *(OpenAPI 3.2)*
119
+
120
+ Replaces the legacy `x-tagGroups` / `x-displayName` extensions. Native nested navigation in tools that support 3.2.
121
+
122
+ ```yaml
123
+ tags:
124
+ - name: billing
125
+ summary: Billing
126
+ kind: nav # 'nav' = navigation group only, no operations directly under it
127
+ - name: invoices
128
+ parent: billing
129
+ summary: Invoices
130
+ - name: payments
131
+ parent: billing
132
+ summary: Payments
133
+ ```
134
+
135
+ For tools still on 3.1, the same effect is achieved with `x-tagGroups` / `x-displayName`. Specs published as 3.2 should drop the extensions.
136
+
137
+ ---
138
+
139
+ ## 2.7. The `QUERY` HTTP method *(OpenAPI 3.2)*
140
+
141
+ For **safe & idempotent** reads whose parameters exceed URL length limits (complex search, large filter sets). Bodies on `GET` are technically legal but proxies eat them; `QUERY` is the standardized escape hatch.
142
+
143
+ ```yaml
144
+ paths:
145
+ /search:
146
+ additionalOperations:
147
+ QUERY:
148
+ summary: Complex search
149
+ requestBody:
150
+ required: true
151
+ content:
152
+ application/json:
153
+ schema:
154
+ $ref: '#/components/schemas/SearchRequest'
155
+ responses:
156
+ '200': { $ref: '#/components/responses/SearchResults' }
157
+ ```
158
+
159
+ `QUERY` is **safe** (no side effects) and **idempotent** (same body → same response) — clients and caches treat it like `GET`.
160
+
161
+ ---
162
+
55
163
  ## 3. Error Envelope — `application/problem+json` (RFC 9457)
56
164
 
57
165
  One shape for every error response in the entire API. Codegen clients can match a single discriminator.
@@ -1,23 +1,85 @@
1
1
  ---
2
2
  name: playwright-automation
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Playwright 1.60+ E2E and browser-automation patterns for 2026. Page Object Model, fixtures (auth + DB cleanup), multi-viewport (375 / 768 / 1280), trace viewer, UI Mode v2 (search + system theme), HAR recording (1.60), screencast API (1.59), Playwright Agents (planner/generator/healer, 1.56+), sharding for CI, accessibility snapshots. Invoke when writing E2E tests or browser automation."
4
5
  ---
5
6
 
6
- # Playwright Automation — E2E Testing
7
+ # Playwright Automation — E2E Testing (1.60+)
7
8
 
8
9
  **ALWAYS invoke when writing E2E tests or browser automation.**
9
10
 
10
- ## Structure
11
+ > Pin Playwright in `package.json` and pin the matching browser binary version. Mismatch between client and browser is the #1 source of "flaky in CI" reports.
12
+
13
+ ## Version policy (2026)
14
+
15
+ - **1.60** (current): HAR recording first-class API (`tracing.startHar()` / `stopHar()`), `locator.drop()`, `test.abort()`, aria-snapshot bounding-box.
16
+ - **1.59**: `page.screencast` (video with action annotations + real-time frames), `browser.bind()`.
17
+ - **1.58**: UI Mode + Trace Viewer search (`Cmd/Ctrl+F`), `system` theme, JSON auto-format, **CLI+SKILLs token-efficient mode** (relevant for AI-assisted authoring).
18
+ - **1.57**: aria snapshot improvements.
19
+ - **1.56**: **Playwright Agents** (planner / generator / healer) — AI-assisted test creation and self-healing locators.
20
+
21
+ ```json
22
+ // package.json
23
+ "devDependencies": {
24
+ "@playwright/test": "1.60.0"
25
+ },
26
+ "scripts": {
27
+ "test:e2e": "playwright test",
28
+ "test:e2e:ui": "playwright test --ui",
29
+ "test:e2e:debug": "PWDEBUG=1 playwright test",
30
+ "test:e2e:trace": "playwright show-trace test-results/trace.zip"
31
+ }
32
+ ```
33
+
34
+ ## Folder structure
11
35
 
12
36
  ```
13
37
  tests/e2e/
14
- ├── fixtures/ # Auth, DB cleanup, custom fixtures
15
- ├── pages/ # Page Object Model
16
- ├── flows/ # User journey tests
17
- ├── api/ # API-only tests
38
+ ├── fixtures/ # Auth, DB cleanup, custom fixtures
39
+ ├── pages/ # Page Object Model
40
+ ├── flows/ # User journey tests
41
+ ├── api/ # API-only tests (request fixture)
42
+ ├── visual/ # Screenshot / aria-snapshot regressions
18
43
  └── playwright.config.ts
19
44
  ```
20
45
 
46
+ ## `playwright.config.ts` — production-ready baseline
47
+
48
+ ```ts
49
+ import { defineConfig, devices } from '@playwright/test';
50
+
51
+ export default defineConfig({
52
+ testDir: './tests/e2e',
53
+ fullyParallel: true,
54
+ forbidOnly: !!process.env['CI'],
55
+ retries: process.env['CI'] ? 2 : 0,
56
+ workers: process.env['CI'] ? 4 : undefined,
57
+ reporter: [
58
+ ['html', { open: 'never' }],
59
+ ['junit', { outputFile: 'test-results/junit.xml' }],
60
+ ...(process.env['CI'] ? [['github']] : []),
61
+ ],
62
+ use: {
63
+ baseURL: process.env['E2E_BASE_URL'] ?? 'http://localhost:3000',
64
+ trace: 'on-first-retry', // saves cost; debug locally with --trace=on
65
+ screenshot: 'only-on-failure',
66
+ video: 'retain-on-failure',
67
+ actionTimeout: 10_000,
68
+ navigationTimeout: 30_000,
69
+ },
70
+ projects: [
71
+ { name: 'chromium-desktop', use: { ...devices['Desktop Chrome'], viewport: { width: 1280, height: 800 } } },
72
+ { name: 'webkit-tablet', use: { ...devices['iPad (gen 7)'] } },
73
+ { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
74
+ ],
75
+ webServer: process.env['CI'] ? undefined : {
76
+ command: 'npm run dev',
77
+ url: 'http://localhost:3000',
78
+ reuseExistingServer: true,
79
+ },
80
+ });
81
+ ```
82
+
21
83
  ## Page Object Model
22
84
 
23
85
  ```typescript
@@ -42,10 +104,15 @@ export abstract class BasePage {
42
104
  }
43
105
  ```
44
106
 
45
- ## Auth Fixture
107
+ ## Fixtures — cleanup is mandatory
46
108
 
47
- ```typescript
48
- export function generateTestUser(): TestUser {
109
+ ```ts
110
+ // tests/e2e/fixtures/auth.ts
111
+ import { test as base } from '@playwright/test';
112
+
113
+ type TestUser = { name: string; email: string; password: string };
114
+
115
+ function generateTestUser(): TestUser {
49
116
  const ts = Date.now();
50
117
  const rand = Math.random().toString(36).substring(7);
51
118
  return {
@@ -54,9 +121,42 @@ export function generateTestUser(): TestUser {
54
121
  password: 'TestPassword123!',
55
122
  };
56
123
  }
124
+
125
+ export const test = base.extend<{ user: TestUser }>({
126
+ user: async ({ request }, use) => {
127
+ const user = generateTestUser();
128
+ await request.post('/api/test/users', { data: user });
129
+ await use(user);
130
+ await request.delete(`/api/test/users/${encodeURIComponent(user.email)}`); // cleanup
131
+ },
132
+ });
133
+ ```
134
+
135
+ The cleanup runs **even if the test fails** — fixtures unwind with try/finally semantics. Track every created entity in a fixture; never trust a test to clean up after itself.
136
+
137
+ ## Selector priority (use locators, not CSS)
138
+
139
+ 1. `getByRole('button', { name: 'Save' })` — accessibility-aware
140
+ 2. `getByLabel('Email')` — form fields
141
+ 3. `getByPlaceholder('Search...')` — when label is implied
142
+ 4. `getByText('Welcome back')` — visible text content
143
+ 5. `getByTestId('submit-button')` — escape hatch when above don't fit
144
+
145
+ CSS / XPath only as last resort. Prefer roles — they double as accessibility checks.
146
+
147
+ ```html
148
+ <!-- Required data-testid on app-rendered elements -->
149
+ <input data-testid="email-input" />
150
+ <input data-testid="password-input" />
151
+ <button data-testid="submit-button" />
152
+ <div data-testid="error-message" />
153
+ <div data-testid="success-message" />
154
+ <div data-testid="loading-spinner" />
155
+ <nav data-testid="sidebar" />
156
+ <button data-testid="hamburger-menu" />
57
157
  ```
58
158
 
59
- ## Multi-Viewport Testing
159
+ ## Multi-viewport testing (375 / 768 / 1280)
60
160
 
61
161
  ```typescript
62
162
  const viewports = [
@@ -66,30 +166,76 @@ const viewports = [
66
166
  ] as const;
67
167
 
68
168
  for (const viewport of viewports) {
69
- test.describe(`Responsive - ${viewport.name}`, () => {
169
+ test.describe(`Responsive ${viewport.name}`, () => {
70
170
  test.use({ viewport: { width: viewport.width, height: viewport.height } });
71
171
  test('navigation adapts', async ({ page }) => { /* ... */ });
72
172
  });
73
173
  }
74
174
  ```
75
175
 
76
- ## Required data-testid
176
+ ## Aria snapshots — accessibility regression *(1.50+, improved in 1.60)*
77
177
 
78
- ```html
79
- <input data-testid="email-input" />
80
- <input data-testid="password-input" />
81
- <button data-testid="submit-button" />
82
- <div data-testid="error-message" />
83
- <div data-testid="success-message" />
84
- <div data-testid="loading-spinner" />
85
- <nav data-testid="sidebar" />
86
- <button data-testid="hamburger-menu" />
178
+ ```ts
179
+ // First run records snapshot:
180
+ await expect(page.locator('main')).toMatchAriaSnapshot();
181
+
182
+ // Subsequent runs assert structure stays the same — catches accessibility regressions
183
+ // Update with: npx playwright test --update-snapshots
87
184
  ```
88
185
 
186
+ ## HAR recording *(1.60)*
187
+
188
+ ```ts
189
+ // Record outbound network for replay or debugging
190
+ await context.tracing.startHar({ path: 'test.har' });
191
+ await page.goto('/checkout');
192
+ await context.tracing.stopHar();
193
+
194
+ // Replay against a recorded HAR (offline / deterministic tests):
195
+ await context.routeFromHAR('checkout.har', { update: false });
196
+ ```
197
+
198
+ ## Trace Viewer + UI Mode
199
+
200
+ ```bash
201
+ npx playwright test --trace on # full trace
202
+ npx playwright show-trace test-results/.../trace.zip
203
+ npx playwright test --ui # UI Mode v2 — Cmd/Ctrl+F search, system theme, single-worker
204
+ ```
205
+
206
+ ## Sharding for CI
207
+
208
+ ```yaml
209
+ # Split a slow E2E suite across N runners
210
+ strategy:
211
+ matrix:
212
+ shardIndex: [1, 2, 3, 4]
213
+ shardTotal: [4]
214
+ steps:
215
+ - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
216
+ ```
217
+
218
+ Then merge HTML reports with `npx playwright merge-reports`.
219
+
220
+ ## Playwright Agents *(1.56+, AI-assisted)*
221
+
222
+ The `planner`, `generator`, and `healer` agents support AI-driven test creation and self-healing locators. Useful when bootstrapping coverage on a legacy app, but **always commit human-reviewed tests** — never let an agent rewrite locked tests in CI without approval.
223
+
89
224
  ## FORBIDDEN
90
225
 
91
- 1. **Hardcoded test data** — generate unique data with timestamps
92
- 2. **`.skip()` or `.only()`** — never in committed code
93
- 3. **No cleanup** always track + clean created data
94
- 4. **Mocked auth** use real authentication flows
95
- 5. **Single viewport** test mobile + tablet + desktop
226
+ | Pattern | Why |
227
+ |---|---|
228
+ | Hardcoded test data (`test@test.com`) | Breaks parallel runs, leaves orphan rows |
229
+ | `.skip()` or `.only()` in committed code | Silently disables coverage |
230
+ | No fixture cleanup | Test DB fills with garbage |
231
+ | Mocked auth | Integration value lost; the real flow breaks unnoticed |
232
+ | Single viewport | Mobile breaks shipped to prod |
233
+ | `page.waitForTimeout(N)` | Flaky; use `waitFor`, `toBeVisible`, etc. |
234
+ | CSS selectors when a role / label exists | Loses accessibility coverage |
235
+ | `--no-deps` in CI install | Browser binaries silently outdated |
236
+
237
+ ## See Also
238
+
239
+ - `test-coverage` — what to test, fixtures rules
240
+ - `accessibility-wcag22` — pair with `getByRole` + aria snapshots
241
+ - `ci-pipelines` — Playwright on CI (cache `~/.cache/ms-playwright`)
@@ -1,16 +1,97 @@
1
1
  ---
2
2
  name: postgres-patterns
3
- version: 1.0.0
4
- description: PostgreSQL design, security, and tuning at the SQL/operations layer — role separation, Row-Level Security (RLS), connection security, JSONB, partitioning, indexing strategy (BTREE/GIN/BRIN/partial/expression), EXPLAIN ANALYZE, isolation levels, locking, advisory locks, connection pool sizing. Stack-agnostic. Invoke when designing Postgres schemas, hardening database access, or debugging Postgres query performance. Complements per-stack ORM skills (Drizzle, SQLAlchemy, Eloquent, Mongoose) and the database-migrations skill.
3
+ version: 2.0.0
4
+ description: "PostgreSQL design, security, and tuning at the SQL/operations layer — role separation, Row-Level Security (RLS), connection security, JSONB, partitioning, indexing strategy (BTREE/GIN/BRIN/skip-scan/partial/expression), EXPLAIN ANALYZE, isolation levels, locking, advisory locks, connection pool sizing, plus PG 18 (Sept 2025) features: uuidv7(), virtual generated columns (now default), temporal constraints, asynchronous I/O, OAuth 2.0 auth. Stack-agnostic. Invoke when designing Postgres schemas, hardening database access, or debugging Postgres query performance. Complements per-stack ORM skills (Drizzle, SQLAlchemy, Eloquent, Mongoose) and the database-migrations skill."
5
5
  ---
6
6
 
7
- # PostgreSQL — Design, Security, and Tuning
7
+ # PostgreSQL — Design, Security, and Tuning (PG 17 / 18-aware)
8
8
 
9
9
  **Invoke when designing a Postgres schema, granting database access, picking an index, debugging a slow query, or hardening prod connection settings.**
10
10
 
11
11
  > Postgres is the same on every stack. The application code differs. This skill covers the database side — what to ask of it, how to lock it down, and how to read its performance signals. For schema **deployment** (concurrent indexes, lock timeouts, backfills, parallel change), see `database-migrations`.
12
12
 
13
- This skill assumes Postgres ≥ 15 (14 still supported but missing some features called out below). PG 16/17 add more flagged where relevant.
13
+ This skill assumes Postgres ≥ 16. **PG 18 (released Sept 25, 2025) is the recommended target for new projects** — see §0 for headline features. PG 17 (Sept 2024) is the safe LTS-style choice for production today; PG 14/15 are still supported but missing features called out below.
14
+
15
+ ---
16
+
17
+ ## 0. PG 18 — What changed (Sept 25, 2025)
18
+
19
+ The most consequential release for application code in years. Adopt for greenfield; plan migration for prod.
20
+
21
+ | Feature | Why it matters |
22
+ |---|---|
23
+ | **`uuidv7()`** built-in function | Time-ordered UUIDs → drastically better B-tree locality than `gen_random_uuid()` (uuidv4). Use as PK default. |
24
+ | **Virtual generated columns** (now the **default** for `GENERATED ... AS (...)` without `STORED`) | Computed at read time, no write amplification. PG 17 only had `STORED`. |
25
+ | **Temporal constraints** on PK / UNIQUE / FK (`PERIOD`) | Native bitemporal modelling — no more manual `tstzrange` + EXCLUDE workarounds for "valid history" tables. |
26
+ | **Asynchronous I/O (AIO) subsystem** | Up to **3×** sequential-scan / bitmap-heap / vacuum perf. Tunable via `io_method` GUC. |
27
+ | **Skip-scan on multicolumn B-tree indexes** | Index `(a, b)` is now usable to filter by `b` alone (with `a`-cardinality penalty). Reduces "wrong-prefix" index proliferation. |
28
+ | **OAuth 2.0 authentication** | Native SSO at the wire protocol — no PAM/LDAP gymnastics. |
29
+ | **`pg_upgrade` keeps optimizer stats** | Post-upgrade slow-down on first day eliminated. |
30
+ | **OLD / NEW in `RETURNING`** | `UPDATE ... RETURNING OLD.balance, NEW.balance` — audit trails without triggers. |
31
+
32
+ ### `uuidv7()` as PK default *(PG 18 idiom)*
33
+
34
+ ```sql
35
+ -- PG 18+
36
+ CREATE TABLE orders (
37
+ id uuid PRIMARY KEY DEFAULT uuidv7(),
38
+ ...
39
+ );
40
+
41
+ -- PG ≤ 17 — emulate via extension
42
+ CREATE EXTENSION IF NOT EXISTS pg_uuidv7; -- third-party
43
+ ```
44
+
45
+ UUIDv7 layout: 48-bit unix-ms timestamp + random tail. Inserts hit the *right* edge of the B-tree → no random-IO write amplification (the well-known v4 problem).
46
+
47
+ ### Virtual generated columns
48
+
49
+ ```sql
50
+ -- PG 18 — default is VIRTUAL (computed at read)
51
+ CREATE TABLE invoices (
52
+ amount_cents int NOT NULL,
53
+ tax_cents int GENERATED ALWAYS AS (amount_cents * 0.1) NOT STORED, -- still works; explicit NOT STORED
54
+ total_cents int GENERATED ALWAYS AS (amount_cents + (amount_cents * 0.1)) -- DEFAULT virtual in PG 18
55
+ );
56
+ ```
57
+
58
+ Use VIRTUAL for cheap derivations; STORED when the value is read >> written or indexed.
59
+
60
+ ### Temporal constraints
61
+
62
+ ```sql
63
+ -- PG 18 — native bitemporal PK
64
+ CREATE TABLE employee_salary (
65
+ employee_id int NOT NULL,
66
+ salary numeric NOT NULL,
67
+ valid tstzrange NOT NULL,
68
+ PRIMARY KEY (employee_id, valid WITHOUT OVERLAPS)
69
+ );
70
+ -- INSERTs that overlap on `valid` for the same employee are rejected — no EXCLUDE-with-gist hack.
71
+ ```
72
+
73
+ ### Tuning AIO
74
+
75
+ ```
76
+ # postgresql.conf — PG 18
77
+ io_method = 'io_uring' # Linux ≥ 5.x recommended; 'worker' fallback on others
78
+ io_workers = 3 # tune to NVMe queue depth
79
+ ```
80
+
81
+ ---
82
+
83
+ ## PG 17 — Still excellent (Sept 2024)
84
+
85
+ If you're not yet on 18, prioritize these PG 17 features:
86
+
87
+ - **VACUUM** memory cut up to 20× → much shorter maintenance windows.
88
+ - **WAL throughput** up to 2× under heavy write concurrency.
89
+ - **`pg_createsubscriber`** — promote a physical standby to a logical subscriber in minutes (cuts initial-sync time).
90
+ - **`JSON_TABLE()`** — SQL-standard way to flatten JSONB into rows; avoid hand-written `jsonb_to_recordset` plumbing.
91
+ - **`COPY ... ON_ERROR ignore`** — bulk loads tolerate single bad rows without aborting.
92
+ - **BRIN parallel build** — finally usable for big append-only tables.
93
+
94
+ ---
14
95
 
15
96
  ---
16
97