start-vibing-stacks 2.6.0 → 2.7.3
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.
- package/README.md +83 -135
- package/dist/index.js +16 -2
- package/dist/migrate.d.ts +27 -0
- package/dist/migrate.js +217 -0
- package/dist/setup.js +10 -0
- package/package.json +1 -1
- package/stacks/_shared/agents/claude-md-compactor.md +1 -0
- package/stacks/_shared/agents/commit-manager.md +1 -0
- package/stacks/_shared/agents/documenter.md +1 -0
- package/stacks/_shared/agents/domain-updater.md +1 -0
- package/stacks/_shared/agents/research-web.md +1 -0
- package/stacks/_shared/agents/security-auditor.md +168 -0
- package/stacks/_shared/agents/tester.md +1 -0
- package/stacks/_shared/hooks/final-check.ts +205 -0
- package/stacks/_shared/hooks/stop-validator.ts +77 -1
- package/stacks/_shared/skills/accessibility-wcag22/SKILL.md +284 -0
- package/stacks/_shared/skills/ci-pipelines/SKILL.md +166 -0
- package/stacks/_shared/skills/codebase-knowledge/SKILL.md +5 -0
- package/stacks/_shared/skills/database-migrations/SKILL.md +256 -0
- package/stacks/_shared/skills/debugging-patterns/SKILL.md +5 -0
- package/stacks/_shared/skills/docker-patterns/SKILL.md +5 -0
- package/stacks/_shared/skills/docs-tracker/SKILL.md +5 -0
- package/stacks/_shared/skills/error-handling/SKILL.md +335 -0
- package/stacks/_shared/skills/final-check/SKILL.md +74 -37
- package/stacks/_shared/skills/git-workflow/SKILL.md +5 -0
- package/stacks/_shared/skills/hook-development/SKILL.md +5 -0
- package/stacks/_shared/skills/observability/SKILL.md +351 -0
- package/stacks/_shared/skills/performance-patterns/SKILL.md +5 -0
- package/stacks/_shared/skills/playwright-automation/SKILL.md +5 -0
- package/stacks/_shared/skills/quality-gate/SKILL.md +5 -0
- package/stacks/_shared/skills/research-cache/SKILL.md +5 -0
- package/stacks/_shared/skills/secrets-management/SKILL.md +245 -0
- package/stacks/_shared/skills/security-baseline/SKILL.md +202 -0
- package/stacks/_shared/skills/test-coverage/SKILL.md +5 -0
- package/stacks/_shared/skills/ui-ux-audit/SKILL.md +5 -0
- package/stacks/frontend/react/skills/preline-ui/SKILL.md +5 -0
- package/stacks/frontend/react/skills/react-patterns/SKILL.md +5 -0
- package/stacks/frontend/react/skills/react-standards/SKILL.md +5 -0
- package/stacks/frontend/react/skills/react-ui-patterns/SKILL.md +5 -0
- package/stacks/frontend/react/skills/shadcn-ui/SKILL.md +5 -0
- package/stacks/frontend/react/skills/tailwind-patterns/SKILL.md +5 -0
- package/stacks/frontend/react/skills/zod-validation/SKILL.md +5 -0
- package/stacks/frontend/react-inertia/skills/inertia-react/SKILL.md +5 -0
- package/stacks/frontend/react-inertia/skills/react-standards/SKILL.md +5 -0
- package/stacks/nodejs/skills/api-security-node/SKILL.md +275 -0
- package/stacks/nodejs/skills/bun-runtime/SKILL.md +5 -0
- package/stacks/nodejs/skills/mongoose-patterns/SKILL.md +5 -0
- package/stacks/nodejs/skills/nextjs-app-router/SKILL.md +5 -0
- package/stacks/nodejs/skills/trpc-api/SKILL.md +5 -0
- package/stacks/nodejs/skills/typescript-strict/SKILL.md +5 -0
- package/stacks/nodejs/stack.json +2 -1
- package/stacks/nodejs/workflows/ci.yml +90 -0
- package/stacks/nodejs/workflows/security.yml +45 -0
- package/stacks/php/skills/api-design/SKILL.md +5 -0
- package/stacks/php/skills/api-security/SKILL.md +5 -0
- package/stacks/php/skills/composer-workflow/SKILL.md +5 -0
- package/stacks/php/skills/external-api-patterns/SKILL.md +5 -0
- package/stacks/php/skills/inertia-react/SKILL.md +5 -0
- package/stacks/php/skills/laravel-inertia-i18n/SKILL.md +5 -0
- package/stacks/php/skills/laravel-octane/SKILL.md +5 -0
- package/stacks/php/skills/laravel-patterns/SKILL.md +5 -0
- package/stacks/php/skills/mariadb-octane/SKILL.md +5 -0
- package/stacks/php/skills/php-patterns/SKILL.md +5 -0
- package/stacks/php/skills/phpstan-analysis/SKILL.md +5 -0
- package/stacks/php/skills/phpunit-testing/SKILL.md +5 -0
- package/stacks/php/skills/security-scan-php/SKILL.md +5 -0
- package/stacks/php/workflows/ci.yml +106 -0
- package/stacks/php/workflows/security.yml +36 -0
- package/stacks/python/skills/api-security-python/SKILL.md +312 -0
- package/stacks/python/skills/async-patterns/SKILL.md +5 -0
- package/stacks/python/skills/django-patterns/SKILL.md +5 -0
- package/stacks/python/skills/fastapi-patterns/SKILL.md +5 -0
- package/stacks/python/skills/pydantic-validation/SKILL.md +5 -0
- package/stacks/python/skills/pytest-testing/SKILL.md +5 -0
- package/stacks/python/skills/python-patterns/SKILL.md +5 -0
- package/stacks/python/skills/python-performance/SKILL.md +5 -0
- package/stacks/python/skills/scripting-automation/SKILL.md +5 -0
- package/stacks/python/stack.json +2 -1
- package/stacks/python/workflows/ci.yml +76 -0
- package/stacks/python/workflows/security.yml +56 -0
|
@@ -1,56 +1,93 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
name: final-check
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
description: Final validator with VETO power. Now executable — runs `npx tsx .claude/hooks/final-check.ts` to scan for debug statements, secrets, .skip, any, RCE risks, SQL concat. Blocks completion on CRITICAL/HIGH findings.
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# Final Check — Executable Validator
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
**ALWAYS run BEFORE completing ANY task. HAS VETO POWER.**
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
> v2.0+ promotes this from a manual checklist to an executable script. The script never trusts memory.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
## How to Run
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npx tsx .claude/hooks/final-check.ts
|
|
17
|
+
```
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- [ ] No TODO/FIXME left unresolved
|
|
17
|
-
- [ ] No hardcoded secrets or credentials
|
|
19
|
+
Exit codes:
|
|
20
|
+
- `0` — clean (or only LOW/MEDIUM warnings)
|
|
21
|
+
- `1` — at least one CRITICAL or HIGH finding → **task blocked**
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
- [ ] CLAUDE.md "Last Change" updated
|
|
21
|
-
- [ ] Affected domain files updated
|
|
22
|
-
- [ ] New patterns documented
|
|
23
|
+
## What It Scans
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
- [ ] New code has tests
|
|
26
|
-
- [ ] Existing tests still pass
|
|
27
|
-
- [ ] Edge cases covered
|
|
25
|
+
For every file in the diff (staged + unstaged + untracked) matching the active stack's source extensions, every line is checked against:
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
| Severity | Rule | Why blocking |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| CRITICAL | Hardcoded secret pattern (api key / token / password) | Leak risk |
|
|
30
|
+
| CRITICAL | Public env var with `*SECRET\|*TOKEN\|*PRIVATE\|*PASSWORD\|*CREDENTIAL` | Bundles into client |
|
|
31
|
+
| HIGH | Arbitrary code-execution function (`eval`, Python `exec`) | RCE |
|
|
32
|
+
| HIGH | `subprocess` with `shell=True` (Python) | Command injection |
|
|
33
|
+
| HIGH | SQL string concatenation / f-string SQL | SQL injection |
|
|
34
|
+
| HIGH | `it.only` / `test.only` / `describe.only` / `it.skip` in committed test | False green CI |
|
|
35
|
+
| MEDIUM | `console.log/debug/trace` outside tests | Debug leftover, log noise |
|
|
36
|
+
| MEDIUM | PHP `var_dump/dd/dump/print_r` | Debug leftover |
|
|
37
|
+
| MEDIUM | TypeScript `any` (without `/* ok */` escape) | Erodes strict mode |
|
|
38
|
+
| MEDIUM | `@ts-ignore` (use `@ts-expect-error` with comment) | Silently outdated |
|
|
39
|
+
| MEDIUM | `@pytest.mark.skip` | Hidden test gaps |
|
|
40
|
+
| LOW | `print()` outside tests (Python) | Use logger |
|
|
41
|
+
| LOW | TODO / FIXME / XXX / HACK | Track or remove |
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
- [ ] Codebase knowledge checked before implementing
|
|
37
|
-
- [ ] Research cache checked before web search
|
|
38
|
-
- [ ] Security patterns applied
|
|
43
|
+
Lines containing recognised placeholders (`<your...>`, `YOUR_X`, `placeholder`, `example.com`, `sk_test_`) are skipped to avoid false positives in `.env.example` and docs.
|
|
39
44
|
|
|
40
|
-
##
|
|
45
|
+
## Workflow
|
|
41
46
|
|
|
42
47
|
```
|
|
43
|
-
|
|
48
|
+
1. Implementation done
|
|
49
|
+
2. Run npx tsx .claude/hooks/final-check.ts
|
|
50
|
+
3. If exit 1 → fix, re-run
|
|
51
|
+
4. If exit 0 → proceed to quality-gate → commit
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Suppressing False Positives
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
1. [Fix needed]
|
|
47
|
-
2. [Fix needed]
|
|
56
|
+
When a finding is intentional (e.g. an `any` in well-justified glue code):
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
```ts
|
|
59
|
+
const result = (json as any) /* ok: external lib without types */;
|
|
50
60
|
```
|
|
51
61
|
|
|
62
|
+
Only `/* ok */` immediately after `: any` is recognised. For other rules, refactor — there is no general-purpose suppression by design.
|
|
63
|
+
|
|
64
|
+
## Hook Wiring (Optional)
|
|
65
|
+
|
|
66
|
+
To run automatically on Stop, add to `.claude/settings.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"hooks": {
|
|
71
|
+
"Stop": [{
|
|
72
|
+
"matcher": "*",
|
|
73
|
+
"hooks": [{ "type": "command", "command": "npx tsx ${CLAUDE_PROJECT_DIR}/.claude/hooks/final-check.ts" }]
|
|
74
|
+
}]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The default Stop is `stop-validator.ts` (git/branch/CLAUDE.md/secret scan). `final-check.ts` is complementary — runs before it as a code-quality gate.
|
|
80
|
+
|
|
52
81
|
## Rules
|
|
53
82
|
|
|
54
|
-
1. **VETO POWER** —
|
|
55
|
-
2. **
|
|
56
|
-
3. **RE-
|
|
83
|
+
1. **VETO POWER** — exit 1 blocks task completion
|
|
84
|
+
2. **NEVER skip** — `--no-verify` style bypasses are forbidden
|
|
85
|
+
3. **RE-RUN after fixes** — don't trust memory
|
|
86
|
+
4. **One finding at a time** — fix CRITICAL first, then HIGH
|
|
87
|
+
5. **Refactor over suppress** — `/* ok */` is for extraordinary cases
|
|
88
|
+
|
|
89
|
+
## See Also
|
|
90
|
+
|
|
91
|
+
- `quality-gate` — typecheck/lint/test/build (different scope)
|
|
92
|
+
- `security-baseline` — what the secret/RCE rules enforce
|
|
93
|
+
- `stop-validator.ts` — git/branch/docs gate (sibling, complementary)
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
---
|
|
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.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Observability — Logs, Traces, Metrics
|
|
8
|
+
|
|
9
|
+
**ALWAYS invoke when adding logging, instrumentation, error tracking, or analyzing production issues.**
|
|
10
|
+
|
|
11
|
+
> Three pillars: **Logs** (what happened) + **Traces** (where time was spent) + **Metrics** (how much / how often).
|
|
12
|
+
> One signal: **correlation/trace IDs** that thread through all three.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Structured Logging — Mandatory
|
|
17
|
+
|
|
18
|
+
Logs are JSON, not text. Free-form strings can't be queried, aggregated, or alerted on.
|
|
19
|
+
|
|
20
|
+
### Required fields per log line
|
|
21
|
+
|
|
22
|
+
| Field | Source |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `timestamp` | ISO-8601, UTC |
|
|
25
|
+
| `level` | `trace` / `debug` / `info` / `warn` / `error` / `fatal` |
|
|
26
|
+
| `msg` | Short human description |
|
|
27
|
+
| `service` | Service name |
|
|
28
|
+
| `env` | `production` / `staging` / `development` |
|
|
29
|
+
| `trace_id` | OpenTelemetry trace id (W3C `traceparent`) |
|
|
30
|
+
| `span_id` | Current span |
|
|
31
|
+
| `request_id` | Inbound HTTP request id (mirror to `x-request-id` header) |
|
|
32
|
+
| `user_id` | Authenticated user (hash if PII concerns) — **never** email/full name |
|
|
33
|
+
|
|
34
|
+
### Node.js — pino
|
|
35
|
+
```ts
|
|
36
|
+
// lib/logger.ts
|
|
37
|
+
import pino from 'pino';
|
|
38
|
+
import { randomUUID } from 'crypto';
|
|
39
|
+
|
|
40
|
+
export const logger = pino({
|
|
41
|
+
level: process.env['LOG_LEVEL'] ?? 'info',
|
|
42
|
+
base: {
|
|
43
|
+
service: 'api',
|
|
44
|
+
env: process.env['NODE_ENV'],
|
|
45
|
+
pid: process.pid,
|
|
46
|
+
},
|
|
47
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
48
|
+
redact: {
|
|
49
|
+
paths: [
|
|
50
|
+
'req.headers.authorization',
|
|
51
|
+
'req.headers.cookie',
|
|
52
|
+
'req.body.password',
|
|
53
|
+
'req.body.token',
|
|
54
|
+
'*.password',
|
|
55
|
+
'*.creditCard',
|
|
56
|
+
'*.ssn',
|
|
57
|
+
],
|
|
58
|
+
censor: '[REDACTED]',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// HTTP middleware: attach request_id + child logger to req
|
|
63
|
+
export function requestLogger(req, res, next) {
|
|
64
|
+
const requestId = req.headers['x-request-id'] ?? randomUUID();
|
|
65
|
+
res.setHeader('x-request-id', requestId);
|
|
66
|
+
req.log = logger.child({ request_id: requestId, method: req.method, path: req.path });
|
|
67
|
+
req.log.info({ event: 'request.start' });
|
|
68
|
+
res.on('finish', () => {
|
|
69
|
+
req.log.info({ event: 'request.end', status: res.statusCode, duration_ms: Date.now() - req.startTime });
|
|
70
|
+
});
|
|
71
|
+
next();
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Python — structlog
|
|
76
|
+
```python
|
|
77
|
+
import structlog, logging, sys, uuid
|
|
78
|
+
|
|
79
|
+
structlog.configure(
|
|
80
|
+
processors=[
|
|
81
|
+
structlog.contextvars.merge_contextvars,
|
|
82
|
+
structlog.processors.add_log_level,
|
|
83
|
+
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
|
84
|
+
structlog.processors.StackInfoRenderer(),
|
|
85
|
+
structlog.processors.format_exc_info,
|
|
86
|
+
structlog.processors.JSONRenderer(),
|
|
87
|
+
],
|
|
88
|
+
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
|
|
89
|
+
logger_factory=structlog.PrintLoggerFactory(file=sys.stdout),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
log = structlog.get_logger()
|
|
93
|
+
|
|
94
|
+
# FastAPI middleware
|
|
95
|
+
@app.middleware("http")
|
|
96
|
+
async def request_logger(request, call_next):
|
|
97
|
+
request_id = request.headers.get("x-request-id", str(uuid.uuid4()))
|
|
98
|
+
structlog.contextvars.bind_contextvars(
|
|
99
|
+
request_id=request_id, method=request.method, path=request.url.path
|
|
100
|
+
)
|
|
101
|
+
log.info("request.start")
|
|
102
|
+
response = await call_next(request)
|
|
103
|
+
response.headers["x-request-id"] = request_id
|
|
104
|
+
log.info("request.end", status=response.status_code)
|
|
105
|
+
structlog.contextvars.clear_contextvars()
|
|
106
|
+
return response
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### PHP — Monolog
|
|
110
|
+
```php
|
|
111
|
+
// config/logging.php
|
|
112
|
+
'channels' => [
|
|
113
|
+
'json' => [
|
|
114
|
+
'driver' => 'monolog',
|
|
115
|
+
'handler' => Monolog\Handler\StreamHandler::class,
|
|
116
|
+
'with' => ['stream' => 'php://stdout'],
|
|
117
|
+
'formatter' => Monolog\Formatter\JsonFormatter::class,
|
|
118
|
+
'processors' => [
|
|
119
|
+
Monolog\Processor\WebProcessor::class,
|
|
120
|
+
Monolog\Processor\UidProcessor::class,
|
|
121
|
+
],
|
|
122
|
+
],
|
|
123
|
+
],
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 2. Log Levels — Use Them Right
|
|
129
|
+
|
|
130
|
+
| Level | When |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `trace` | Verbose diagnostics, off in prod |
|
|
133
|
+
| `debug` | Development helper, off in prod by default |
|
|
134
|
+
| `info` | Business events: signup, payment, login, job completed |
|
|
135
|
+
| `warn` | Recoverable anomaly: retry, deprecated path, fallback used |
|
|
136
|
+
| `error` | Operation failed for one user/request, system continues |
|
|
137
|
+
| `fatal` | Cannot continue, process exits |
|
|
138
|
+
|
|
139
|
+
Default prod level: `info`. Errors should always page or alert. `debug` flooding logs is a cost issue.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 3. PII Redaction — Mandatory
|
|
144
|
+
|
|
145
|
+
Never log:
|
|
146
|
+
- Passwords, hashes, tokens, cookies, `Authorization` headers
|
|
147
|
+
- Full credit card / IBAN / SSN / passport
|
|
148
|
+
- Plaintext email if your jurisdiction (GDPR/LGPD) treats it as PII without legitimate basis
|
|
149
|
+
- Full request body or full response body without filtering
|
|
150
|
+
|
|
151
|
+
Redact at the logger level (so it can't be bypassed by a forgetful caller):
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// pino redact paths (above)
|
|
155
|
+
// or wrap manually:
|
|
156
|
+
function safeBody(body: any) {
|
|
157
|
+
const out = { ...body };
|
|
158
|
+
for (const k of ['password', 'token', 'cookie', 'authorization']) {
|
|
159
|
+
if (k in out) out[k] = '[REDACTED]';
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For email: log a hash of the email or the first 2 chars + domain (`jo***@example.com`). Document the policy.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 4. Distributed Tracing — OpenTelemetry
|
|
170
|
+
|
|
171
|
+
Tracing answers "where did the request spend time" across services.
|
|
172
|
+
|
|
173
|
+
### Node.js (auto-instrumentation)
|
|
174
|
+
```ts
|
|
175
|
+
// instrumentation.ts — load BEFORE any other import
|
|
176
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
177
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
178
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
179
|
+
import { Resource } from '@opentelemetry/resources';
|
|
180
|
+
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
181
|
+
|
|
182
|
+
new NodeSDK({
|
|
183
|
+
resource: new Resource({ [ATTR_SERVICE_NAME]: 'api' }),
|
|
184
|
+
traceExporter: new OTLPTraceExporter({ url: process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] }),
|
|
185
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
186
|
+
}).start();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Run: `node --import ./instrumentation.js dist/index.js`
|
|
190
|
+
|
|
191
|
+
### Python (FastAPI)
|
|
192
|
+
```python
|
|
193
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
194
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
195
|
+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
196
|
+
|
|
197
|
+
FastAPIInstrumentor.instrument_app(app)
|
|
198
|
+
SQLAlchemyInstrumentor().instrument(engine=engine)
|
|
199
|
+
HTTPXClientInstrumentor().instrument()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Manual span — when you want to time business logic
|
|
203
|
+
```ts
|
|
204
|
+
import { trace } from '@opentelemetry/api';
|
|
205
|
+
const tracer = trace.getTracer('billing');
|
|
206
|
+
|
|
207
|
+
await tracer.startActiveSpan('charge_customer', async (span) => {
|
|
208
|
+
try {
|
|
209
|
+
span.setAttribute('customer.id', customerId);
|
|
210
|
+
const charge = await stripe.charges.create({ amount, customer: customerId });
|
|
211
|
+
span.setAttribute('charge.id', charge.id);
|
|
212
|
+
return charge;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
span.recordException(err);
|
|
215
|
+
span.setStatus({ code: 2 /* ERROR */ });
|
|
216
|
+
throw err;
|
|
217
|
+
} finally {
|
|
218
|
+
span.end();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 5. Error Tracking — Sentry
|
|
226
|
+
|
|
227
|
+
Pair with structured logging. Sentry is for **alertable** errors with full stack + breadcrumbs; logs are for **everything**.
|
|
228
|
+
|
|
229
|
+
### Node.js
|
|
230
|
+
```ts
|
|
231
|
+
import * as Sentry from '@sentry/node';
|
|
232
|
+
|
|
233
|
+
Sentry.init({
|
|
234
|
+
dsn: process.env['SENTRY_DSN'],
|
|
235
|
+
environment: process.env['NODE_ENV'],
|
|
236
|
+
tracesSampleRate: 0.1,
|
|
237
|
+
profilesSampleRate: 0.1,
|
|
238
|
+
beforeSend(event) {
|
|
239
|
+
// Defense in depth — strip if logger redaction missed it
|
|
240
|
+
if (event.request?.cookies) delete event.request.cookies;
|
|
241
|
+
if (event.request?.headers?.['authorization']) {
|
|
242
|
+
event.request.headers['authorization'] = '[REDACTED]';
|
|
243
|
+
}
|
|
244
|
+
return event;
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Python
|
|
250
|
+
```python
|
|
251
|
+
import sentry_sdk
|
|
252
|
+
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
|
253
|
+
from sentry_sdk.scrubber import EventScrubber, DEFAULT_DENYLIST
|
|
254
|
+
|
|
255
|
+
sentry_sdk.init(
|
|
256
|
+
dsn=os.environ["SENTRY_DSN"],
|
|
257
|
+
environment=os.environ["APP_ENV"],
|
|
258
|
+
traces_sample_rate=0.1,
|
|
259
|
+
integrations=[FastApiIntegration()],
|
|
260
|
+
event_scrubber=EventScrubber(denylist=DEFAULT_DENYLIST + ["jwt", "session"]),
|
|
261
|
+
send_default_pii=False,
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 6. Metrics
|
|
268
|
+
|
|
269
|
+
Track **RED** (Rate / Errors / Duration) on every endpoint and **USE** (Utilization / Saturation / Errors) on every resource.
|
|
270
|
+
|
|
271
|
+
OpenTelemetry metrics + Prometheus exporter, or vendor SDK (Datadog, New Relic).
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { metrics } from '@opentelemetry/api';
|
|
275
|
+
const meter = metrics.getMeter('api');
|
|
276
|
+
|
|
277
|
+
const httpDuration = meter.createHistogram('http.server.duration', { unit: 'ms' });
|
|
278
|
+
const ordersCreated = meter.createCounter('orders.created');
|
|
279
|
+
|
|
280
|
+
httpDuration.record(durationMs, { method, route, status: String(statusCode) });
|
|
281
|
+
ordersCreated.add(1, { plan: order.plan });
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Cardinality rule: never tag with user_id, email, or unbounded values — explodes time-series storage. Use `route`, `status`, `plan`, etc.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 7. Health Checks
|
|
289
|
+
|
|
290
|
+
Two endpoints, distinct semantics:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
app.get('/healthz', (_, res) => res.json({ ok: true })); // liveness — am I running?
|
|
294
|
+
|
|
295
|
+
app.get('/readyz', async (_, res) => { // readiness — can I serve?
|
|
296
|
+
const checks = await Promise.allSettled([
|
|
297
|
+
db.raw('SELECT 1'),
|
|
298
|
+
redis.ping(),
|
|
299
|
+
]);
|
|
300
|
+
const allOk = checks.every(c => c.status === 'fulfilled');
|
|
301
|
+
res.status(allOk ? 200 : 503).json({ ok: allOk, checks });
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Kubernetes uses `livenessProbe` → `/healthz` (restart on fail) and `readinessProbe` → `/readyz` (remove from LB on fail).
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 8. Audit Log — Separate Stream
|
|
310
|
+
|
|
311
|
+
Security-relevant events go to a dedicated, append-only stream:
|
|
312
|
+
|
|
313
|
+
- Logins (success + failure)
|
|
314
|
+
- Authorization denials
|
|
315
|
+
- Permission/role changes
|
|
316
|
+
- Admin actions (impersonation, data exports, deletes)
|
|
317
|
+
- Payment events
|
|
318
|
+
- Data access (when required by compliance: HIPAA, SOC 2)
|
|
319
|
+
|
|
320
|
+
Different retention (often longer), different access (security team), different integrity guarantees (immutable, hash-chained).
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Pre-Commit Checklist
|
|
325
|
+
|
|
326
|
+
- [ ] Logger configured with redact paths for password/token/cookie/authorization
|
|
327
|
+
- [ ] All endpoints emit `request.start` / `request.end` with `request_id`
|
|
328
|
+
- [ ] Errors include stack trace and `trace_id`
|
|
329
|
+
- [ ] No `console.log` / `print` / `dd()` left in code (use the logger)
|
|
330
|
+
- [ ] No PII in info-level logs
|
|
331
|
+
- [ ] Sentry initialized with `beforeSend` scrubber
|
|
332
|
+
- [ ] Tracing initialized at process start (before app code)
|
|
333
|
+
- [ ] `/healthz` + `/readyz` endpoints exist
|
|
334
|
+
|
|
335
|
+
## FORBIDDEN
|
|
336
|
+
|
|
337
|
+
| Pattern | Reason |
|
|
338
|
+
|---|---|
|
|
339
|
+
| `console.log(req.body)` | Leaks passwords, tokens |
|
|
340
|
+
| Logger without redaction | Future caller will leak |
|
|
341
|
+
| Unbounded label cardinality (user_id as metric tag) | Cost explosion |
|
|
342
|
+
| Same severity for everything (all `info`) | No alerting signal |
|
|
343
|
+
| Tracing in dev only | Prod is where you need it |
|
|
344
|
+
| Sentry without scrubber | PII in error reports |
|
|
345
|
+
| Audit log in same stream as app log | Tamper risk, retention conflict |
|
|
346
|
+
|
|
347
|
+
## See Also
|
|
348
|
+
|
|
349
|
+
- `secrets-management` — what NEVER to log
|
|
350
|
+
- `error-handling` — Result types and error taxonomy
|
|
351
|
+
- `security-baseline` — A09: Security Logging Failures
|